mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
@@ -1,5 +1,5 @@
|
||||
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
|
||||
NEXT_PUBLIC_VERSION=4.4.2
|
||||
NEXT_PUBLIC_VERSION=4.4.3
|
||||
|
||||
|
||||
# 可在此添加环境变量,去掉最左边的(# )注释即可
|
||||
|
||||
202
blog.config.js
202
blog.config.js
@@ -1,7 +1,9 @@
|
||||
// 注: process.env.XX是Vercel的环境变量,配置方式见:https://docs.tangly1024.com/article/how-to-config-notion-next#c4768010ae7d44609b744e79e2f9959a
|
||||
const BLOG = {
|
||||
// Important page_id!!!Duplicate Template from https://www.notion.so/tanghh/02ab3b8678004aa69e9e415905ef32a5
|
||||
NOTION_PAGE_ID: process.env.NOTION_PAGE_ID || '02ab3b8678004aa69e9e415905ef32a5',
|
||||
NOTION_PAGE_ID:
|
||||
process.env.NOTION_PAGE_ID ||
|
||||
'02ab3b8678004aa69e9e415905ef32a5,en:7c1d570661754c8fbc568e00a01fd70e',
|
||||
PSEUDO_STATIC: process.env.NEXT_PUBLIC_PSEUDO_STATIC || false, // 伪静态路径,开启后所有文章URL都以 .html 结尾。
|
||||
NEXT_REVALIDATE_SECOND: process.env.NEXT_PUBLIC_REVALIDATE_SECOND || 5, // 更新内容缓存间隔 单位(秒);即每个页面有5秒的纯静态期、此期间无论多少次访问都不会抓取notion数据;调大该值有助于节省Vercel资源、同时提升访问速率,但也会使文章更新有延迟。
|
||||
THEME: process.env.NEXT_PUBLIC_THEME || 'simple', // 当前主题,在themes文件夹下可找到所有支持的主题;主题名称就是文件夹名,例如 example,fukasawa,gitbook,heo,hexo,landing,matery,medium,next,nobelium,plog,simple
|
||||
@@ -11,7 +13,8 @@ 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时关闭根据时间自动切换夜间模式
|
||||
|
||||
IS_TAG_COLOR_DISTINGUISHED: process.env.NEXT_PUBLIC_IS_TAG_COLOR_DISTINGUISHED === 'true' || true, // 对于名称相同的tag是否区分tag的颜色
|
||||
IS_TAG_COLOR_DISTINGUISHED:
|
||||
process.env.NEXT_PUBLIC_IS_TAG_COLOR_DISTINGUISHED === 'true' || true, // 对于名称相同的tag是否区分tag的颜色
|
||||
|
||||
// 3.14.1版本后,欢迎语在此配置,英文逗号隔开 , 即可支持多个欢迎语打字效果。
|
||||
GREETING_WORDS:
|
||||
@@ -43,7 +46,9 @@ const BLOG = {
|
||||
IMAGE_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMAGE_COMPRESS_WIDTH || 800, // 图片压缩宽度默认值,作用于博客封面和文章内容 越小加载图片越快
|
||||
IMAGE_ZOOM_IN_WIDTH: process.env.NEXT_PUBLIC_IMAGE_ZOOM_IN_WIDTH || 1200, // 文章图片点击放大后的画质宽度,不代表在网页中的实际展示宽度
|
||||
RANDOM_IMAGE_URL: process.env.NEXT_PUBLIC_RANDOM_IMAGE_URL || '', // 随机图片API,如果未配置下面的关键字,主页封面,头像,文章封面图都会被替换为随机图片
|
||||
RANDOM_IMAGE_REPLACE_TEXT: process.env.NEXT_PUBLIC_RANDOM_IMAGE_NOT_REPLACE_TEXT || 'images.unsplash.com', // 触发替换图片的 url 关键字(多个支持用英文逗号分开),只有图片地址中包含此关键字才会替换为上方随机图片url
|
||||
RANDOM_IMAGE_REPLACE_TEXT:
|
||||
process.env.NEXT_PUBLIC_RANDOM_IMAGE_NOT_REPLACE_TEXT ||
|
||||
'images.unsplash.com', // 触发替换图片的 url 关键字(多个支持用英文逗号分开),只有图片地址中包含此关键字才会替换为上方随机图片url
|
||||
// eg: images.unsplash.com(notion图床的所有图片都会替换),如果你在 notion 里已经添加了一个随机图片 url,恰巧那个服务跑路或者挂掉,想一键切换所有配图可以将该 url 配置在这里
|
||||
// 默认下会将你上传到 notion的主页封面图和头像也给替换,建议将主页封面图和头像放在其他图床,在 notion 里配置 link 即可。
|
||||
|
||||
@@ -122,23 +127,29 @@ const BLOG = {
|
||||
|
||||
CAN_COPY: process.env.NEXT_PUBLIC_CAN_COPY || true, // 是否允许复制页面内容 默认允许,如果设置为false、则全栈禁止复制内容。
|
||||
// 自定义右键菜单
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU || true, // 自定义右键菜单,覆盖系统菜单
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU || true, // 自定义右键菜单,覆盖系统菜单
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH || true, // 是否显示切换主题
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE || true, // 是否显示深色模式
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH ||
|
||||
true, // 是否显示切换主题
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE || true, // 是否显示深色模式
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK || true, // 是否显示分享链接
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST || true, // 是否显示随机博客
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY || true, // 是否显示分类
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_TAG || true, // 是否显示标签
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY || true, // 是否显示分类
|
||||
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG:
|
||||
process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_TAG || true, // 是否显示标签
|
||||
|
||||
// 自定义外部脚本,外部样式
|
||||
CUSTOM_EXTERNAL_JS: [''], // e.g. ['http://xx.com/script.js','http://xx.com/script.js']
|
||||
CUSTOM_EXTERNAL_CSS: [''], // e.g. ['http://xx.com/style.css','http://xx.com/style.css']
|
||||
|
||||
// 侧栏布局 是否反转(左变右,右变左) 已支持主题: hexo next medium fukasawa example
|
||||
LAYOUT_SIDEBAR_REVERSE: process.env.NEXT_PUBLIC_LAYOUT_SIDEBAR_REVERSE || false,
|
||||
LAYOUT_SIDEBAR_REVERSE:
|
||||
process.env.NEXT_PUBLIC_LAYOUT_SIDEBAR_REVERSE || false,
|
||||
|
||||
// 一个小插件展示你的facebook fan page~ @see https://tw.andys.pro/article/add-facebook-fanpage-notionnext
|
||||
FACEBOOK_PAGE_TITLE: process.env.NEXT_PUBLIC_FACEBOOK_PAGE_TITLE || null, // 邊欄 Facebook Page widget 的標題欄,填''則無標題欄 e.g FACEBOOK 粉絲團'
|
||||
@@ -151,7 +162,8 @@ const BLOG = {
|
||||
// START********代码相关********
|
||||
// PrismJs 代码相关
|
||||
PRISM_JS_PATH: 'https://npm.elemecdn.com/prismjs@1.29.0/components/',
|
||||
PRISM_JS_AUTO_LOADER: 'https://npm.elemecdn.com/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js',
|
||||
PRISM_JS_AUTO_LOADER:
|
||||
'https://npm.elemecdn.com/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js',
|
||||
|
||||
// 代码主题 @see https://github.com/PrismJS/prism-themes
|
||||
PRISM_THEME_PREFIX_PATH:
|
||||
@@ -168,16 +180,19 @@ const BLOG = {
|
||||
CODE_MAC_BAR: process.env.NEXT_PUBLIC_CODE_MAC_BAR || true, // 代码左上角显示mac的红黄绿图标
|
||||
CODE_LINE_NUMBERS: process.env.NEXT_PUBLIC_CODE_LINE_NUMBERS || false, // 是否显示行号
|
||||
CODE_COLLAPSE: process.env.NEXT_PUBLIC_CODE_COLLAPSE || true, // 是否支持折叠代码框
|
||||
CODE_COLLAPSE_EXPAND_DEFAULT: process.env.NEXT_PUBLIC_CODE_COLLAPSE_EXPAND_DEFAULT || true, // 折叠代码默认是展开状态
|
||||
CODE_COLLAPSE_EXPAND_DEFAULT:
|
||||
process.env.NEXT_PUBLIC_CODE_COLLAPSE_EXPAND_DEFAULT || true, // 折叠代码默认是展开状态
|
||||
|
||||
// END********代码相关********
|
||||
|
||||
// Mermaid 图表CDN
|
||||
MERMAID_CDN:
|
||||
process.env.NEXT_PUBLIC_MERMAID_CDN || 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.4/mermaid.min.js', // CDN
|
||||
process.env.NEXT_PUBLIC_MERMAID_CDN ||
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.4/mermaid.min.js', // CDN
|
||||
// QRCodeCDN
|
||||
QR_CODE_CDN:
|
||||
process.env.NEXT_PUBLIC_QR_CODE_CDN || 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js',
|
||||
process.env.NEXT_PUBLIC_QR_CODE_CDN ||
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js',
|
||||
|
||||
BACKGROUND_LIGHT: '#eeeeee', // use hex value, don't forget '#' e.g #fffefc
|
||||
BACKGROUND_DARK: '#000000', // use hex value, don't forget '#'
|
||||
@@ -202,24 +217,32 @@ const BLOG = {
|
||||
POSTS_PER_PAGE: process.env.NEXT_PUBLIC_POST_PER_PAGE || 12, // post counts per page
|
||||
POSTS_SORT_BY: process.env.NEXT_PUBLIC_POST_SORT_BY || 'notion', // 排序方式 'date'按时间,'notion'由notion控制
|
||||
|
||||
POST_WAITING_TIME_FOR_404: process.env.NEXT_PUBLIC_POST_WAITING_TIME_FOR_404 || '8', // 文章加载超时时间,单位秒;超时后跳转到404页面
|
||||
POST_WAITING_TIME_FOR_404:
|
||||
process.env.NEXT_PUBLIC_POST_WAITING_TIME_FOR_404 || '8', // 文章加载超时时间,单位秒;超时后跳转到404页面
|
||||
|
||||
ALGOLIA_APP_ID: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || null, // 在这里查看 https://dashboard.algolia.com/account/api-keys/
|
||||
ALGOLIA_ADMIN_APP_KEY: process.env.ALGOLIA_ADMIN_APP_KEY || null, // 管理后台的KEY,不要暴露在代码中,在这里查看 https://dashboard.algolia.com/account/api-keys/
|
||||
ALGOLIA_SEARCH_ONLY_APP_KEY: process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_APP_KEY || null, // 客户端搜索用的KEY
|
||||
ALGOLIA_SEARCH_ONLY_APP_KEY:
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_APP_KEY || null, // 客户端搜索用的KEY
|
||||
ALGOLIA_INDEX: process.env.NEXT_PUBLIC_ALGOLIA_INDEX || null, // 在Algolia中创建一个index用作数据库
|
||||
// ALGOLIA_RECREATE_DATA: process.env.ALGOLIA_RECREATE_DATA || process.env.npm_lifecycle_event === 'build', // 为true时重新构建索引数据; 默认在build时会构建
|
||||
|
||||
PREVIEW_CATEGORY_COUNT: 16, // 首页最多展示的分类数量,0为不限制
|
||||
PREVIEW_TAG_COUNT: 16, // 首页最多展示的标签数量,0为不限制
|
||||
|
||||
POST_DISABLE_GALLERY_CLICK: process.env.NEXT_PUBLIC_POST_DISABLE_GALLERY_CLICK || false, // 画册视图禁止点击,方便在友链页面的画册插入链接
|
||||
POST_DISABLE_GALLERY_CLICK:
|
||||
process.env.NEXT_PUBLIC_POST_DISABLE_GALLERY_CLICK || false, // 画册视图禁止点击,方便在友链页面的画册插入链接
|
||||
|
||||
// ********动态特效相关********
|
||||
// 鼠标点击烟花特效
|
||||
FIREWORKS: process.env.NEXT_PUBLIC_FIREWORKS || false, // 开关
|
||||
// 烟花色彩,感谢 https://github.com/Vixcity 提交的色彩
|
||||
FIREWORKS_COLOR: ['255, 20, 97', '24, 255, 146', '90, 135, 255', '251, 243, 140'],
|
||||
FIREWORKS_COLOR: [
|
||||
'255, 20, 97',
|
||||
'24, 255, 146',
|
||||
'90, 135, 255',
|
||||
'251, 243, 140'
|
||||
],
|
||||
|
||||
// 樱花飘落特效
|
||||
SAKURA: process.env.NEXT_PUBLIC_SAKURA || false, // 开关
|
||||
@@ -238,14 +261,16 @@ const BLOG = {
|
||||
process.env.NEXT_PUBLIC_TIANLI_GPT_CSS ||
|
||||
'https://cdn1.tianli0.top/gh/zhheo/Post-Abstract-AI@0.15.2/tianli_gpt.css',
|
||||
TianliGPT_JS:
|
||||
process.env.NEXT_PUBLIC_TIANLI_GPT_JS || 'https://cdn1.tianli0.top/gh/zhheo/Post-Abstract-AI@0.15.2/tianli_gpt.js',
|
||||
process.env.NEXT_PUBLIC_TIANLI_GPT_JS ||
|
||||
'https://cdn1.tianli0.top/gh/zhheo/Post-Abstract-AI@0.15.2/tianli_gpt.js',
|
||||
TianliGPT_KEY: process.env.NEXT_PUBLIC_TIANLI_GPT_KEY || '',
|
||||
|
||||
// Chatbase 是否显示chatbase机器人 https://www.chatbase.co/
|
||||
CHATBASE_ID: process.env.NEXT_PUBLIC_CHATBASE_ID || null,
|
||||
// WebwhizAI 机器人 @see https://github.com/webwhiz-ai/webwhiz
|
||||
WEB_WHIZ_ENABLED: process.env.NEXT_PUBLIC_WEB_WHIZ_ENABLED || false, // 是否显示
|
||||
WEB_WHIZ_BASE_URL: process.env.NEXT_PUBLIC_WEB_WHIZ_BASE_URL || 'https://api.webwhiz.ai', // 可以自建服务器
|
||||
WEB_WHIZ_BASE_URL:
|
||||
process.env.NEXT_PUBLIC_WEB_WHIZ_BASE_URL || 'https://api.webwhiz.ai', // 可以自建服务器
|
||||
WEB_WHIZ_CHAT_BOT_ID: process.env.NEXT_PUBLIC_WEB_WHIZ_CHAT_BOT_ID || null, // 在后台获取ID
|
||||
DIFY_CHATBOT_ENABLED: process.env.NEXT_PUBLIC_DIFY_CHATBOT_ENABLED || false,
|
||||
DIFY_CHATBOT_BASE_URL: process.env.NEXT_PUBLIC_DIFY_CHATBOT_BASE_URL || '',
|
||||
@@ -255,12 +280,14 @@ const BLOG = {
|
||||
WIDGET_PET_LINK:
|
||||
process.env.NEXT_PUBLIC_WIDGET_PET_LINK ||
|
||||
'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models
|
||||
WIDGET_PET_SWITCH_THEME: process.env.NEXT_PUBLIC_WIDGET_PET_SWITCH_THEME || true, // 点击宠物挂件切换博客主题
|
||||
WIDGET_PET_SWITCH_THEME:
|
||||
process.env.NEXT_PUBLIC_WIDGET_PET_SWITCH_THEME || true, // 点击宠物挂件切换博客主题
|
||||
|
||||
// 音乐播放插件
|
||||
MUSIC_PLAYER: process.env.NEXT_PUBLIC_MUSIC_PLAYER || false, // 是否使用音乐播放插件
|
||||
MUSIC_PLAYER_VISIBLE: process.env.NEXT_PUBLIC_MUSIC_PLAYER_VISIBLE || true, // 是否在左下角显示播放和切换,如果使用播放器,打开自动播放再隐藏,就会以类似背景音乐的方式播放,无法取消和暂停
|
||||
MUSIC_PLAYER_AUTO_PLAY: process.env.NEXT_PUBLIC_MUSIC_PLAYER_AUTO_PLAY || true, // 是否自动播放,不过自动播放时常不生效(移动设备不支持自动播放)
|
||||
MUSIC_PLAYER_AUTO_PLAY:
|
||||
process.env.NEXT_PUBLIC_MUSIC_PLAYER_AUTO_PLAY || true, // 是否自动播放,不过自动播放时常不生效(移动设备不支持自动播放)
|
||||
MUSIC_PLAYER_LRC_TYPE: process.env.NEXT_PUBLIC_MUSIC_PLAYER_LRC_TYPE || '0', // 歌词显示类型,可选值: 3 | 1 | 0(0:禁用 lrc 歌词,1:lrc 格式的字符串,3:lrc 文件 url)(前提是有配置歌词路径,对 meting 无效)
|
||||
MUSIC_PLAYER_CDN_URL:
|
||||
process.env.NEXT_PUBLIC_MUSIC_PLAYER_CDN_URL ||
|
||||
@@ -272,78 +299,105 @@ const BLOG = {
|
||||
name: '风を共に舞う気持ち',
|
||||
artist: 'Falcom Sound Team jdk',
|
||||
url: 'https://music.163.com/song/media/outer/url?id=731419.mp3',
|
||||
cover: 'https://p2.music.126.net/kn6ugISTonvqJh3LHLaPtQ==/599233837187278.jpg'
|
||||
cover:
|
||||
'https://p2.music.126.net/kn6ugISTonvqJh3LHLaPtQ==/599233837187278.jpg'
|
||||
},
|
||||
{
|
||||
name: '王都グランセル',
|
||||
artist: 'Falcom Sound Team jdk',
|
||||
url: 'https://music.163.com/song/media/outer/url?id=731355.mp3',
|
||||
cover: 'https://p1.music.126.net/kn6ugISTonvqJh3LHLaPtQ==/599233837187278.jpg'
|
||||
cover:
|
||||
'https://p1.music.126.net/kn6ugISTonvqJh3LHLaPtQ==/599233837187278.jpg'
|
||||
}
|
||||
],
|
||||
MUSIC_PLAYER_METING: process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING || false, // 是否要开启 MetingJS,从平台获取歌单。会覆盖自定义的 MUSIC_PLAYER_AUDIO_LIST,更多配置信息:https://github.com/metowolf/MetingJS
|
||||
MUSIC_PLAYER_METING_SERVER: process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING_SERVER || 'netease', // 音乐平台,[netease, tencent, kugou, xiami, baidu]
|
||||
MUSIC_PLAYER_METING_ID: process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING_ID || '60198', // 对应歌单的 id
|
||||
MUSIC_PLAYER_METING_LRC_TYPE: process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING_LRC_TYPE || '1', // 可选值: 3 | 1 | 0(0:禁用 lrc 歌词,1:lrc 格式的字符串,3:lrc 文件 url)
|
||||
MUSIC_PLAYER_METING_SERVER:
|
||||
process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING_SERVER || 'netease', // 音乐平台,[netease, tencent, kugou, xiami, baidu]
|
||||
MUSIC_PLAYER_METING_ID:
|
||||
process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING_ID || '60198', // 对应歌单的 id
|
||||
MUSIC_PLAYER_METING_LRC_TYPE:
|
||||
process.env.NEXT_PUBLIC_MUSIC_PLAYER_METING_LRC_TYPE || '1', // 可选值: 3 | 1 | 0(0:禁用 lrc 歌词,1:lrc 格式的字符串,3:lrc 文件 url)
|
||||
|
||||
// ********挂件组件相关********
|
||||
// ----> 评论互动 可同时开启多个支持 WALINE VALINE GISCUS CUSDIS UTTERRANCES GITALK
|
||||
|
||||
COMMENT_HIDE_SINGLE_TAB: process.env.NEXT_PUBLIC_COMMENT_HIDE_SINGLE_TAB || false, // Whether hide the tab when there's no tabs. 只有一个评论组件时是否隐藏切换组件的标签页
|
||||
COMMENT_HIDE_SINGLE_TAB:
|
||||
process.env.NEXT_PUBLIC_COMMENT_HIDE_SINGLE_TAB || false, // Whether hide the tab when there's no tabs. 只有一个评论组件时是否隐藏切换组件的标签页
|
||||
|
||||
// artalk 评论插件
|
||||
COMMENT_ARTALK_SERVER: process.env.NEXT_PUBLIC_COMMENT_ARTALK_SERVER || '', // ArtalkServert后端地址 https://artalk.js.org/guide/deploy.html
|
||||
COMMENT_ARTALK_JS:
|
||||
process.env.NEXT_PUBLIC_COMMENT_ARTALK_JS || 'https://cdnjs.cloudflare.com/ajax/libs/artalk/2.5.5/Artalk.js', // ArtalkServert js cdn
|
||||
process.env.NEXT_PUBLIC_COMMENT_ARTALK_JS ||
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/artalk/2.5.5/Artalk.js', // ArtalkServert js cdn
|
||||
COMMENT_ARTALK_CSS:
|
||||
process.env.NEXT_PUBLIC_COMMENT_ARTALK_CSS || 'https://cdnjs.cloudflare.com/ajax/libs/artalk/2.5.5/Artalk.css', // ArtalkServert css cdn
|
||||
process.env.NEXT_PUBLIC_COMMENT_ARTALK_CSS ||
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/artalk/2.5.5/Artalk.css', // ArtalkServert css cdn
|
||||
|
||||
// twikoo
|
||||
COMMENT_TWIKOO_ENV_ID: process.env.NEXT_PUBLIC_COMMENT_ENV_ID || '', // TWIKOO后端地址 腾讯云环境填envId;Vercel环境填域名,教程:https://tangly1024.com/article/notionnext-twikoo
|
||||
COMMENT_TWIKOO_COUNT_ENABLE: process.env.NEXT_PUBLIC_COMMENT_TWIKOO_COUNT_ENABLE || false, // 博客列表是否显示评论数
|
||||
COMMENT_TWIKOO_COUNT_ENABLE:
|
||||
process.env.NEXT_PUBLIC_COMMENT_TWIKOO_COUNT_ENABLE || false, // 博客列表是否显示评论数
|
||||
COMMENT_TWIKOO_CDN_URL:
|
||||
process.env.NEXT_PUBLIC_COMMENT_TWIKOO_CDN_URL || 'https://cdn.staticfile.org/twikoo/1.6.17/twikoo.min.js', // twikoo客户端cdn
|
||||
process.env.NEXT_PUBLIC_COMMENT_TWIKOO_CDN_URL ||
|
||||
'https://cdn.staticfile.org/twikoo/1.6.17/twikoo.min.js', // twikoo客户端cdn
|
||||
|
||||
// utterance
|
||||
COMMENT_UTTERRANCES_REPO: process.env.NEXT_PUBLIC_COMMENT_UTTERRANCES_REPO || '', // 你的代码仓库名, 例如我是 'tangly1024/NotionNext'; 更多文档参考 https://utteranc.es/
|
||||
COMMENT_UTTERRANCES_REPO:
|
||||
process.env.NEXT_PUBLIC_COMMENT_UTTERRANCES_REPO || '', // 你的代码仓库名, 例如我是 'tangly1024/NotionNext'; 更多文档参考 https://utteranc.es/
|
||||
|
||||
// giscus @see https://giscus.app/
|
||||
COMMENT_GISCUS_REPO: process.env.NEXT_PUBLIC_COMMENT_GISCUS_REPO || '', // 你的Github仓库名 e.g 'tangly1024/NotionNext'
|
||||
COMMENT_GISCUS_REPO_ID: process.env.NEXT_PUBLIC_COMMENT_GISCUS_REPO_ID || '', // 你的Github Repo ID e.g ( 設定完 giscus 即可看到 )
|
||||
COMMENT_GISCUS_CATEGORY_ID: process.env.NEXT_PUBLIC_COMMENT_GISCUS_CATEGORY_ID || '', // 你的Github Discussions 內的 Category ID ( 設定完 giscus 即可看到 )
|
||||
COMMENT_GISCUS_MAPPING: process.env.NEXT_PUBLIC_COMMENT_GISCUS_MAPPING || 'pathname', // 你的Github Discussions 使用哪種方式來標定文章, 預設 'pathname'
|
||||
COMMENT_GISCUS_REACTIONS_ENABLED: process.env.NEXT_PUBLIC_COMMENT_GISCUS_REACTIONS_ENABLED || '1', // 你的 Giscus 是否開啟文章表情符號 '1' 開啟 "0" 關閉 預設開啟
|
||||
COMMENT_GISCUS_EMIT_METADATA: process.env.NEXT_PUBLIC_COMMENT_GISCUS_EMIT_METADATA || '0', // 你的 Giscus 是否提取 Metadata '1' 開啟 '0' 關閉 預設關閉
|
||||
COMMENT_GISCUS_INPUT_POSITION: process.env.NEXT_PUBLIC_COMMENT_GISCUS_INPUT_POSITION || 'bottom', // 你的 Giscus 發表留言位置 'bottom' 尾部 'top' 頂部, 預設 'bottom'
|
||||
COMMENT_GISCUS_CATEGORY_ID:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_CATEGORY_ID || '', // 你的Github Discussions 內的 Category ID ( 設定完 giscus 即可看到 )
|
||||
COMMENT_GISCUS_MAPPING:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_MAPPING || 'pathname', // 你的Github Discussions 使用哪種方式來標定文章, 預設 'pathname'
|
||||
COMMENT_GISCUS_REACTIONS_ENABLED:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_REACTIONS_ENABLED || '1', // 你的 Giscus 是否開啟文章表情符號 '1' 開啟 "0" 關閉 預設開啟
|
||||
COMMENT_GISCUS_EMIT_METADATA:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_EMIT_METADATA || '0', // 你的 Giscus 是否提取 Metadata '1' 開啟 '0' 關閉 預設關閉
|
||||
COMMENT_GISCUS_INPUT_POSITION:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_INPUT_POSITION || 'bottom', // 你的 Giscus 發表留言位置 'bottom' 尾部 'top' 頂部, 預設 'bottom'
|
||||
COMMENT_GISCUS_LANG: process.env.NEXT_PUBLIC_COMMENT_GISCUS_LANG || 'zh-CN', // 你的 Giscus 語言 e.g 'en', 'zh-TW', 'zh-CN', 預設 'en'
|
||||
COMMENT_GISCUS_LOADING: process.env.NEXT_PUBLIC_COMMENT_GISCUS_LOADING || 'lazy', // 你的 Giscus 載入是否漸進式載入, 預設 'lazy'
|
||||
COMMENT_GISCUS_CROSSORIGIN: process.env.NEXT_PUBLIC_COMMENT_GISCUS_CROSSORIGIN || 'anonymous', // 你的 Giscus 可以跨網域, 預設 'anonymous'
|
||||
COMMENT_GISCUS_LOADING:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_LOADING || 'lazy', // 你的 Giscus 載入是否漸進式載入, 預設 'lazy'
|
||||
COMMENT_GISCUS_CROSSORIGIN:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GISCUS_CROSSORIGIN || 'anonymous', // 你的 Giscus 可以跨網域, 預設 'anonymous'
|
||||
|
||||
COMMENT_CUSDIS_APP_ID: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_APP_ID || '', // data-app-id 36位 see https://cusdis.com/
|
||||
COMMENT_CUSDIS_HOST: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_HOST || 'https://cusdis.com', // data-host, change this if you're using self-hosted version
|
||||
COMMENT_CUSDIS_SCRIPT_SRC: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_SCRIPT_SRC || '/js/cusdis.es.js', // change this if you're using self-hosted version
|
||||
COMMENT_CUSDIS_HOST:
|
||||
process.env.NEXT_PUBLIC_COMMENT_CUSDIS_HOST || 'https://cusdis.com', // data-host, change this if you're using self-hosted version
|
||||
COMMENT_CUSDIS_SCRIPT_SRC:
|
||||
process.env.NEXT_PUBLIC_COMMENT_CUSDIS_SCRIPT_SRC || '/js/cusdis.es.js', // change this if you're using self-hosted version
|
||||
|
||||
// gitalk评论插件 更多参考 https://gitalk.github.io/
|
||||
COMMENT_GITALK_REPO: process.env.NEXT_PUBLIC_COMMENT_GITALK_REPO || '', // 你的Github仓库名,例如 'NotionNext'
|
||||
COMMENT_GITALK_OWNER: process.env.NEXT_PUBLIC_COMMENT_GITALK_OWNER || '', // 你的用户名 e.g tangly1024
|
||||
COMMENT_GITALK_ADMIN: process.env.NEXT_PUBLIC_COMMENT_GITALK_ADMIN || '', // 管理员用户名、一般是自己 e.g 'tangly1024'
|
||||
COMMENT_GITALK_CLIENT_ID: process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_ID || '', // e.g 20位ID , 在gitalk后台获取
|
||||
COMMENT_GITALK_CLIENT_SECRET: process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_SECRET || '', // e.g 40位ID, 在gitalk后台获取
|
||||
COMMENT_GITALK_CLIENT_ID:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_ID || '', // e.g 20位ID , 在gitalk后台获取
|
||||
COMMENT_GITALK_CLIENT_SECRET:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_SECRET || '', // e.g 40位ID, 在gitalk后台获取
|
||||
COMMENT_GITALK_DISTRACTION_FREE_MODE: false, // 类似facebook的无干扰模式
|
||||
COMMENT_GITALK_JS_CDN_URL:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GITALK_JS_CDN_URL || 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js', // gitalk客户端 js cdn
|
||||
process.env.NEXT_PUBLIC_COMMENT_GITALK_JS_CDN_URL ||
|
||||
'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js', // gitalk客户端 js cdn
|
||||
COMMENT_GITALK_CSS_CDN_URL:
|
||||
process.env.NEXT_PUBLIC_COMMENT_GITALK_CSS_CDN_URL || 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css', // gitalk客户端 css cdn
|
||||
process.env.NEXT_PUBLIC_COMMENT_GITALK_CSS_CDN_URL ||
|
||||
'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css', // gitalk客户端 css cdn
|
||||
|
||||
COMMENT_GITTER_ROOM: process.env.NEXT_PUBLIC_COMMENT_GITTER_ROOM || '', // gitter聊天室 see https://gitter.im/ 不需要则留空
|
||||
COMMENT_DAO_VOICE_ID: process.env.NEXT_PUBLIC_COMMENT_DAO_VOICE_ID || '', // DaoVoice http://dashboard.daovoice.io/get-started
|
||||
COMMENT_TIDIO_ID: process.env.NEXT_PUBLIC_COMMENT_TIDIO_ID || '', // [tidio_id] -> //code.tidio.co/[tidio_id].js
|
||||
|
||||
COMMENT_VALINE_CDN: process.env.NEXT_PUBLIC_VALINE_CDN || 'https://unpkg.com/valine@1.5.1/dist/Valine.min.js',
|
||||
COMMENT_VALINE_CDN:
|
||||
process.env.NEXT_PUBLIC_VALINE_CDN ||
|
||||
'https://unpkg.com/valine@1.5.1/dist/Valine.min.js',
|
||||
COMMENT_VALINE_APP_ID: process.env.NEXT_PUBLIC_VALINE_ID || '', // Valine @see https://valine.js.org/quickstart.html 或 https://github.com/stonehank/react-valine#%E8%8E%B7%E5%8F%96app-id-%E5%92%8C-app-key
|
||||
COMMENT_VALINE_APP_KEY: process.env.NEXT_PUBLIC_VALINE_KEY || '',
|
||||
COMMENT_VALINE_SERVER_URLS: process.env.NEXT_PUBLIC_VALINE_SERVER_URLS || '', // 该配置适用于国内自定义域名用户, 海外版本会自动检测(无需手动填写) @see https://valine.js.org/configuration.html#serverURLs
|
||||
COMMENT_VALINE_PLACEHOLDER: process.env.NEXT_PUBLIC_VALINE_PLACEHOLDER || '抢个沙发吧~', // 可以搭配后台管理评论 https://github.com/DesertsP/Valine-Admin 便于查看评论,以及邮件通知,垃圾评论过滤等功能
|
||||
COMMENT_VALINE_PLACEHOLDER:
|
||||
process.env.NEXT_PUBLIC_VALINE_PLACEHOLDER || '抢个沙发吧~', // 可以搭配后台管理评论 https://github.com/DesertsP/Valine-Admin 便于查看评论,以及邮件通知,垃圾评论过滤等功能
|
||||
|
||||
COMMENT_WALINE_SERVER_URL: process.env.NEXT_PUBLIC_WALINE_SERVER_URL || '', // 请配置完整的Waline评论地址 例如 hhttps://preview-waline.tangly1024.com @see https://waline.js.org/guide/get-started.html
|
||||
COMMENT_WALINE_RECENT: process.env.NEXT_PUBLIC_WALINE_RECENT || false, // 最新评论
|
||||
@@ -357,15 +411,18 @@ const BLOG = {
|
||||
// TOKEN: Webmention的API token
|
||||
COMMENT_WEBMENTION_ENABLE: process.env.NEXT_PUBLIC_WEBMENTION_ENABLE || false,
|
||||
COMMENT_WEBMENTION_AUTH: process.env.NEXT_PUBLIC_WEBMENTION_AUTH || '',
|
||||
COMMENT_WEBMENTION_HOSTNAME: process.env.NEXT_PUBLIC_WEBMENTION_HOSTNAME || '',
|
||||
COMMENT_WEBMENTION_TWITTER_USERNAME: process.env.NEXT_PUBLIC_TWITTER_USERNAME || '',
|
||||
COMMENT_WEBMENTION_HOSTNAME:
|
||||
process.env.NEXT_PUBLIC_WEBMENTION_HOSTNAME || '',
|
||||
COMMENT_WEBMENTION_TWITTER_USERNAME:
|
||||
process.env.NEXT_PUBLIC_TWITTER_USERNAME || '',
|
||||
COMMENT_WEBMENTION_TOKEN: process.env.NEXT_PUBLIC_WEBMENTION_TOKEN || '',
|
||||
|
||||
// <---- 评论插件
|
||||
|
||||
// ----> 站点统计
|
||||
ANALYTICS_VERCEL: process.env.NEXT_PUBLIC_ANALYTICS_VERCEL || false, // vercel自带的统计 https://vercel.com/docs/concepts/analytics/quickstart https://github.com/tangly1024/NotionNext/issues/897
|
||||
ANALYTICS_BUSUANZI_ENABLE: process.env.NEXT_PUBLIC_ANALYTICS_BUSUANZI_ENABLE || true, // 展示网站阅读量、访问数 see http://busuanzi.ibruce.info/
|
||||
ANALYTICS_BUSUANZI_ENABLE:
|
||||
process.env.NEXT_PUBLIC_ANALYTICS_BUSUANZI_ENABLE || true, // 展示网站阅读量、访问数 see http://busuanzi.ibruce.info/
|
||||
ANALYTICS_BAIDU_ID: process.env.NEXT_PUBLIC_ANALYTICS_BAIDU_ID || '', // e.g 只需要填写百度统计的id,[baidu_id] -> https://hm.baidu.com/hm.js?[baidu_id]
|
||||
ANALYTICS_CNZZ_ID: process.env.NEXT_PUBLIC_ANALYTICS_CNZZ_ID || '', // 只需要填写站长统计的id, [cnzz_id] -> https://s9.cnzz.com/z_stat.php?id=[cnzz_id]&web_id=[cnzz_id]
|
||||
ANALYTICS_GOOGLE_ID: process.env.NEXT_PUBLIC_ANALYTICS_GOOGLE_ID || '', // 谷歌Analytics的id e.g: G-XXXXXXXXXX
|
||||
@@ -378,13 +435,18 @@ const BLOG = {
|
||||
MATOMO_HOST_URL: process.env.NEXT_PUBLIC_MATOMO_HOST_URL || '', // Matomo服务器地址,不带斜杠
|
||||
MATOMO_SITE_ID: process.env.NEXT_PUBLIC_MATOMO_SITE_ID || '', // Matomo网站ID
|
||||
// ACKEE网站访客统计工具
|
||||
ANALYTICS_ACKEE_TRACKER: process.env.NEXT_PUBLIC_ANALYTICS_ACKEE_TRACKER || '', // e.g 'https://ackee.tangly1024.com/tracker.js'
|
||||
ANALYTICS_ACKEE_DATA_SERVER: process.env.NEXT_PUBLIC_ANALYTICS_ACKEE_DATA_SERVER || '', // e.g https://ackee.tangly1024.com , don't end with a slash
|
||||
ANALYTICS_ACKEE_DOMAIN_ID: process.env.NEXT_PUBLIC_ANALYTICS_ACKEE_DOMAIN_ID || '', // e.g '82e51db6-dec2-423a-b7c9-b4ff7ebb3302'
|
||||
ANALYTICS_ACKEE_TRACKER:
|
||||
process.env.NEXT_PUBLIC_ANALYTICS_ACKEE_TRACKER || '', // e.g 'https://ackee.tangly1024.com/tracker.js'
|
||||
ANALYTICS_ACKEE_DATA_SERVER:
|
||||
process.env.NEXT_PUBLIC_ANALYTICS_ACKEE_DATA_SERVER || '', // e.g https://ackee.tangly1024.com , don't end with a slash
|
||||
ANALYTICS_ACKEE_DOMAIN_ID:
|
||||
process.env.NEXT_PUBLIC_ANALYTICS_ACKEE_DOMAIN_ID || '', // e.g '82e51db6-dec2-423a-b7c9-b4ff7ebb3302'
|
||||
|
||||
SEO_GOOGLE_SITE_VERIFICATION: process.env.NEXT_PUBLIC_SEO_GOOGLE_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code
|
||||
SEO_GOOGLE_SITE_VERIFICATION:
|
||||
process.env.NEXT_PUBLIC_SEO_GOOGLE_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code
|
||||
|
||||
SEO_BAIDU_SITE_VERIFICATION: process.env.NEXT_PUBLIC_SEO_BAIDU_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code
|
||||
SEO_BAIDU_SITE_VERIFICATION:
|
||||
process.env.NEXT_PUBLIC_SEO_BAIDU_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code
|
||||
|
||||
// 微软 Clarity 站点分析
|
||||
CLARITY_ID: process.env.NEXT_PUBLIC_CLARITY_ID || null, // 只需要复制Clarity脚本中的ID部分,ID是一个十位的英文数字组合
|
||||
@@ -396,10 +458,14 @@ const BLOG = {
|
||||
// 谷歌广告
|
||||
ADSENSE_GOOGLE_ID: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_ID || '', // 谷歌广告ID e.g ca-pub-xxxxxxxxxxxxxxxx
|
||||
ADSENSE_GOOGLE_TEST: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_TEST || false, // 谷歌广告ID测试模式,这种模式获取假的测试广告,用于开发 https://www.tangly1024.com/article/local-dev-google-adsense
|
||||
ADSENSE_GOOGLE_SLOT_IN_ARTICLE: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_IN_ARTICLE || '3806269138', // Google AdScene>广告>按单元广告>新建文章内嵌广告 粘贴html代码中的data-ad-slot值
|
||||
ADSENSE_GOOGLE_SLOT_FLOW: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_FLOW || '1510444138', // Google AdScene>广告>按单元广告>新建信息流广告
|
||||
ADSENSE_GOOGLE_SLOT_NATIVE: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_NATIVE || '4980048999', // Google AdScene>广告>按单元广告>新建原生广告
|
||||
ADSENSE_GOOGLE_SLOT_AUTO: process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_AUTO || '8807314373', // Google AdScene>广告>按单元广告>新建展示广告 (自动广告)
|
||||
ADSENSE_GOOGLE_SLOT_IN_ARTICLE:
|
||||
process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_IN_ARTICLE || '3806269138', // Google AdScene>广告>按单元广告>新建文章内嵌广告 粘贴html代码中的data-ad-slot值
|
||||
ADSENSE_GOOGLE_SLOT_FLOW:
|
||||
process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_FLOW || '1510444138', // Google AdScene>广告>按单元广告>新建信息流广告
|
||||
ADSENSE_GOOGLE_SLOT_NATIVE:
|
||||
process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_NATIVE || '4980048999', // Google AdScene>广告>按单元广告>新建原生广告
|
||||
ADSENSE_GOOGLE_SLOT_AUTO:
|
||||
process.env.NEXT_PUBLIC_ADSENSE_GOOGLE_SLOT_AUTO || '8807314373', // Google AdScene>广告>按单元广告>新建展示广告 (自动广告)
|
||||
|
||||
// 万维广告
|
||||
AD_WWADS_ID: process.env.NEXT_PUBLIC_WWAD_ID || null, // https://wwads.cn/ 创建您的万维广告单元ID
|
||||
@@ -413,13 +479,17 @@ const BLOG = {
|
||||
type: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE || 'type', // 文章类型,
|
||||
type_post: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_POST || 'Post', // 当type文章类型与此值相同时,为博文。
|
||||
type_page: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_PAGE || 'Page', // 当type文章类型与此值相同时,为单页。
|
||||
type_notice: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_NOTICE || 'Notice', // 当type文章类型与此值相同时,为公告。
|
||||
type_notice:
|
||||
process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_NOTICE || 'Notice', // 当type文章类型与此值相同时,为公告。
|
||||
type_menu: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_MENU || 'Menu', // 当type文章类型与此值相同时,为菜单。
|
||||
type_sub_menu: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_SUB_MENU || 'SubMenu', // 当type文章类型与此值相同时,为子菜单。
|
||||
type_sub_menu:
|
||||
process.env.NEXT_PUBLIC_NOTION_PROPERTY_TYPE_SUB_MENU || 'SubMenu', // 当type文章类型与此值相同时,为子菜单。
|
||||
title: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TITLE || 'title', // 文章标题
|
||||
status: process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS || 'status',
|
||||
status_publish: process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS_PUBLISH || 'Published', // 当status状态值与此相同时为发布,可以为中文
|
||||
status_invisible: process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS_INVISIBLE || 'Invisible', // 当status状态值与此相同时为隐藏发布,可以为中文 , 除此之外其他页面状态不会显示在博客上
|
||||
status_publish:
|
||||
process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS_PUBLISH || 'Published', // 当status状态值与此相同时为发布,可以为中文
|
||||
status_invisible:
|
||||
process.env.NEXT_PUBLIC_NOTION_PROPERTY_STATUS_INVISIBLE || 'Invisible', // 当status状态值与此相同时为隐藏发布,可以为中文 , 除此之外其他页面状态不会显示在博客上
|
||||
summary: process.env.NEXT_PUBLIC_NOTION_PROPERTY_SUMMARY || 'summary',
|
||||
slug: process.env.NEXT_PUBLIC_NOTION_PROPERTY_SLUG || 'slug',
|
||||
category: process.env.NEXT_PUBLIC_NOTION_PROPERTY_CATEGORY || 'category',
|
||||
@@ -450,8 +520,10 @@ const BLOG = {
|
||||
// 作废配置
|
||||
AVATAR: process.env.NEXT_PUBLIC_AVATAR || '/avatar.svg', // 作者头像,被notion中的ICON覆盖。若无ICON则取public目录下的avatar.png
|
||||
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 ,被notion中的页面标题覆盖;此处请勿留空白,否则服务器无法编译
|
||||
HOME_BANNER_IMAGE: process.env.NEXT_PUBLIC_HOME_BANNER_IMAGE || '/bg_image.jpg', // 首页背景大图, 会被notion中的封面图覆盖,若无封面图则会使用代码中的 /public/bg_image.jpg 文件
|
||||
DESCRIPTION: process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖
|
||||
HOME_BANNER_IMAGE:
|
||||
process.env.NEXT_PUBLIC_HOME_BANNER_IMAGE || '/bg_image.jpg', // 首页背景大图, 会被notion中的封面图覆盖,若无封面图则会使用代码中的 /public/bg_image.jpg 文件
|
||||
DESCRIPTION:
|
||||
process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖
|
||||
|
||||
// 开发相关
|
||||
NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
import AOS from 'aos'
|
||||
import { isBrowser } from 'react-notion-x'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
// import AOS from 'aos'
|
||||
|
||||
/**
|
||||
* 加载滚动动画
|
||||
* 改从外部CDN读取
|
||||
* https://michalsnik.github.io/aos/
|
||||
*/
|
||||
export default function AOSAnimation() {
|
||||
if (isBrowser) {
|
||||
AOS.init()
|
||||
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'
|
||||
)
|
||||
]).then(() => {
|
||||
if (window.AOS) {
|
||||
window.AOS.init()
|
||||
}
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
initAOS()
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* custom by hexo-theme-yun @YunYouJun
|
||||
*/
|
||||
import { useEffect } from 'react'
|
||||
import anime from 'animejs'
|
||||
// import anime from 'animejs'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* 鼠标点击烟花特效
|
||||
@@ -14,17 +15,37 @@ const Fireworks = () => {
|
||||
const fireworksColor = siteConfig('FIREWORKS_COLOR')
|
||||
|
||||
useEffect(() => {
|
||||
createFireworks({ colors: fireworksColor })
|
||||
// 异步加载
|
||||
async function loadFireworks() {
|
||||
loadExternalResource(
|
||||
'https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js',
|
||||
'js'
|
||||
).then(() => {
|
||||
if (window.anime) {
|
||||
createFireworks({
|
||||
config: { colors: fireworksColor },
|
||||
anime: window.anime
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadFireworks()
|
||||
|
||||
return () => {
|
||||
// 在组件卸载时清理资源(如果需要)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <canvas id='fireworks' className='fireworks'></canvas>
|
||||
}
|
||||
export default Fireworks
|
||||
|
||||
/**
|
||||
* 创建烟花
|
||||
* @param config
|
||||
*/
|
||||
function createFireworks(config) {
|
||||
* 创建烟花
|
||||
* @param config
|
||||
*/
|
||||
function createFireworks({ config, anime }) {
|
||||
const defaultConfig = {
|
||||
colors: config?.colors,
|
||||
numberOfParticules: 20,
|
||||
@@ -57,8 +78,8 @@ function createFireworks(config) {
|
||||
const ctx = canvasEl.getContext('2d')
|
||||
|
||||
/**
|
||||
* 设置画布尺寸
|
||||
*/
|
||||
* 设置画布尺寸
|
||||
*/
|
||||
function setCanvasSize(canvasEl) {
|
||||
canvasEl.width = window.innerWidth
|
||||
canvasEl.height = window.innerHeight
|
||||
@@ -67,16 +88,16 @@ function createFireworks(config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* update pointer
|
||||
* @param {TouchEvent} e
|
||||
*/
|
||||
* update pointer
|
||||
* @param {TouchEvent} e
|
||||
*/
|
||||
function updateCoords(e) {
|
||||
pointerX =
|
||||
e.clientX ||
|
||||
(e.touches[0] ? e.touches[0].clientX : e.changedTouches[0].clientX)
|
||||
e.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.clientY ||
|
||||
(e.touches[0] ? e.touches[0].clientY : e.changedTouches[0].clientY)
|
||||
}
|
||||
|
||||
function setParticuleDirection(p) {
|
||||
@@ -93,26 +114,25 @@ function createFireworks(config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定位置创建粒子
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns
|
||||
*/
|
||||
* 在指定位置创建粒子
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns
|
||||
*/
|
||||
function createParticule(x, y) {
|
||||
const p = {
|
||||
x,
|
||||
y,
|
||||
color: `rgba(${
|
||||
colors[anime.random(0, colors.length - 1)]
|
||||
},${
|
||||
anime.random(0.2, 0.8)
|
||||
})`,
|
||||
color: `rgba(${colors[anime.random(0, colors.length - 1)]},${anime.random(
|
||||
0.2,
|
||||
0.8
|
||||
)})`,
|
||||
radius: anime.random(config.circleRadius.min, config.circleRadius.max),
|
||||
endPos: null,
|
||||
draw() {}
|
||||
}
|
||||
p.endPos = setParticuleDirection(p)
|
||||
p.draw = function() {
|
||||
p.draw = function () {
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true)
|
||||
ctx.fillStyle = p.color
|
||||
@@ -131,7 +151,7 @@ function createFireworks(config) {
|
||||
lineWidth: 6,
|
||||
draw() {}
|
||||
}
|
||||
p.draw = function() {
|
||||
p.draw = function () {
|
||||
ctx.globalAlpha = p.alpha
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true)
|
||||
@@ -144,13 +164,17 @@ function createFireworks(config) {
|
||||
}
|
||||
|
||||
function renderParticule(anim) {
|
||||
for (let i = 0; i < anim.animatables.length; i++) { anim.animatables[i].target.draw() }
|
||||
for (let i = 0; i < anim.animatables.length; i++) {
|
||||
anim.animatables[i].target.draw()
|
||||
}
|
||||
}
|
||||
|
||||
function animateParticules(x, y) {
|
||||
const circle = createCircle(x, y)
|
||||
const particules = []
|
||||
for (let i = 0; i < config.numberOfParticules; i++) { particules.push(createParticule(x, y)) }
|
||||
for (let i = 0; i < config.numberOfParticules; i++) {
|
||||
particules.push(createParticule(x, y))
|
||||
}
|
||||
|
||||
anime
|
||||
.timeline()
|
||||
@@ -197,7 +221,7 @@ function createFireworks(config) {
|
||||
|
||||
document.addEventListener(
|
||||
'mousedown',
|
||||
(e) => {
|
||||
e => {
|
||||
render.play()
|
||||
updateCoords(e)
|
||||
animateParticules(pointerX, pointerY)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Giscus from '@giscus/react'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
// import Giscus from '@giscus/react'
|
||||
|
||||
/**
|
||||
* Giscus评论 @see https://giscus.app/zh-CN
|
||||
@@ -12,21 +14,34 @@ import Giscus from '@giscus/react'
|
||||
const GiscusComponent = () => {
|
||||
const { isDarkMode } = useGlobal()
|
||||
const theme = isDarkMode ? 'dark' : 'light'
|
||||
useEffect(() => {
|
||||
loadExternalResource('/js/giscus.js', 'js').then(() => {
|
||||
if (window.Giscus) {
|
||||
window.Giscus.init('#giscus')
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
window.Giscus.destroy()
|
||||
}
|
||||
}, [isDarkMode])
|
||||
|
||||
return (
|
||||
<Giscus
|
||||
repo={siteConfig('COMMENT_GISCUS_REPO')}
|
||||
repoId={siteConfig('COMMENT_GISCUS_REPO_ID')}
|
||||
categoryId={siteConfig('COMMENT_GISCUS_CATEGORY_ID')}
|
||||
mapping={siteConfig('COMMENT_GISCUS_MAPPING')}
|
||||
reactionsEnabled={siteConfig('COMMENT_GISCUS_REACTIONS_ENABLED')}
|
||||
emitMetadata={siteConfig('COMMENT_GISCUS_EMIT_METADATA')}
|
||||
theme={theme}
|
||||
inputPosition={siteConfig('COMMENT_GISCUS_INPUT_POSITION')}
|
||||
lang={siteConfig('COMMENT_GISCUS_LANG')}
|
||||
loading={siteConfig('COMMENT_GISCUS_LOADING')}
|
||||
crossorigin={siteConfig('COMMENT_GISCUS_CROSSORIGIN')}
|
||||
/>
|
||||
<div
|
||||
id='giscus'
|
||||
data-repo={siteConfig('COMMENT_GISCUS_REPO')}
|
||||
data-repo-id={siteConfig('COMMENT_GISCUS_REPO_ID')}
|
||||
// data-category='{{ $.Site.Params.giscus.dataCategory }}'
|
||||
data-category-id={siteConfig('COMMENT_GISCUS_CATEGORY_ID')}
|
||||
data-mapping={siteConfig('COMMENT_GISCUS_MAPPING')}
|
||||
// data-strict='0'
|
||||
data-reactions-enabled={siteConfig('COMMENT_GISCUS_REACTIONS_ENABLED')}
|
||||
data-emit-metadata={siteConfig('COMMENT_GISCUS_EMIT_METADATA')}
|
||||
data-input-position={siteConfig('COMMENT_GISCUS_INPUT_POSITION')}
|
||||
data-theme={theme}
|
||||
data-lang={siteConfig('COMMENT_GISCUS_LANG')}
|
||||
data-loading={siteConfig('COMMENT_GISCUS_LOADING')}
|
||||
// crossorigin={siteConfig('COMMENT_GISCUS_CROSSORIGIN')}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import NProgress from 'nprogress'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* 出现页面加载进度条
|
||||
*/
|
||||
export default function LoadingProgress() {
|
||||
const router = useRouter()
|
||||
const [NProgress, setNProgress] = useState(null)
|
||||
// 加载进度条
|
||||
useEffect(() => {
|
||||
const handleStart = (url) => {
|
||||
NProgress.start()
|
||||
loadExternalResource(
|
||||
'https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.js',
|
||||
'js'
|
||||
).then(() => {
|
||||
if (window.NProgress) {
|
||||
setNProgress(window.NProgress)
|
||||
// 调速
|
||||
window.NProgress.settings.minimun = 0.1
|
||||
loadExternalResource(
|
||||
'https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.css',
|
||||
'css'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const handleStart = url => {
|
||||
NProgress?.start()
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
NProgress.done()
|
||||
NProgress?.done()
|
||||
}
|
||||
|
||||
router.events.on('routeChangeStart', handleStart)
|
||||
|
||||
@@ -1,53 +1,52 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import {
|
||||
FacebookShareButton,
|
||||
FacebookIcon,
|
||||
FacebookMessengerShareButton,
|
||||
FacebookMessengerIcon,
|
||||
RedditShareButton,
|
||||
RedditIcon,
|
||||
LineShareButton,
|
||||
LineIcon,
|
||||
EmailShareButton,
|
||||
EmailIcon,
|
||||
TwitterShareButton,
|
||||
TwitterIcon,
|
||||
TelegramShareButton,
|
||||
TelegramIcon,
|
||||
WhatsappShareButton,
|
||||
WhatsappIcon,
|
||||
LinkedinShareButton,
|
||||
EmailShareButton,
|
||||
FacebookIcon,
|
||||
FacebookMessengerIcon,
|
||||
FacebookMessengerShareButton,
|
||||
FacebookShareButton,
|
||||
HatenaIcon,
|
||||
HatenaShareButton,
|
||||
InstapaperIcon,
|
||||
InstapaperShareButton,
|
||||
LineIcon,
|
||||
LineShareButton,
|
||||
LinkedinIcon,
|
||||
PinterestShareButton,
|
||||
PinterestIcon,
|
||||
VKIcon,
|
||||
VKShareButton,
|
||||
OKShareButton,
|
||||
OKIcon,
|
||||
TumblrShareButton,
|
||||
TumblrIcon,
|
||||
LinkedinShareButton,
|
||||
LivejournalIcon,
|
||||
LivejournalShareButton,
|
||||
MailruShareButton,
|
||||
MailruIcon,
|
||||
MailruShareButton,
|
||||
OKIcon,
|
||||
OKShareButton,
|
||||
PinterestIcon,
|
||||
PinterestShareButton,
|
||||
PocketIcon,
|
||||
PocketShareButton,
|
||||
RedditIcon,
|
||||
RedditShareButton,
|
||||
TelegramIcon,
|
||||
TelegramShareButton,
|
||||
TumblrIcon,
|
||||
TumblrShareButton,
|
||||
TwitterIcon,
|
||||
TwitterShareButton,
|
||||
VKIcon,
|
||||
VKShareButton,
|
||||
ViberIcon,
|
||||
ViberShareButton,
|
||||
WorkplaceShareButton,
|
||||
WorkplaceIcon,
|
||||
WeiboShareButton,
|
||||
WeiboIcon,
|
||||
PocketShareButton,
|
||||
PocketIcon,
|
||||
InstapaperShareButton,
|
||||
InstapaperIcon,
|
||||
HatenaShareButton,
|
||||
HatenaIcon
|
||||
WeiboShareButton,
|
||||
WhatsappIcon,
|
||||
WhatsappShareButton,
|
||||
WorkplaceIcon,
|
||||
WorkplaceShareButton
|
||||
} from 'react-share'
|
||||
|
||||
const QrCode = dynamic(() => import('@/components/QrCode'), { ssr: false })
|
||||
@@ -59,10 +58,11 @@ const QrCode = dynamic(() => import('@/components/QrCode'), { ssr: false })
|
||||
*/
|
||||
const ShareButtons = ({ post }) => {
|
||||
const router = useRouter()
|
||||
const shareUrl = siteConfig('LINK') + router.asPath
|
||||
const [shareUrl, setShareUrl] = useState(siteConfig('LINK') + router.asPath)
|
||||
const title = post.title || siteConfig('TITLE')
|
||||
const image = post.pageCover
|
||||
const body = post?.title + ' | ' + title + ' ' + shareUrl + ' ' + post?.summary
|
||||
const body =
|
||||
post?.title + ' | ' + title + ' ' + shareUrl + ' ' + post?.summary
|
||||
|
||||
const services = siteConfig('POSTS_SHARE_SERVICES').split(',')
|
||||
const titleWithSiteInfo = title + ' | ' + siteConfig('TITLE')
|
||||
@@ -70,8 +70,8 @@ const ShareButtons = ({ post }) => {
|
||||
const [qrCodeShow, setQrCodeShow] = useState(false)
|
||||
|
||||
const copyUrl = () => {
|
||||
copy(shareUrl)
|
||||
alert(locale.COMMON.URL_COPIED)
|
||||
navigator?.clipboard?.writeText(shareUrl)
|
||||
alert(locale.COMMON.URL_COPIED + ' \n' + shareUrl)
|
||||
}
|
||||
|
||||
const openPopover = () => {
|
||||
@@ -81,300 +81,308 @@ const ShareButtons = ({ post }) => {
|
||||
setQrCodeShow(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setShareUrl(window.location.href)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{services.map(singleService => {
|
||||
if (singleService === 'facebook') {
|
||||
return (
|
||||
<FacebookShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<FacebookIcon size={32} round />
|
||||
</FacebookShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'messenger') {
|
||||
return (
|
||||
<FacebookMessengerShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
appId={siteConfig('FACEBOOK_APP_ID')}
|
||||
className="mx-1"
|
||||
>
|
||||
<FacebookMessengerIcon size={32} round />
|
||||
</FacebookMessengerShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'line') {
|
||||
return (
|
||||
<LineShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<LineIcon size={32} round />
|
||||
</LineShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'reddit') {
|
||||
return (
|
||||
<RedditShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
windowWidth={660}
|
||||
windowHeight={460}
|
||||
className="mx-1"
|
||||
>
|
||||
<RedditIcon size={32} round />
|
||||
</RedditShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'email') {
|
||||
return (
|
||||
<EmailShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
subject={titleWithSiteInfo}
|
||||
body={body}
|
||||
className="mx-1"
|
||||
>
|
||||
<EmailIcon size={32} round />
|
||||
</EmailShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'twitter') {
|
||||
return (
|
||||
<TwitterShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<TwitterIcon size={32} round />
|
||||
</TwitterShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'telegram') {
|
||||
return (
|
||||
<TelegramShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<TelegramIcon size={32} round />
|
||||
</TelegramShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'whatsapp') {
|
||||
return (
|
||||
<WhatsappShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
separator=":: "
|
||||
className="mx-1"
|
||||
>
|
||||
<WhatsappIcon size={32} round />
|
||||
</WhatsappShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'linkedin') {
|
||||
return (
|
||||
<LinkedinShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<LinkedinIcon size={32} round />
|
||||
</LinkedinShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'pinterest') {
|
||||
return (
|
||||
<PinterestShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
media={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<PinterestIcon size={32} round />
|
||||
</PinterestShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'vkshare') {
|
||||
return (
|
||||
<VKShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
image={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<VKIcon size={32} round />
|
||||
</VKShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'okshare') {
|
||||
return (
|
||||
<OKShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
image={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<OKIcon size={32} round />
|
||||
</OKShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'tumblr') {
|
||||
return (
|
||||
<TumblrShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<TumblrIcon size={32} round />
|
||||
</TumblrShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'livejournal') {
|
||||
return (
|
||||
<LivejournalShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
description={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<LivejournalIcon size={32} round />
|
||||
</LivejournalShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'mailru') {
|
||||
return (
|
||||
<MailruShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<MailruIcon size={32} round />
|
||||
</MailruShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'viber') {
|
||||
return (
|
||||
<ViberShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<ViberIcon size={32} round />
|
||||
</ViberShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'workplace') {
|
||||
return (
|
||||
<WorkplaceShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
quote={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<WorkplaceIcon size={32} round />
|
||||
</WorkplaceShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'weibo') {
|
||||
return (
|
||||
<WeiboShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
image={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<WeiboIcon size={32} round />
|
||||
</WeiboShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'pocket') {
|
||||
return (
|
||||
<PocketShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<PocketIcon size={32} round />
|
||||
</PocketShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'instapaper') {
|
||||
return (
|
||||
<InstapaperShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<InstapaperIcon size={32} round />
|
||||
</InstapaperShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'hatena') {
|
||||
return (
|
||||
<HatenaShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
windowWidth={660}
|
||||
windowHeight={460}
|
||||
className="mx-1"
|
||||
>
|
||||
<HatenaIcon size={32} round />
|
||||
</HatenaShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'qq') {
|
||||
return <button key={singleService} className='cursor-pointer bg-blue-600 text-white rounded-full mx-1'>
|
||||
<a target='_blank' rel='noreferrer' aria-label="Share by QQ" href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${title}&desc=${body}`} >
|
||||
<i className='fab fa-qq w-8' />
|
||||
</a>
|
||||
</button>
|
||||
}
|
||||
if (singleService === 'wechat') {
|
||||
return <button onMouseEnter={openPopover} onMouseLeave={closePopover} aria-label={singleService} key={singleService} className='cursor-pointer bg-green-600 text-white rounded-full mx-1'>
|
||||
<div id='wechat-button'>
|
||||
<i className='fab fa-weixin w-8' />
|
||||
</div>
|
||||
<div className='absolute'>
|
||||
<div id='pop' className={(qrCodeShow ? 'opacity-100 ' : ' invisible opacity-0') + ' z-40 absolute bottom-10 -left-10 bg-white shadow-xl transition-all duration-200 text-center'}>
|
||||
<div className='p-2 mt-1 w-28 h-28'>
|
||||
{ qrCodeShow && <QrCode value={shareUrl}/> }
|
||||
</div>
|
||||
<span className='text-black font-semibold p-1 rounded-t-lg text-sm mx-auto mb-1'>
|
||||
{locale.COMMON.SCAN_QR_CODE}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
if (singleService === 'link') {
|
||||
return <button aria-label={singleService} key={singleService} className='cursor-pointer bg-yellow-500 text-white rounded-full mx-1'>
|
||||
<div alt={locale.COMMON.URL_COPIED} onClick={copyUrl} >
|
||||
<i className='fas fa-link w-8' />
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
return <></>
|
||||
})}
|
||||
</>
|
||||
<>
|
||||
{services.map(singleService => {
|
||||
if (singleService === 'facebook') {
|
||||
return (
|
||||
<FacebookShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className='mx-1'>
|
||||
<FacebookIcon size={32} round />
|
||||
</FacebookShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'messenger') {
|
||||
return (
|
||||
<FacebookMessengerShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
appId={siteConfig('FACEBOOK_APP_ID')}
|
||||
className='mx-1'>
|
||||
<FacebookMessengerIcon size={32} round />
|
||||
</FacebookMessengerShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'line') {
|
||||
return (
|
||||
<LineShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className='mx-1'>
|
||||
<LineIcon size={32} round />
|
||||
</LineShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'reddit') {
|
||||
return (
|
||||
<RedditShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
windowWidth={660}
|
||||
windowHeight={460}
|
||||
className='mx-1'>
|
||||
<RedditIcon size={32} round />
|
||||
</RedditShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'email') {
|
||||
return (
|
||||
<EmailShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
subject={titleWithSiteInfo}
|
||||
body={body}
|
||||
className='mx-1'>
|
||||
<EmailIcon size={32} round />
|
||||
</EmailShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'twitter') {
|
||||
return (
|
||||
<TwitterShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<TwitterIcon size={32} round />
|
||||
</TwitterShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'telegram') {
|
||||
return (
|
||||
<TelegramShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<TelegramIcon size={32} round />
|
||||
</TelegramShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'whatsapp') {
|
||||
return (
|
||||
<WhatsappShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
separator=':: '
|
||||
className='mx-1'>
|
||||
<WhatsappIcon size={32} round />
|
||||
</WhatsappShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'linkedin') {
|
||||
return (
|
||||
<LinkedinShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className='mx-1'>
|
||||
<LinkedinIcon size={32} round />
|
||||
</LinkedinShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'pinterest') {
|
||||
return (
|
||||
<PinterestShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
media={image}
|
||||
className='mx-1'>
|
||||
<PinterestIcon size={32} round />
|
||||
</PinterestShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'vkshare') {
|
||||
return (
|
||||
<VKShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
image={image}
|
||||
className='mx-1'>
|
||||
<VKIcon size={32} round />
|
||||
</VKShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'okshare') {
|
||||
return (
|
||||
<OKShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
image={image}
|
||||
className='mx-1'>
|
||||
<OKIcon size={32} round />
|
||||
</OKShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'tumblr') {
|
||||
return (
|
||||
<TumblrShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<TumblrIcon size={32} round />
|
||||
</TumblrShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'livejournal') {
|
||||
return (
|
||||
<LivejournalShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
description={shareUrl}
|
||||
className='mx-1'>
|
||||
<LivejournalIcon size={32} round />
|
||||
</LivejournalShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'mailru') {
|
||||
return (
|
||||
<MailruShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<MailruIcon size={32} round />
|
||||
</MailruShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'viber') {
|
||||
return (
|
||||
<ViberShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<ViberIcon size={32} round />
|
||||
</ViberShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'workplace') {
|
||||
return (
|
||||
<WorkplaceShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
quote={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<WorkplaceIcon size={32} round />
|
||||
</WorkplaceShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'weibo') {
|
||||
return (
|
||||
<WeiboShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
image={image}
|
||||
className='mx-1'>
|
||||
<WeiboIcon size={32} round />
|
||||
</WeiboShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'pocket') {
|
||||
return (
|
||||
<PocketShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<PocketIcon size={32} round />
|
||||
</PocketShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'instapaper') {
|
||||
return (
|
||||
<InstapaperShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className='mx-1'>
|
||||
<InstapaperIcon size={32} round />
|
||||
</InstapaperShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'hatena') {
|
||||
return (
|
||||
<HatenaShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
windowWidth={660}
|
||||
windowHeight={460}
|
||||
className='mx-1'>
|
||||
<HatenaIcon size={32} round />
|
||||
</HatenaShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'qq') {
|
||||
return (
|
||||
<button
|
||||
key={singleService}
|
||||
className='cursor-pointer bg-blue-600 text-white rounded-full mx-1'>
|
||||
<a
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
aria-label='Share by QQ'
|
||||
href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${title}&desc=${body}`}>
|
||||
<i className='fab fa-qq w-8' />
|
||||
</a>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
if (singleService === 'wechat') {
|
||||
return (
|
||||
<button
|
||||
onMouseEnter={openPopover}
|
||||
onMouseLeave={closePopover}
|
||||
aria-label={singleService}
|
||||
key={singleService}
|
||||
className='cursor-pointer bg-green-600 text-white rounded-full mx-1'>
|
||||
<div id='wechat-button'>
|
||||
<i className='fab fa-weixin w-8' />
|
||||
</div>
|
||||
<div className='absolute'>
|
||||
<div
|
||||
id='pop'
|
||||
className={
|
||||
(qrCodeShow ? 'opacity-100 ' : ' invisible opacity-0') +
|
||||
' z-40 absolute bottom-10 -left-10 bg-white shadow-xl transition-all duration-200 text-center'
|
||||
}>
|
||||
<div className='p-2 mt-1 w-28 h-28'>
|
||||
{qrCodeShow && <QrCode value={shareUrl} />}
|
||||
</div>
|
||||
<span className='text-black font-semibold p-1 rounded-t-lg text-sm mx-auto mb-1'>
|
||||
{locale.COMMON.SCAN_QR_CODE}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
if (singleService === 'link') {
|
||||
return (
|
||||
<button
|
||||
aria-label={singleService}
|
||||
key={singleService}
|
||||
className='cursor-pointer bg-yellow-500 text-white rounded-full mx-1'>
|
||||
<div alt={locale.COMMON.URL_COPIED} onClick={copyUrl}>
|
||||
<i className='fas fa-link w-8' />
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
return <></>
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
9
lib/cache/cache_manager.js
vendored
9
lib/cache/cache_manager.js
vendored
@@ -1,7 +1,6 @@
|
||||
import MemoryCache from './memory_cache'
|
||||
import FileCache from './local_file_cache'
|
||||
import MongoCache from './mongo_db_cache'
|
||||
import BLOG from '@/blog.config'
|
||||
import FileCache from './local_file_cache'
|
||||
import MemoryCache from './memory_cache'
|
||||
|
||||
/**
|
||||
* 为减少频繁接口请求,notion数据将被缓存
|
||||
@@ -39,9 +38,7 @@ export async function delCacheData(key) {
|
||||
* @returns
|
||||
*/
|
||||
function getApi() {
|
||||
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
|
||||
return MongoCache
|
||||
} else if (process.env.ENABLE_FILE_CACHE) {
|
||||
if (process.env.ENABLE_FILE_CACHE) {
|
||||
return FileCache
|
||||
} else {
|
||||
return MemoryCache
|
||||
|
||||
49
lib/cache/mongo_db_cache.js
vendored
49
lib/cache/mongo_db_cache.js
vendored
@@ -1,49 +0,0 @@
|
||||
const MongoClient = require('mongodb').MongoClient
|
||||
|
||||
const DB_URL = process.env.MONGO_DB_URL // e.g. mongodb+srv://mongo_user:[password]@xxx.mongodb.net//?retryWrites=true&w=majority
|
||||
const DB_NAME = process.env.MONGO_DB_NAME // e.g. tangly1024
|
||||
const DB_COLLECTION = 'posts'
|
||||
|
||||
export async function getCache (key) {
|
||||
const client = await MongoClient.connect(DB_URL).catch(err => { console.error(err) })
|
||||
const dbo = client.db(DB_NAME)
|
||||
const query = { block_id: key }
|
||||
const res = await dbo.collection('posts').findOne(query).catch(err => { console.error(err) })
|
||||
await client.close()
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 并发请求写文件异常; Vercel生产环境不支持写文件。
|
||||
* @param key
|
||||
* @param data
|
||||
* @returns {Promise<null>}
|
||||
*/
|
||||
export async function setCache (key, data) {
|
||||
const client = await MongoClient.connect(DB_URL).catch(err => { console.error(err) })
|
||||
const dbo = client.db(DB_NAME)
|
||||
data.block_id = key
|
||||
const query = { block_id: key }
|
||||
const jsonObj = JSON.parse(JSON.stringify(data))
|
||||
|
||||
const updRes = await dbo.collection(DB_COLLECTION).updateOne(query, { $set: jsonObj }).catch(err => { console.error(err) })
|
||||
console.log('更新结果', key, updRes)
|
||||
if (updRes.matchedCount === 0) {
|
||||
const insertRes = await dbo.collection(DB_COLLECTION).insertOne(jsonObj).catch(err => { console.error(err) })
|
||||
console.log('插入结果', key, insertRes)
|
||||
}
|
||||
await client.close()
|
||||
return data
|
||||
}
|
||||
|
||||
export async function delCache (key, data) {
|
||||
const client = await MongoClient.connect(DB_URL).catch(err => { console.error(err) })
|
||||
const dbo = client.db(DB_NAME)
|
||||
const query = { block_id: key }
|
||||
const res = await dbo.collection('posts').deleteOne(query).catch(err => { console.error(err) })
|
||||
console.log('删除结果', key, res)
|
||||
await client.close()
|
||||
return null
|
||||
}
|
||||
|
||||
export default { getCache, setCache, delCache }
|
||||
@@ -52,7 +52,7 @@ export const siteConfig = (key, defaultVal = null, extendConfig = null) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 其次 有传入的配置参考,则尝试读取
|
||||
// 其次 有传入的extendConfig,则尝试读取
|
||||
if (!val && extendConfig) {
|
||||
val = extendConfig[key]
|
||||
}
|
||||
@@ -64,24 +64,37 @@ export const siteConfig = (key, defaultVal = null, extendConfig = null) => {
|
||||
|
||||
if (!val) {
|
||||
return defaultVal
|
||||
} else {
|
||||
if (typeof val === 'string') {
|
||||
if (val === 'true' || val === 'false') {
|
||||
return JSON.parse(val)
|
||||
}
|
||||
if (/^\d+$/.test(val)) {
|
||||
// 如果是数字,使用parseFloat或者parseInt将字符串转换为数字
|
||||
return parseInt(val)
|
||||
}
|
||||
return val
|
||||
} else {
|
||||
try {
|
||||
return JSON.parse(val)
|
||||
} catch (error) {
|
||||
// 如果值是一个字符串但不是有效的 JSON 格式,直接返回字符串
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 从Notion_CONFIG读取的配置通常都是字符串,适当转义
|
||||
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 格式,直接返回字符串
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getPostBlocks, getSingleBlock } from '@/lib/notion/getPostBlocks'
|
||||
import { compressImage, mapImgUrl } from '@/lib/notion/mapImage'
|
||||
import { deepClone } from '@/lib/utils'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import { extractLangId, extractLangPrefix } from '../utils/pageId'
|
||||
|
||||
export { getAllTags } from '../notion/getAllTags'
|
||||
export { getPost } from '../notion/getNotionPost'
|
||||
@@ -18,18 +19,62 @@ export { getPostBlocks } from '../notion/getPostBlocks'
|
||||
* 获取博客数据; 基于Notion实现
|
||||
* @param {*} pageId
|
||||
* @param {*} from
|
||||
* @param latestPostCount 截取最新文章数量
|
||||
* @param categoryCount
|
||||
* @param tagsCount 截取标签数量
|
||||
* @param pageType 过滤的文章类型,数组格式 ['Page','Post']
|
||||
* @param {*} locale 语言 zh|en|jp 等等
|
||||
* @returns
|
||||
*
|
||||
*/
|
||||
export async function getGlobalData({ pageId = BLOG.NOTION_PAGE_ID, from }) {
|
||||
// 从notion获取
|
||||
const data = await getNotionPageData({ pageId, from })
|
||||
export async function getGlobalData({
|
||||
pageId = BLOG.NOTION_PAGE_ID,
|
||||
from,
|
||||
locale
|
||||
}) {
|
||||
// 获取站点数据 , 如果pageId有逗号隔开则分次取数据
|
||||
const siteIds = pageId?.split(',') || []
|
||||
let data = EmptyData(pageId)
|
||||
|
||||
try {
|
||||
for (let index = 0; index < siteIds.length; index++) {
|
||||
const siteId = siteIds[index]
|
||||
const id = extractLangId(siteId)
|
||||
const prefix = extractLangPrefix(siteId)
|
||||
// 第一个id站点默认语言
|
||||
if (index === 0 || locale === prefix) {
|
||||
data = await getNotionPageData({
|
||||
pageId: id,
|
||||
from
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('异常', error)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定notion的collection数据
|
||||
* @param pageId
|
||||
* @param from 请求来源
|
||||
* @returns {Promise<JSX.Element|*|*[]>}
|
||||
*/
|
||||
export async function getNotionPageData({ pageId, from }) {
|
||||
// 尝试从缓存获取
|
||||
const cacheKey = 'page_block_' + pageId
|
||||
let data = await getDataFromCache(cacheKey)
|
||||
if (data && data.pageIds?.length > 0) {
|
||||
// console.log('[API<<--缓存]', `from:${from}`, `root-page-id:${pageId}`)
|
||||
// return data
|
||||
} else {
|
||||
// 从接口读取
|
||||
data = await getDataBaseInfoByNotionAPI({ pageId, from })
|
||||
// 存入缓存
|
||||
if (data) {
|
||||
await setDataToCache(cacheKey, data)
|
||||
}
|
||||
}
|
||||
|
||||
// 返回给前端的数据做处理
|
||||
const db = deepClone(data)
|
||||
// 减少返回给前端的数据,减少流量损耗
|
||||
delete db.block
|
||||
delete db.schema
|
||||
delete db.rawMetadata
|
||||
@@ -47,10 +92,12 @@ export async function getGlobalData({ pageId = BLOG.NOTION_PAGE_ID, from }) {
|
||||
if (db?.post) {
|
||||
db.post = cleanBlock(db?.post)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理block数据
|
||||
*/
|
||||
function cleanBlock(post) {
|
||||
const pageBlock = post?.blockMap?.block
|
||||
for (const i in pageBlock) {
|
||||
@@ -98,28 +145,6 @@ function getLatestPosts({ allPages, from, latestPostCount }) {
|
||||
return latestPosts.slice(0, latestPostCount)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定notion的collection数据
|
||||
* @param pageId
|
||||
* @param from 请求来源
|
||||
* @returns {Promise<JSX.Element|*|*[]>}
|
||||
*/
|
||||
export async function getNotionPageData({ pageId, from }) {
|
||||
// 尝试从缓存获取
|
||||
const cacheKey = 'page_block_' + pageId
|
||||
const data = await getDataFromCache(cacheKey)
|
||||
if (data && data.pageIds?.length > 0) {
|
||||
// console.log('[API<<--缓存]', `from:${from}`, `root-page-id:${pageId}`)
|
||||
return data
|
||||
}
|
||||
const db = await getDataBaseInfoByNotionAPI({ pageId, from })
|
||||
// 存入缓存
|
||||
if (db) {
|
||||
await setDataToCache(cacheKey, db)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户自定义单页菜单
|
||||
* 旧版本,不读取Menu菜单,而是读取type=Page生成菜单
|
||||
@@ -167,7 +192,7 @@ function getCustomMenu({ collectionData }) {
|
||||
if (menuPages && menuPages.length > 0) {
|
||||
menuPages.forEach(e => {
|
||||
e.show = true
|
||||
if (e?.slug?.indexOf('http') === 0) {
|
||||
if (e?.slug?.indexOf('http') === 0 && e?.slug?.indexOf(BLOG.LINK) < 0) {
|
||||
e.target = '_blank'
|
||||
e.to = e.slug
|
||||
} else {
|
||||
@@ -225,13 +250,24 @@ function getCategoryOptions(schema) {
|
||||
* @param from
|
||||
* @returns {Promise<{title,description,pageCover,icon}>}
|
||||
*/
|
||||
function getSiteInfo({ collection, block, NOTION_CONFIG }) {
|
||||
function getSiteInfo({ collection, block, NOTION_CONFIG, pageId }) {
|
||||
if (!collection || !block || NOTION_CONFIG || pageId) {
|
||||
return {
|
||||
title: BLOG.TITLE,
|
||||
description: BLOG.DESCRIPTION,
|
||||
pageCover: BLOG.HOME_BANNER_IMAGE,
|
||||
icon: BLOG.AVATAR,
|
||||
link: BLOG.LINK
|
||||
}
|
||||
}
|
||||
|
||||
const title = collection?.name?.[0][0] || BLOG.TITLE
|
||||
const description = collection?.description
|
||||
? Object.assign(collection).description[0][0]
|
||||
: BLOG.DESCRIPTION
|
||||
|
||||
const pageCover = collection?.cover
|
||||
? mapImgUrl(collection?.cover, block[idToUuid(BLOG.NOTION_PAGE_ID)]?.value)
|
||||
? mapImgUrl(collection?.cover, block[pageId]?.value)
|
||||
: BLOG.HOME_BANNER_IMAGE
|
||||
// 用户头像压缩一下
|
||||
let icon = compressImage(
|
||||
@@ -308,6 +344,7 @@ const EmptyData = pageId => {
|
||||
status: 'Published',
|
||||
type: 'Post',
|
||||
slug: '13a171332816461db29d50e9f575b00d',
|
||||
pageCoverThumbnail: BLOG.HOME_BANNER_IMAGE,
|
||||
date: {
|
||||
start_date: '2023-04-24',
|
||||
lastEditedDay: '2023-04-24',
|
||||
@@ -426,7 +463,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
|
||||
// 站点配置优先读取配置表格,否则读取blog.config.js 文件
|
||||
const NOTION_CONFIG = (await getConfigMapFromConfigPage(collectionData)) || {}
|
||||
|
||||
const siteInfo = getSiteInfo({ collection, block })
|
||||
const siteInfo = getSiteInfo({ collection, block, pageId })
|
||||
|
||||
// 查找所有的Post和Page
|
||||
const allPages = collectionData.filter(post => {
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { generateLocaleDict, initLocale, saveLangToLocalStorage } from './lang'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
THEMES,
|
||||
initDarkMode,
|
||||
saveDarkModeToLocalStorage
|
||||
} from '@/themes/theme'
|
||||
import { APPEARANCE, LANG, NOTION_PAGE_ID, THEME } from 'blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import { THEMES, initDarkMode, saveDarkModeToLocalStorage } from '@/themes/theme'
|
||||
import { APPEARANCE, LANG, THEME } from 'blog.config'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
generateLocaleDict,
|
||||
initLocale,
|
||||
redirectUserLang,
|
||||
saveLangToLocalStorage
|
||||
} from './lang'
|
||||
const GlobalContext = createContext()
|
||||
|
||||
/**
|
||||
@@ -12,9 +21,18 @@ const GlobalContext = createContext()
|
||||
* @constructor
|
||||
*/
|
||||
export function GlobalContextProvider(props) {
|
||||
const { post, children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props
|
||||
const {
|
||||
post,
|
||||
children,
|
||||
siteInfo,
|
||||
categoryOptions,
|
||||
tagOptions,
|
||||
NOTION_CONFIG
|
||||
} = props
|
||||
const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || LANG) // 默认语言
|
||||
const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || LANG)) // 默认语言
|
||||
const [locale, updateLocale] = useState(
|
||||
generateLocaleDict(NOTION_CONFIG?.LANG || LANG)
|
||||
) // 默认语言
|
||||
const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || THEME) // 默认博客主题
|
||||
const defaultDarkMode = NOTION_CONFIG?.APPEARANCE || APPEARANCE
|
||||
const [isDarkMode, updateDarkMode] = useState(defaultDarkMode === 'dark') // 默认深色模式
|
||||
@@ -48,6 +66,7 @@ export function GlobalContextProvider(props) {
|
||||
|
||||
/**
|
||||
* 更新语言
|
||||
* 这里是代码级别的多语言,整个站点和文章内容的多语言不在此处理
|
||||
*/
|
||||
function changeLang(lang) {
|
||||
if (lang) {
|
||||
@@ -60,6 +79,7 @@ export function GlobalContextProvider(props) {
|
||||
useEffect(() => {
|
||||
initDarkMode(updateDarkMode, defaultDarkMode)
|
||||
initLocale(lang, locale, updateLang, updateLocale)
|
||||
redirectUserLang(NOTION_PAGE_ID)
|
||||
}, [])
|
||||
|
||||
// 加载进度条
|
||||
|
||||
60
lib/lang.js
60
lib/lang.js
@@ -1,11 +1,12 @@
|
||||
import zhCN from './lang/zh-CN'
|
||||
import { getQueryVariable, isBrowser, mergeDeep } from '@/lib/utils'
|
||||
import enUS from './lang/en-US'
|
||||
import frFR from './lang/fr-FR'
|
||||
import jaJP from './lang/ja-JP'
|
||||
import trTR from './lang/tr-TR'
|
||||
import zhCN from './lang/zh-CN'
|
||||
import zhHK from './lang/zh-HK'
|
||||
import zhTW from './lang/zh-TW'
|
||||
import frFR from './lang/fr-FR'
|
||||
import trTR from './lang/tr-TR'
|
||||
import jaJP from './lang/ja-JP'
|
||||
import { getQueryVariable, isBrowser, mergeDeep } from '@/lib/utils'
|
||||
import { extractLangPrefix } from './utils/pageId'
|
||||
|
||||
/**
|
||||
* 在这里配置所有支持的语言
|
||||
@@ -43,7 +44,9 @@ export function generateLocaleDict(langString) {
|
||||
|
||||
// 然后尝试匹配只有语言匹配的情况
|
||||
if (!userLocale) {
|
||||
const languageOnlyLocales = supportedLocales.filter(locale => locale.startsWith(language))
|
||||
const languageOnlyLocales = supportedLocales.filter(locale =>
|
||||
locale.startsWith(language)
|
||||
)
|
||||
if (languageOnlyLocales.length > 0) {
|
||||
userLocale = LANGS[languageOnlyLocales[0]]
|
||||
}
|
||||
@@ -51,7 +54,9 @@ export function generateLocaleDict(langString) {
|
||||
|
||||
// 如果还没匹配到,则返回最接近的语言包
|
||||
if (!userLocale) {
|
||||
const fallbackLocale = supportedLocales.find(locale => locale.startsWith('en'))
|
||||
const fallbackLocale = supportedLocales.find(locale =>
|
||||
locale.startsWith('en')
|
||||
)
|
||||
userLocale = LANGS[fallbackLocale]
|
||||
}
|
||||
|
||||
@@ -64,7 +69,11 @@ export function generateLocaleDict(langString) {
|
||||
*/
|
||||
export function initLocale(lang, locale, changeLang, changeLocale) {
|
||||
if (isBrowser) {
|
||||
const queryLang = getQueryVariable('lang') || loadLangFromLocalStorage()
|
||||
// 用户请求的预研
|
||||
const queryLang =
|
||||
getQueryVariable('locale') ||
|
||||
getQueryVariable('lang') ||
|
||||
loadLangFromLocalStorage()
|
||||
let currentLang = lang
|
||||
if (queryLang && queryLang !== 'undefined' && queryLang !== lang) {
|
||||
currentLang = queryLang
|
||||
@@ -91,6 +100,39 @@ export const loadLangFromLocalStorage = () => {
|
||||
* 保存语言
|
||||
* @param newTheme
|
||||
*/
|
||||
export const saveLangToLocalStorage = (lang) => {
|
||||
export const saveLangToLocalStorage = lang => {
|
||||
localStorage.setItem('lang', lang)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测用户的预研偏好,跳转至对应的多语言网站
|
||||
* @param {*} lang
|
||||
* @param {*} pageId
|
||||
*
|
||||
*/
|
||||
export const redirectUserLang = (lang, pageId) => {
|
||||
if (!isBrowser) {
|
||||
return
|
||||
}
|
||||
// 只在首页处理跳转
|
||||
if (!window.location.pathname === '/') {
|
||||
return
|
||||
}
|
||||
|
||||
const userLang =
|
||||
getQueryVariable('locale') ||
|
||||
getQueryVariable('lang') ||
|
||||
window?.navigator?.language
|
||||
const siteIds = pageId?.split(',') || []
|
||||
|
||||
// 默认是进首页; 如果检测到有一个多语言匹配了用户浏览器,则自动跳转过去
|
||||
for (let index = 0; index < siteIds.length; index++) {
|
||||
const siteId = siteIds[index]
|
||||
const prefix = extractLangPrefix(siteId)
|
||||
if (prefix === userLang) {
|
||||
if (window.location.pathname.indexOf(prefix) < 0) {
|
||||
window.location.href = '/' + prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
*
|
||||
*/
|
||||
import { getDateValue, getTextContent } from 'notion-utils'
|
||||
import { getPostBlocks } from './getPostBlocks'
|
||||
import { deepClone } from '../utils'
|
||||
import getAllPageIds from './getAllPageIds'
|
||||
import { getPostBlocks } from './getPostBlocks'
|
||||
|
||||
/**
|
||||
* 从Notion中读取Config配置表
|
||||
@@ -23,8 +24,15 @@ export async function getConfigMapFromConfigPage(allPages) {
|
||||
console.warn('[Notion配置] 忽略的配置')
|
||||
return null
|
||||
}
|
||||
// 找到Config类
|
||||
const configPage = allPages?.find(post => {
|
||||
return post && post?.type && (post?.type === 'CONFIG' || post?.type === 'config' || post?.type === 'Config')
|
||||
return (
|
||||
post &&
|
||||
post?.type &&
|
||||
(post?.type === 'CONFIG' ||
|
||||
post?.type === 'config' ||
|
||||
post?.type === 'Config')
|
||||
)
|
||||
})
|
||||
|
||||
if (!configPage) {
|
||||
@@ -43,21 +51,26 @@ export async function getConfigMapFromConfigPage(allPages) {
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
console.warn('[Notion配置] 未找到配置表格', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value)
|
||||
console.warn(
|
||||
'[Notion配置] 未找到配置表格',
|
||||
pageRecordMap.block[configPageId],
|
||||
pageRecordMap.block[configPageId].value
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
// 找到配置文件中的database
|
||||
// for (const contentId of content) {
|
||||
// console.log('内容', contentId, configPageRecordMap.block[contentId].value.type === 'collection_view')
|
||||
// }
|
||||
// 找到PAGE文件中的database
|
||||
const configTableId = content?.find(contentId => {
|
||||
return pageRecordMap.block[contentId].value.type === 'collection_view'
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-constant-condition, no-self-compare
|
||||
if (!configTableId) {
|
||||
console.warn('[Notion配置]未找到配置表格数据', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value)
|
||||
console.warn(
|
||||
'[Notion配置]未找到配置表格数据',
|
||||
pageRecordMap.block[configPageId],
|
||||
pageRecordMap.block[configPageId].value
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -67,7 +80,8 @@ export async function getConfigMapFromConfigPage(allPages) {
|
||||
const rawMetadata = databaseRecordMap.value
|
||||
// Check Type Page-Database和Inline-Database
|
||||
if (
|
||||
rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view'
|
||||
rawMetadata?.type !== 'collection_view_page' &&
|
||||
rawMetadata?.type !== 'collection_view'
|
||||
) {
|
||||
console.error(`pageId "${configTableId}" is not a database`)
|
||||
return null
|
||||
@@ -79,9 +93,21 @@ export async function getConfigMapFromConfigPage(allPages) {
|
||||
const collectionView = pageRecordMap.collection_view
|
||||
const schema = collection?.schema
|
||||
const viewIds = rawMetadata?.view_ids
|
||||
const pageIds = getAllPageIds(collectionQuery, collectionId, collectionView, viewIds)
|
||||
const pageIds = getAllPageIds(
|
||||
collectionQuery,
|
||||
collectionId,
|
||||
collectionView,
|
||||
viewIds
|
||||
)
|
||||
if (pageIds?.length === 0) {
|
||||
console.error('[Notion配置]获取到的文章列表为空,请检查notion模板', collectionQuery, collection, collectionView, viewIds, databaseRecordMap)
|
||||
console.error(
|
||||
'[Notion配置]获取到的文章列表为空,请检查notion模板',
|
||||
collectionQuery,
|
||||
collection,
|
||||
collectionView,
|
||||
viewIds,
|
||||
databaseRecordMap
|
||||
)
|
||||
}
|
||||
// 遍历用户的表格
|
||||
for (let i = 0; i < pageIds.length; i++) {
|
||||
@@ -136,5 +162,31 @@ export async function getConfigMapFromConfigPage(allPages) {
|
||||
}
|
||||
}
|
||||
|
||||
return notionConfig
|
||||
// 最后检查Notion_Config页面的INLINE_CONFIG,是否是一个js对象
|
||||
const combine = Object.assign(
|
||||
{},
|
||||
deepClone(notionConfig),
|
||||
parseConfig(notionConfig?.INLINE_CONFIG)
|
||||
)
|
||||
return combine
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析INLINE_CONFIG
|
||||
* @param {*} configString
|
||||
* @returns
|
||||
*/
|
||||
export function parseConfig(configString) {
|
||||
if (!configString) {
|
||||
return {}
|
||||
}
|
||||
// 解析对象
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
const config = eval('(' + configString + ')')
|
||||
return config
|
||||
} catch (evalError) {
|
||||
console.error('解析 eval(INLINE_CONFIG) 配置时出错:', evalError)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,27 @@ const mapImgUrl = (img, block, type = 'block', from = 'post') => {
|
||||
}
|
||||
|
||||
// Notion 图床转换为永久地址
|
||||
const isNotionSignImg = ret.indexOf('https://www.notion.so/image') !== 0 && (ret.indexOf('secure.notion-static.com') > 0 || ret.indexOf('prod-files-secure') > 0)
|
||||
const isNotionSignImg =
|
||||
ret.indexOf('https://www.notion.so/image') !== 0 &&
|
||||
(ret.indexOf('secure.notion-static.com') > 0 ||
|
||||
ret.indexOf('prod-files-secure') > 0)
|
||||
const isImgBlock = BLOG.IMG_URL_TYPE === 'Notion' || type !== 'block'
|
||||
if (isNotionSignImg && isImgBlock) {
|
||||
ret = BLOG.NOTION_HOST + '/image/' + encodeURIComponent(ret) + '?table=' + type + '&id=' + block.id
|
||||
ret =
|
||||
BLOG.NOTION_HOST +
|
||||
'/image/' +
|
||||
encodeURIComponent(ret) +
|
||||
'?table=' +
|
||||
type +
|
||||
'&id=' +
|
||||
block.id
|
||||
}
|
||||
|
||||
if (!isEmoji(ret) && ret.indexOf('notion.so/images/page-cover') < 0) {
|
||||
if (BLOG.RANDOM_IMAGE_URL) {
|
||||
// 只有配置了随机图片接口,才会替换图片
|
||||
const texts = BLOG.RANDOM_IMAGE_REPLACE_TEXT
|
||||
let isReplace = false;
|
||||
let isReplace = false
|
||||
if (texts) {
|
||||
const textArr = texts.split(',')
|
||||
// 判断是否包含替换的文本
|
||||
@@ -58,7 +68,11 @@ const mapImgUrl = (img, block, type = 'block', from = 'post') => {
|
||||
}
|
||||
|
||||
// 统一压缩图片
|
||||
if (from === 'pageCoverThumbnail' || block.type === 'image' || block.type === 'page') {
|
||||
if (
|
||||
from === 'pageCoverThumbnail' ||
|
||||
block?.type === 'image' ||
|
||||
block?.type === 'page'
|
||||
) {
|
||||
const width = block?.format?.block_width
|
||||
ret = compressImage(ret, width)
|
||||
}
|
||||
@@ -72,8 +86,9 @@ const mapImgUrl = (img, block, type = 'block', from = 'post') => {
|
||||
* @returns
|
||||
*/
|
||||
function isEmoji(str) {
|
||||
const emojiRegex = /[\u{1F300}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F018}-\u{1F270}\u{238C}\u{2B06}\u{2B07}\u{2B05}\u{27A1}\u{2194}-\u{2199}\u{2194}\u{21A9}\u{21AA}\u{2934}\u{2935}\u{25AA}\u{25AB}\u{25FE}\u{25FD}\u{25FB}\u{25FC}\u{25B6}\u{25C0}\u{1F200}-\u{1F251}]/u;
|
||||
return emojiRegex.test(str);
|
||||
const emojiRegex =
|
||||
/[\u{1F300}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F018}-\u{1F270}\u{238C}\u{2B06}\u{2B07}\u{2B05}\u{27A1}\u{2194}-\u{2199}\u{2194}\u{21A9}\u{21AA}\u{2934}\u{2935}\u{25AA}\u{25AB}\u{25FE}\u{25FD}\u{25FB}\u{25FC}\u{25B6}\u{25C0}\u{1F200}-\u{1F251}]/u
|
||||
return emojiRegex.test(str)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +112,10 @@ const compressImage = (image, width, quality = 50, fmt = 'webp') => {
|
||||
const params = new URLSearchParams(urlObj.search)
|
||||
|
||||
// Notion图床
|
||||
if (image.indexOf(BLOG.NOTION_HOST) === 0 && image.indexOf('amazonaws.com') > 0) {
|
||||
if (
|
||||
image.indexOf(BLOG.NOTION_HOST) === 0 &&
|
||||
image.indexOf('amazonaws.com') > 0
|
||||
) {
|
||||
params.set('width', width)
|
||||
params.set('cache', 'v2')
|
||||
// 生成新的URL
|
||||
@@ -117,11 +135,11 @@ const compressImage = (image, width, quality = 50, fmt = 'webp') => {
|
||||
return urlObj.toString()
|
||||
} else if (image.indexOf('https://your_picture_bed') === 0) {
|
||||
// 此处还可以添加您的自定义图传的封面图压缩参数。
|
||||
// .e.g
|
||||
// .e.g
|
||||
return 'do_somethin_here'
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
export { mapImgUrl, compressImage }
|
||||
export { compressImage, mapImgUrl }
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
const { loadExternalResource } = require('../utils');
|
||||
const { loadExternalResource } = require('../utils')
|
||||
|
||||
/**
|
||||
* WOWjs动画,结合animate.css使用很方便
|
||||
* 是data-aos的平替 aos ≈ wowjs + animate
|
||||
*/
|
||||
export const loadWowJS = async () => {
|
||||
await loadExternalResource('/css/wow/animate.css', 'css');
|
||||
await loadExternalResource('https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js', 'js');
|
||||
await loadExternalResource('/css/wow/animate.css', 'css')
|
||||
await loadExternalResource(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js',
|
||||
'js'
|
||||
)
|
||||
// 配合animatecss 实现延时滚动动画,和AOS动画相似
|
||||
const WOW = window.WOW;
|
||||
console.log('加载WOW动画', WOW)
|
||||
const WOW = window.WOW
|
||||
if (WOW) {
|
||||
new WOW().init();
|
||||
new WOW().init()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
40
lib/rss.js
40
lib/rss.js
@@ -1,15 +1,16 @@
|
||||
import fs from 'fs'
|
||||
import { Feed } from 'feed'
|
||||
import BLOG from '@/blog.config'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import { getPostBlocks } from '@/lib/db/getSiteData'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import { getPostBlocks } from '@/lib/db/getSiteData'
|
||||
import { Feed } from 'feed'
|
||||
import fs from 'fs'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import { siteConfig } from './config'
|
||||
|
||||
/**
|
||||
* 生成RSS内容
|
||||
* @param {*} post
|
||||
* @returns
|
||||
*/
|
||||
* 生成RSS内容
|
||||
* @param {*} post
|
||||
* @returns
|
||||
*/
|
||||
const createFeedContent = async post => {
|
||||
// 加密的文章内容只返回摘要
|
||||
if (post.password && post.password !== '') {
|
||||
@@ -20,30 +21,35 @@ const createFeedContent = async post => {
|
||||
post.blockMap = blockMap
|
||||
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
|
||||
const regexExp =
|
||||
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
|
||||
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
|
||||
return content.replace(regexExp, '')
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateRss(posts) {
|
||||
export async function generateRss(NOTION_CONFIG, posts) {
|
||||
const link = siteConfig('LINK', BLOG.LINK, NOTION_CONFIG)
|
||||
const author = siteConfig('AUTHOR', BLOG.AUTHOR, NOTION_CONFIG)
|
||||
const lang = siteConfig('LANG', BLOG.LANG, NOTION_CONFIG)
|
||||
const subPath = siteConfig('SUB_PATH', BLOG.SUB_PATH, NOTION_CONFIG)
|
||||
|
||||
const year = new Date().getFullYear()
|
||||
const feed = new Feed({
|
||||
title: BLOG.TITLE,
|
||||
description: BLOG.DESCRIPTION,
|
||||
link: `${BLOG.LINK}/${BLOG.SUB_PATH}`,
|
||||
language: BLOG.LANG,
|
||||
favicon: `${BLOG.LINK}/favicon.png`,
|
||||
copyright: `All rights reserved ${year}, ${BLOG.AUTHOR}`,
|
||||
link: `${link}/${subPath}`,
|
||||
language: lang,
|
||||
favicon: `${link}/favicon.png`,
|
||||
copyright: `All rights reserved ${year}, ${author}`,
|
||||
author: {
|
||||
name: BLOG.AUTHOR,
|
||||
name: author,
|
||||
email: BLOG.CONTACT_EMAIL,
|
||||
link: BLOG.LINK
|
||||
link: link
|
||||
}
|
||||
})
|
||||
for (const post of posts) {
|
||||
feed.addItem({
|
||||
title: post.title,
|
||||
link: `${BLOG.LINK}/${post.slug}`,
|
||||
link: `${link}/${post.slug}`,
|
||||
description: post.summary,
|
||||
content: await createFeedContent(post),
|
||||
date: new Date(post?.publishDay)
|
||||
|
||||
33
lib/utils/pageId.js
Normal file
33
lib/utils/pageId.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 截取page-id的语言前缀
|
||||
* notionPageId的格式可以是 en:xxxxx
|
||||
* @param {*} str
|
||||
* @returns en|zh|xx
|
||||
*/
|
||||
function extractLangPrefix(str) {
|
||||
const match = str.match(/^(.+?):/)
|
||||
if (match && match[1]) {
|
||||
return match[1]
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取page-id的id
|
||||
* notionPageId的格式可以是 en:xxxxx * @param {*} str
|
||||
* @returns xxxxx
|
||||
*/
|
||||
function extractLangId(str) {
|
||||
// 使用正则表达式匹配字符串
|
||||
const match = str.match(/:\s*(.+)/)
|
||||
// 如果匹配成功,则返回匹配到的内容
|
||||
if (match && match[1]) {
|
||||
return match[1]
|
||||
} else {
|
||||
// 如果没有匹配到,则返回空字符串或者其他你想要返回的值
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { extractLangPrefix, extractLangId }
|
||||
@@ -2,11 +2,36 @@ const { THEME } = require('./blog.config')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const BLOG = require('./blog.config')
|
||||
const { extractLangPrefix } = require('./lib/utils/pageId')
|
||||
|
||||
// 打包时是否分析代码
|
||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
enabled: BLOG.BUNDLE_ANALYZER
|
||||
})
|
||||
|
||||
// 扫描项目 /themes下的目录名
|
||||
const themes = scanSubdirectories(path.resolve(__dirname, 'themes'))
|
||||
// 检测用户开启的多语言
|
||||
const locales = (function () {
|
||||
// 根据BLOG_NOTION_PAGE_ID 检查支持多少种语言数据.
|
||||
// 支持如下格式配置多个语言的页面id xxx,zh:xxx,en:xxx
|
||||
const langs = ['zh', 'en']
|
||||
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)
|
||||
// 如果包含前缀 例如 zh , en 等
|
||||
if (prefix) {
|
||||
if (!langs.includes(prefix)) {
|
||||
langs.push(prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return langs
|
||||
})()
|
||||
|
||||
/**
|
||||
* 扫描指定目录下的文件夹名,用于获取所有主题
|
||||
* @param {*} directory
|
||||
@@ -27,8 +52,7 @@ function scanSubdirectories(directory) {
|
||||
|
||||
return subdirectories
|
||||
}
|
||||
// 扫描项目 /themes下的目录名
|
||||
const themes = scanSubdirectories(path.resolve(__dirname, 'themes'))
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
images: {
|
||||
// 图片压缩
|
||||
@@ -55,8 +79,52 @@ module.exports = withBundleAnalyzer({
|
||||
}
|
||||
]
|
||||
},
|
||||
// 多语言
|
||||
i18n: {
|
||||
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*'
|
||||
@@ -84,17 +152,9 @@ module.exports = withBundleAnalyzer({
|
||||
]
|
||||
},
|
||||
webpack: (config, { dev, isServer }) => {
|
||||
// Replace React with Preact only in client production build
|
||||
// if (!dev && !isServer) {
|
||||
// Object.assign(config.resolve.alias, {
|
||||
// react: 'preact/compat',
|
||||
// 'react-dom/test-utils': 'preact/test-utils',
|
||||
// 'react-dom': 'preact/compat'
|
||||
// })
|
||||
// }
|
||||
// 动态主题:添加 resolve.alias 配置,将动态路径映射到实际路径
|
||||
if (!isServer) {
|
||||
console.log('[加载主题]', path.resolve(__dirname, 'themes', THEME))
|
||||
console.log('[默认主题]', path.resolve(__dirname, 'themes', THEME))
|
||||
}
|
||||
config.resolve.alias['@theme-components'] = path.resolve(
|
||||
__dirname,
|
||||
|
||||
147
package.json
147
package.json
@@ -1,81 +1,70 @@
|
||||
{
|
||||
"name": "notion-next",
|
||||
"version": "4.4.2",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tangly1024/NotionNext.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "tangly",
|
||||
"email": "mail@tangly1024.com",
|
||||
"url": "http://tangly1024.com"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@giscus/react": "^2.2.6",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@next/bundle-analyzer": "^12.1.1",
|
||||
"@vercel/analytics": "^1.0.0",
|
||||
"algoliasearch": "^4.18.0",
|
||||
"animejs": "^3.2.1",
|
||||
"aos": "^2.3.4",
|
||||
"axios": ">=0.21.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"feed": "^4.2.2",
|
||||
"js-md5": "^0.7.3",
|
||||
"localStorage": "^1.0.4",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"mongodb": "^4.6.0",
|
||||
"next": "13.3.1",
|
||||
"notion-client": "6.15.6",
|
||||
"notion-utils": "6.15.6",
|
||||
"nprogress": "^0.2.0",
|
||||
"preact": "^10.5.15",
|
||||
"prism-themes": "1.9.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-facebook": "^8.1.4",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-notion-x": "6.16.0",
|
||||
"react-share": "^4.4.1",
|
||||
"react-tweet-embed": "~2.0.0",
|
||||
"typed.js": "^2.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@waline/client": "^2.5.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-next": "^13.1.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.23.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"next-sitemap": "^1.6.203",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "3.2.5",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"axios": ">=0.21.1"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tangly/NotionNext/issues",
|
||||
"email": "tlyong1992@hotmail.com"
|
||||
}
|
||||
"name": "notion-next",
|
||||
"version": "4.4.3",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tangly1024/NotionNext.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "tangly",
|
||||
"email": "mail@tangly1024.com",
|
||||
"url": "http://tangly1024.com"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@next/bundle-analyzer": "^12.1.1",
|
||||
"@vercel/analytics": "^1.0.0",
|
||||
"algoliasearch": "^4.18.0",
|
||||
"feed": "^4.2.2",
|
||||
"js-md5": "^0.7.3",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"next": "13.3.1",
|
||||
"notion-client": "6.15.6",
|
||||
"notion-utils": "6.15.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-facebook": "^8.1.4",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-notion-x": "6.16.0",
|
||||
"react-share": "^4.4.1",
|
||||
"react-tweet-embed": "~2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@waline/client": "^2.5.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-next": "^13.1.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.23.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"next-sitemap": "^1.6.203",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "3.2.5",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"axios": ">=0.21.1"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tangly/NotionNext/issues",
|
||||
"email": "tlyong1992@hotmail.com"
|
||||
}
|
||||
}
|
||||
|
||||
17
pages/404.js
17
pages/404.js
@@ -1,7 +1,7 @@
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 404
|
||||
@@ -10,12 +10,17 @@ import { siteConfig } from '@/lib/config'
|
||||
*/
|
||||
const NoFound = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const props = (await getGlobalData({ from: '404' })) || {}
|
||||
export async function getStaticProps(req) {
|
||||
const { locale } = req
|
||||
|
||||
const props = (await getGlobalData({ from: '404', locale })) || {}
|
||||
return { props }
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,11 @@ export async function getStaticPaths() {
|
||||
paths: allPages
|
||||
?.filter(row => checkSlug(row))
|
||||
.map(row => ({
|
||||
params: { prefix: row.slug.split('/')[0], slug: row.slug.split('/')[1], suffix: row.slug.split('/').slice(1) }
|
||||
params: {
|
||||
prefix: row.slug.split('/')[0],
|
||||
slug: row.slug.split('/')[1],
|
||||
suffix: row.slug.split('/').slice(1)
|
||||
}
|
||||
})),
|
||||
fallback: true
|
||||
}
|
||||
@@ -46,18 +50,25 @@ export async function getStaticPaths() {
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export async function getStaticProps({ params: { prefix, slug, suffix } }) {
|
||||
export async function getStaticProps({
|
||||
params: { prefix, slug, suffix },
|
||||
locale
|
||||
}) {
|
||||
let fullSlug = prefix + '/' + slug + '/' + suffix.join('/')
|
||||
if (JSON.parse(BLOG.PSEUDO_STATIC)) {
|
||||
const from = `slug-props-${fullSlug}`
|
||||
const props = await getGlobalData({ from, locale })
|
||||
if (siteConfig('PSEUDO_STATIC', BLOG.PSEUDO_STATIC, props.NOTION_CONFIG)) {
|
||||
if (!fullSlug.endsWith('.html')) {
|
||||
fullSlug += '.html'
|
||||
}
|
||||
}
|
||||
const from = `slug-props-${fullSlug}`
|
||||
const props = await getGlobalData({ from })
|
||||
|
||||
// 在列表内查找文章
|
||||
props.post = props?.allPages?.find(p => {
|
||||
return p.type.indexOf('Menu') < 0 && (p.slug === fullSlug || p.id === idToUuid(fullSlug))
|
||||
return (
|
||||
p.type.indexOf('Menu') < 0 &&
|
||||
(p.slug === fullSlug || p.id === idToUuid(fullSlug))
|
||||
)
|
||||
})
|
||||
|
||||
// 处理非列表内文章的内信息
|
||||
@@ -72,7 +83,14 @@ export async function getStaticProps({ params: { prefix, slug, suffix } }) {
|
||||
// 无法获取文章
|
||||
if (!props?.post) {
|
||||
props.post = null
|
||||
return { props, revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND) }
|
||||
return {
|
||||
props,
|
||||
revalidate: siteConfig(
|
||||
'REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 文章内容加载
|
||||
@@ -85,12 +103,18 @@ export async function getStaticProps({ params: { prefix, slug, suffix } }) {
|
||||
}
|
||||
|
||||
// 推荐关联文章处理
|
||||
const allPosts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const allPosts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
if (allPosts && allPosts.length > 0) {
|
||||
const index = allPosts.indexOf(props.post)
|
||||
props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
props.recommendPosts = getRecommendPost(props.post, allPosts, siteConfig('POST_RECOMMEND_COUNT'))
|
||||
props.recommendPosts = getRecommendPost(
|
||||
props.post,
|
||||
allPosts,
|
||||
siteConfig('POST_RECOMMEND_COUNT')
|
||||
)
|
||||
} else {
|
||||
props.prev = null
|
||||
props.next = null
|
||||
@@ -100,7 +124,11 @@ export async function getStaticProps({ params: { prefix, slug, suffix } }) {
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +137,11 @@ function checkSlug(row) {
|
||||
if (slug.startsWith('/')) {
|
||||
slug = slug.substring(1)
|
||||
}
|
||||
return (slug.match(/\//g) || []).length >= 2 && row.type.indexOf('Menu') < 0 && !checkContainHttp(slug)
|
||||
return (
|
||||
(slug.match(/\//g) || []).length >= 2 &&
|
||||
row.type.indexOf('Menu') < 0 &&
|
||||
!checkContainHttp(slug)
|
||||
)
|
||||
}
|
||||
|
||||
export default PrefixSlug
|
||||
|
||||
@@ -28,25 +28,31 @@ export async function getStaticPaths() {
|
||||
const { allPages } = await getGlobalData({ from })
|
||||
const paths = allPages
|
||||
?.filter(row => checkSlug(row))
|
||||
.map(row => ({ params: { prefix: row.slug.split('/')[0], slug: row.slug.split('/')[1] } }))
|
||||
.map(row => ({
|
||||
params: { prefix: row.slug.split('/')[0], slug: row.slug.split('/')[1] }
|
||||
}))
|
||||
return {
|
||||
paths: paths,
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { prefix, slug } }) {
|
||||
export async function getStaticProps({ params: { prefix, slug }, locale }) {
|
||||
let fullSlug = prefix + '/' + slug
|
||||
if (JSON.parse(BLOG.PSEUDO_STATIC)) {
|
||||
const from = `slug-props-${fullSlug}`
|
||||
const props = await getGlobalData({ from, locale })
|
||||
|
||||
if (siteConfig('PSEUDO_STATIC', BLOG.PSEUDO_STATIC, props.NOTION_CONFIG)) {
|
||||
if (!fullSlug.endsWith('.html')) {
|
||||
fullSlug += '.html'
|
||||
}
|
||||
}
|
||||
const from = `slug-props-${fullSlug}`
|
||||
const props = await getGlobalData({ from })
|
||||
// 在列表内查找文章
|
||||
props.post = props?.allPages?.find(p => {
|
||||
return p.type.indexOf('Menu') < 0 && (p.slug === fullSlug || p.id === idToUuid(fullSlug))
|
||||
return (
|
||||
p.type.indexOf('Menu') < 0 &&
|
||||
(p.slug === fullSlug || p.id === idToUuid(fullSlug))
|
||||
)
|
||||
})
|
||||
|
||||
// 处理非列表内文章的内信息
|
||||
@@ -61,7 +67,14 @@ export async function getStaticProps({ params: { prefix, slug } }) {
|
||||
// 无法获取文章
|
||||
if (!props?.post) {
|
||||
props.post = null
|
||||
return { props, revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND) }
|
||||
return {
|
||||
props,
|
||||
revalidate: siteConfig(
|
||||
'REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 文章内容加载
|
||||
@@ -74,12 +87,18 @@ export async function getStaticProps({ params: { prefix, slug } }) {
|
||||
}
|
||||
|
||||
// 推荐关联文章处理
|
||||
const allPosts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const allPosts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
if (allPosts && allPosts.length > 0) {
|
||||
const index = allPosts.indexOf(props.post)
|
||||
props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
props.recommendPosts = getRecommendPost(props.post, allPosts, siteConfig('POST_RECOMMEND_COUNT'))
|
||||
props.recommendPosts = getRecommendPost(
|
||||
props.post,
|
||||
allPosts,
|
||||
siteConfig('POST_RECOMMEND_COUNT')
|
||||
)
|
||||
} else {
|
||||
props.prev = null
|
||||
props.next = null
|
||||
@@ -89,7 +108,11 @@ export async function getStaticProps({ params: { prefix, slug } }) {
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
function checkSlug(row) {
|
||||
@@ -97,6 +120,10 @@ function checkSlug(row) {
|
||||
if (slug.startsWith('/')) {
|
||||
slug = slug.substring(1)
|
||||
}
|
||||
return (slug.match(/\//g) || []).length === 1 && !checkContainHttp(slug) && row.type.indexOf('Menu') < 0
|
||||
return (
|
||||
(slug.match(/\//g) || []).length === 1 &&
|
||||
!checkContainHttp(slug) &&
|
||||
row.type.indexOf('Menu') < 0
|
||||
)
|
||||
}
|
||||
export default PrefixSlug
|
||||
|
||||
@@ -53,7 +53,10 @@ const Slug = props => {
|
||||
|
||||
props = { ...props, lock, setLock, validPassword }
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
@@ -67,25 +70,31 @@ export async function getStaticPaths() {
|
||||
|
||||
const from = 'slug-paths'
|
||||
const { allPages } = await getGlobalData({ from })
|
||||
const paths = allPages?.filter(row => checkSlug(row)).map(row => ({ params: { prefix: row.slug } }))
|
||||
const paths = allPages
|
||||
?.filter(row => checkSlug(row))
|
||||
.map(row => ({ params: { prefix: row.slug } }))
|
||||
return {
|
||||
paths: paths,
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { prefix } }) {
|
||||
export async function getStaticProps({ params: { prefix }, locale }) {
|
||||
let fullSlug = prefix
|
||||
if (JSON.parse(BLOG.PSEUDO_STATIC)) {
|
||||
const from = `slug-props-${fullSlug}`
|
||||
const props = await getGlobalData({ from, locale })
|
||||
if (siteConfig('PSEUDO_STATIC', BLOG.PSEUDO_STATIC, props.NOTION_CONFIG)) {
|
||||
if (!fullSlug.endsWith('.html')) {
|
||||
fullSlug += '.html'
|
||||
}
|
||||
}
|
||||
const from = `slug-props-${fullSlug}`
|
||||
const props = await getGlobalData({ from })
|
||||
|
||||
// 在列表内查找文章
|
||||
props.post = props?.allPages?.find(p => {
|
||||
return p.type.indexOf('Menu') < 0 && (p.slug === fullSlug || p.id === idToUuid(fullSlug))
|
||||
return (
|
||||
p.type.indexOf('Menu') < 0 &&
|
||||
(p.slug === fullSlug || p.id === idToUuid(fullSlug))
|
||||
)
|
||||
})
|
||||
|
||||
// 处理非列表内文章的内信息
|
||||
@@ -99,7 +108,14 @@ export async function getStaticProps({ params: { prefix } }) {
|
||||
// 无法获取文章
|
||||
if (!props?.post) {
|
||||
props.post = null
|
||||
return { props, revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND) }
|
||||
return {
|
||||
props,
|
||||
revalidate: siteConfig(
|
||||
'REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 文章内容加载
|
||||
@@ -113,12 +129,18 @@ export async function getStaticProps({ params: { prefix } }) {
|
||||
}
|
||||
|
||||
// 推荐关联文章处理
|
||||
const allPosts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const allPosts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
if (allPosts && allPosts.length > 0) {
|
||||
const index = allPosts.indexOf(props.post)
|
||||
props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
props.recommendPosts = getRecommendPost(props.post, allPosts, siteConfig('POST_RECOMMEND_COUNT'))
|
||||
props.recommendPosts = getRecommendPost(
|
||||
props.post,
|
||||
allPosts,
|
||||
siteConfig('POST_RECOMMEND_COUNT')
|
||||
)
|
||||
} else {
|
||||
props.prev = null
|
||||
props.next = null
|
||||
@@ -128,7 +150,11 @@ export async function getStaticProps({ params: { prefix } }) {
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +198,11 @@ function checkSlug(row) {
|
||||
if (slug.startsWith('/')) {
|
||||
slug = slug.substring(1)
|
||||
}
|
||||
return (slug.match(/\//g) || []).length === 0 && !checkContainHttp(slug) && row.type.indexOf('Menu') < 0
|
||||
return (
|
||||
(slug.match(/\//g) || []).length === 0 &&
|
||||
!checkContainHttp(slug) &&
|
||||
row.type.indexOf('Menu') < 0
|
||||
)
|
||||
}
|
||||
|
||||
export default Slug
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
// import '@/styles/animate.css' // @see https://animate.style/
|
||||
import '@/styles/globals.css'
|
||||
import '@/styles/nprogress.css'
|
||||
import '@/styles/utility-patterns.css'
|
||||
|
||||
// core styles shared by all of react-notion-x (required)
|
||||
import 'react-notion-x/src/styles.css'
|
||||
import '@/styles/notion.css' // 重写部分样式
|
||||
import 'aos/dist/aos.css' // You can also use <link> for styles
|
||||
import 'react-notion-x/src/styles.css'
|
||||
|
||||
import useAdjustStyle from '@/hooks/useAdjustStyle'
|
||||
import { GlobalContextProvider } from '@/lib/global'
|
||||
import { getGlobalLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { getQueryParam } from '../lib/utils'
|
||||
import useAdjustStyle from '@/hooks/useAdjustStyle'
|
||||
|
||||
// 各种扩展插件 这个要阻塞引入
|
||||
import BLOG from '@/blog.config'
|
||||
import ExternalPlugins from '@/components/ExternalPlugins'
|
||||
import GlobalHead from '@/components/GlobalHead'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
/**
|
||||
* App挂载DOM 入口文件
|
||||
@@ -27,11 +25,15 @@ import BLOG from '@/blog.config'
|
||||
*/
|
||||
const MyApp = ({ Component, pageProps }) => {
|
||||
// 一些可能出现 bug 的样式,可以统一放入该钩子进行调整
|
||||
useAdjustStyle();
|
||||
useAdjustStyle()
|
||||
|
||||
const route = useRouter()
|
||||
const queryParam = useMemo(() => {
|
||||
return getQueryParam(route.asPath, 'theme') || pageProps?.NOTION_CONFIG?.THEME || BLOG.THEME
|
||||
return (
|
||||
getQueryParam(route.asPath, 'theme') ||
|
||||
pageProps?.NOTION_CONFIG?.THEME ||
|
||||
BLOG.THEME
|
||||
)
|
||||
}, [route])
|
||||
|
||||
// 整体布局
|
||||
@@ -47,7 +49,7 @@ const MyApp = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<GlobalContextProvider {...pageProps}>
|
||||
<GLayout {...pageProps}>
|
||||
<GlobalHead {...pageProps}/>
|
||||
<GlobalHead {...pageProps} />
|
||||
<Component {...pageProps} />
|
||||
</GLayout>
|
||||
<ExternalPlugins {...pageProps} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// eslint-disable-next-line @next/next/no-document-import-in-page
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import BLOG from '@/blog.config'
|
||||
import Document, { Head, Html, Main, NextScript } from 'next/document'
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
@@ -10,29 +10,52 @@ class MyDocument extends Document {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang={BLOG.LANG}>
|
||||
<Head>
|
||||
<link rel='icon' href= {`${BLOG.BLOG_FAVICON}`} />
|
||||
{/* 预加载字体 */}
|
||||
{BLOG.FONT_AWESOME && <>
|
||||
<link rel='preload' href={BLOG.FONT_AWESOME} as="style" crossOrigin="anonymous" />
|
||||
<link rel="stylesheet" href={BLOG.FONT_AWESOME} crossOrigin="anonymous" referrerPolicy="no-referrer" />
|
||||
</>}
|
||||
<Html lang={BLOG.LANG}>
|
||||
<Head>
|
||||
<link rel='icon' href={`${BLOG.BLOG_FAVICON}`} />
|
||||
{/* 预加载字体 */}
|
||||
{BLOG.FONT_AWESOME && (
|
||||
<>
|
||||
<link
|
||||
rel='preload'
|
||||
href={BLOG.FONT_AWESOME}
|
||||
as='style'
|
||||
crossOrigin='anonymous'
|
||||
/>
|
||||
<link
|
||||
rel='stylesheet'
|
||||
href={BLOG.FONT_AWESOME}
|
||||
crossOrigin='anonymous'
|
||||
referrerPolicy='no-referrer'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{BLOG.FONT_URL?.map((fontUrl, index) => {
|
||||
if (fontUrl.endsWith('.css') || fontUrl.includes('googleapis.com/css')) {
|
||||
return <link key={index} rel="stylesheet" href={fontUrl} />
|
||||
} else {
|
||||
return <link key={index} rel="preload" href={fontUrl} as="font" type="font/woff2" />
|
||||
}
|
||||
})}
|
||||
</Head>
|
||||
{BLOG.FONT_URL?.map((fontUrl, index) => {
|
||||
if (
|
||||
fontUrl.endsWith('.css') ||
|
||||
fontUrl.includes('googleapis.com/css')
|
||||
) {
|
||||
return <link key={index} rel='stylesheet' href={fontUrl} />
|
||||
} else {
|
||||
return (
|
||||
<link
|
||||
key={index}
|
||||
rel='preload'
|
||||
href={fontUrl}
|
||||
as='font'
|
||||
type='font/woff2'
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</Head>
|
||||
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { useEffect } from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { formatDateFmt } from '@/lib/utils/formatDate'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const ArchiveIndex = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
@@ -28,10 +31,12 @@ const ArchiveIndex = props => {
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const props = await getGlobalData({ from: 'archive-index' })
|
||||
export async function getStaticProps({ locale }) {
|
||||
const props = await getGlobalData({ from: 'archive-index', locale })
|
||||
// 处理分页
|
||||
props.posts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
props.posts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
delete props.allPages
|
||||
|
||||
const postsSortByDate = Object.create(props.posts)
|
||||
@@ -56,7 +61,11 @@ export async function getStaticProps() {
|
||||
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,19 +11,26 @@ import { useRouter } from 'next/router'
|
||||
*/
|
||||
export default function Category(props) {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { category } }) {
|
||||
export async function getStaticProps({ params: { category }, locale }) {
|
||||
const from = 'category-props'
|
||||
let props = await getGlobalData({ from })
|
||||
let props = await getGlobalData({ from, locale })
|
||||
|
||||
// 过滤状态
|
||||
props.posts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
props.posts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
// 处理过滤
|
||||
props.posts = props.posts.filter(post => post && post.category && post.category.includes(category))
|
||||
props.posts = props.posts.filter(
|
||||
post => post && post.category && post.category.includes(category)
|
||||
)
|
||||
// 处理文章页数
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
@@ -39,7 +46,11 @@ export async function getStaticProps({ params: { category } }) {
|
||||
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ import { useRouter } from 'next/router'
|
||||
|
||||
export default function Category(props) {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
return <Layout {...props} />
|
||||
}
|
||||
@@ -28,7 +31,10 @@ export async function getStaticProps({ params: { category, page } }) {
|
||||
// 处理文章页数
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
props.posts = props.posts.slice(siteConfig('POSTS_PER_PAGE') * (page - 1), siteConfig('POSTS_PER_PAGE') * page)
|
||||
props.posts = props.posts.slice(
|
||||
siteConfig('POSTS_PER_PAGE') * (page - 1),
|
||||
siteConfig('POSTS_PER_PAGE') * page
|
||||
)
|
||||
|
||||
delete props.allPages
|
||||
props.page = page
|
||||
@@ -37,7 +43,11 @@ export async function getStaticProps({ params: { category, page } }) {
|
||||
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +60,9 @@ export async function getStaticPaths() {
|
||||
// 过滤状态类型
|
||||
const categoryPosts = allPages
|
||||
?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
.filter(post => post && post.category && post.category.includes(category.name))
|
||||
.filter(
|
||||
post => post && post.category && post.category.includes(category.name)
|
||||
)
|
||||
// 处理文章页数
|
||||
const postCount = categoryPosts.length
|
||||
const totalPages = Math.ceil(postCount / siteConfig('POSTS_PER_PAGE'))
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import React from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 分类首页
|
||||
@@ -12,16 +11,23 @@ import { siteConfig } from '@/lib/config'
|
||||
*/
|
||||
export default function Category(props) {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const props = await getGlobalData({ from: 'category-index-props' })
|
||||
export async function getStaticProps({ locale }) {
|
||||
const props = await getGlobalData({ from: 'category-index-props', locale })
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ const Index = props => {
|
||||
* SSG 获取数据
|
||||
* @returns
|
||||
*/
|
||||
export async function getStaticProps() {
|
||||
export async function getStaticProps(req) {
|
||||
const { locale } = req
|
||||
const from = 'index'
|
||||
const props = await getGlobalData({ from })
|
||||
const props = await getGlobalData({ from, locale })
|
||||
|
||||
props.posts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
@@ -58,7 +59,7 @@ export async function getStaticProps() {
|
||||
generateRobotsTxt()
|
||||
// 生成Feed订阅
|
||||
if (JSON.parse(BLOG.ENABLE_RSS)) {
|
||||
generateRss(props?.latestPosts || [])
|
||||
generateRss(props?.NOTION_CONFIG, props?.latestPosts || [])
|
||||
}
|
||||
|
||||
// 生成全文索引 - 仅在 yarn build 时执行 && process.env.npm_lifecycle_event === 'build'
|
||||
@@ -67,7 +68,11 @@ export async function getStaticProps() {
|
||||
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,17 @@ import { useRouter } from 'next/router'
|
||||
*/
|
||||
const Page = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
export async function getStaticPaths({ locale }) {
|
||||
const from = 'page-paths'
|
||||
const { postCount } = await getGlobalData({ from })
|
||||
const { postCount } = await getGlobalData({ from, locale })
|
||||
const totalPages = Math.ceil(postCount / siteConfig('POSTS_PER_PAGE'))
|
||||
return {
|
||||
// remove first page, we 're not gonna handle that.
|
||||
@@ -33,9 +36,14 @@ export async function getStaticProps({ params: { page } }) {
|
||||
const from = `page-${page}`
|
||||
const props = await getGlobalData({ from })
|
||||
const { allPages } = props
|
||||
const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const allPosts = allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
// 处理分页
|
||||
props.posts = allPosts.slice(siteConfig('POSTS_PER_PAGE') * (page - 1), siteConfig('POSTS_PER_PAGE') * page)
|
||||
props.posts = allPosts.slice(
|
||||
siteConfig('POSTS_PER_PAGE') * (page - 1),
|
||||
siteConfig('POSTS_PER_PAGE') * page
|
||||
)
|
||||
props.page = page
|
||||
|
||||
// 处理预览
|
||||
@@ -45,14 +53,22 @@ export async function getStaticProps({ params: { page } }) {
|
||||
if (post.password && post.password !== '') {
|
||||
continue
|
||||
}
|
||||
post.blockMap = await getPostBlocks(post.id, 'slug', siteConfig('POST_PREVIEW_LINES'))
|
||||
post.blockMap = await getPostBlocks(
|
||||
post.id,
|
||||
'slug',
|
||||
siteConfig('POST_PREVIEW_LINES')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ import { useRouter } from 'next/router'
|
||||
|
||||
const Index = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
return <Layout {...props} />
|
||||
}
|
||||
@@ -17,13 +20,15 @@ const Index = props => {
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export async function getStaticProps({ params: { keyword } }) {
|
||||
export async function getStaticProps({ params: { keyword }, locale }) {
|
||||
const props = await getGlobalData({
|
||||
from: 'search-props',
|
||||
pageType: ['Post']
|
||||
locale
|
||||
})
|
||||
const { allPages } = props
|
||||
const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const allPosts = allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
props.posts = await filterByMemCache(allPosts, keyword)
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
@@ -35,7 +40,11 @@ export async function getStaticProps({ params: { keyword } }) {
|
||||
props.keyword = keyword
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +96,8 @@ function getTextContent(textArray) {
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
const isIterable = obj =>
|
||||
obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
|
||||
/**
|
||||
* 在内存缓存中进行全文索引
|
||||
@@ -103,8 +113,12 @@ async function filterByMemCache(allPosts, keyword) {
|
||||
for (const post of allPosts) {
|
||||
const cacheKey = 'page_block_' + post.id
|
||||
const page = await getDataFromCache(cacheKey, true)
|
||||
const tagContent = post?.tags && Array.isArray(post?.tags) ? post?.tags.join(' ') : ''
|
||||
const categoryContent = post.category && Array.isArray(post.category) ? post.category.join(' ') : ''
|
||||
const tagContent =
|
||||
post?.tags && Array.isArray(post?.tags) ? post?.tags.join(' ') : ''
|
||||
const categoryContent =
|
||||
post.category && Array.isArray(post.category)
|
||||
? post.category.join(' ')
|
||||
: ''
|
||||
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
||||
let hit = articleInfo.toLowerCase().indexOf(keyword) > -1
|
||||
const indexContent = getPageContentText(post, page)
|
||||
|
||||
@@ -8,7 +8,10 @@ import { useRouter } from 'next/router'
|
||||
const Index = props => {
|
||||
const { keyword } = props
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
props = { ...props, currentSearch: keyword }
|
||||
|
||||
return <Layout {...props} />
|
||||
@@ -19,23 +22,33 @@ const Index = props => {
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export async function getStaticProps({ params: { keyword, page } }) {
|
||||
export async function getStaticProps({ params: { keyword, page }, locale }) {
|
||||
const props = await getGlobalData({
|
||||
from: 'search-props',
|
||||
pageType: ['Post']
|
||||
pageType: ['Post'],
|
||||
locale
|
||||
})
|
||||
const { allPages } = props
|
||||
const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
const allPosts = allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
props.posts = await filterByMemCache(allPosts, keyword)
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
props.posts = props.posts.slice(siteConfig('POSTS_PER_PAGE') * (page - 1), siteConfig('POSTS_PER_PAGE') * page)
|
||||
props.posts = props.posts.slice(
|
||||
siteConfig('POSTS_PER_PAGE') * (page - 1),
|
||||
siteConfig('POSTS_PER_PAGE') * page
|
||||
)
|
||||
props.keyword = keyword
|
||||
props.page = page
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +100,8 @@ function getTextContent(textArray) {
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
const isIterable = obj =>
|
||||
obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
|
||||
/**
|
||||
* 在内存缓存中进行全文索引
|
||||
@@ -103,8 +117,12 @@ async function filterByMemCache(allPosts, keyword) {
|
||||
for (const post of allPosts) {
|
||||
const cacheKey = 'page_block_' + post.id
|
||||
const page = await getDataFromCache(cacheKey, true)
|
||||
const tagContent = post?.tags && Array.isArray(post?.tags) ? post?.tags.join(' ') : ''
|
||||
const categoryContent = post.category && Array.isArray(post.category) ? post.category.join(' ') : ''
|
||||
const tagContent =
|
||||
post?.tags && Array.isArray(post?.tags) ? post?.tags.join(' ') : ''
|
||||
const categoryContent =
|
||||
post.category && Array.isArray(post.category)
|
||||
? post.category.join(' ')
|
||||
: ''
|
||||
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
||||
let hit = articleInfo.indexOf(keyword) > -1
|
||||
let indexContent = [post.summary]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { useRouter } from 'next/router'
|
||||
import BLOG from '@/blog.config'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 搜索路由
|
||||
@@ -13,7 +13,10 @@ const Search = props => {
|
||||
const { posts } = props
|
||||
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const keyword = router?.query?.s
|
||||
@@ -25,7 +28,7 @@ const Search = props => {
|
||||
const tagContent = post?.tags ? post?.tags.join(' ') : ''
|
||||
const categoryContent = post.category ? post.category.join(' ') : ''
|
||||
const searchContent =
|
||||
post.title + post.summary + tagContent + categoryContent
|
||||
post.title + post.summary + tagContent + categoryContent
|
||||
return searchContent.toLowerCase().includes(keyword.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
@@ -40,16 +43,22 @@ const Search = props => {
|
||||
/**
|
||||
* 浏览器前端搜索
|
||||
*/
|
||||
export async function getStaticProps() {
|
||||
export async function getStaticProps({ locale }) {
|
||||
const props = await getGlobalData({
|
||||
from: 'search-props',
|
||||
pageType: ['Post']
|
||||
locale
|
||||
})
|
||||
const { allPages } = props
|
||||
props.posts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
props.posts = allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 登录
|
||||
@@ -11,18 +11,27 @@ import { siteConfig } from '@/lib/config'
|
||||
*/
|
||||
const SignIn = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
export async function getStaticProps(req) {
|
||||
const { locale } = req
|
||||
|
||||
const from = 'SignIn'
|
||||
const props = await getGlobalData({ from })
|
||||
const props = await getGlobalData({ from, locale })
|
||||
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 注册
|
||||
@@ -11,18 +11,27 @@ import { siteConfig } from '@/lib/config'
|
||||
*/
|
||||
const SignUp = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
export async function getStaticProps(req) {
|
||||
const { locale } = req
|
||||
|
||||
const from = 'SignIn'
|
||||
const props = await getGlobalData({ from })
|
||||
const props = await getGlobalData({ from, locale })
|
||||
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,61 +1,94 @@
|
||||
// pages/sitemap.xml.js
|
||||
import { getServerSideSitemap } from 'next-sitemap'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import BLOG from '@/blog.config'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getNotionPageData } from '@/lib/db/getSiteData'
|
||||
import { extractLangId, extractLangPrefix } from '@/lib/utils/pageId'
|
||||
import { getServerSideSitemap } from 'next-sitemap'
|
||||
|
||||
export const getServerSideProps = async (ctx) => {
|
||||
const { allPages } = await getGlobalData({ from: 'rss' })
|
||||
const defaultFields = [
|
||||
{
|
||||
loc: `${BLOG.LINK}`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}, {
|
||||
loc: `${BLOG.LINK}/archive`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}, {
|
||||
loc: `${BLOG.LINK}/category`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}, {
|
||||
loc: `${BLOG.LINK}/feed`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}, {
|
||||
loc: `${BLOG.LINK}/search`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}, {
|
||||
loc: `${BLOG.LINK}/tag`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}
|
||||
]
|
||||
const postFields = allPages?.filter(p => p.status === BLOG.NOTION_PROPERTY_NAME.status_publish)?.map(post => {
|
||||
const slugWithoutLeadingSlash = post?.slug.startsWith('/') ? post?.slug?.slice(1) : post.slug
|
||||
return {
|
||||
loc: `${BLOG.LINK}/${slugWithoutLeadingSlash}`,
|
||||
lastmod: new Date(post?.publishDay).toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}
|
||||
})
|
||||
const fields = defaultFields.concat(postFields)
|
||||
export const getServerSideProps = async ctx => {
|
||||
let fields = []
|
||||
const siteIds = BLOG.NOTION_PAGE_ID.split(',')
|
||||
for (let index = 0; index < siteIds.length; index++) {
|
||||
const siteId = siteIds[index]
|
||||
const id = extractLangId(siteId)
|
||||
const locale = extractLangPrefix(siteId)
|
||||
// 第一个id站点默认语言
|
||||
const siteData = await getNotionPageData({
|
||||
pageId: id,
|
||||
from: 'sitemap.xml'
|
||||
})
|
||||
const link = siteConfig('LINK', BLOG.LINK, siteData.NOTION_CONFIG)
|
||||
const localeFields = generateLocalesSitemap(link, siteData.allPages, locale)
|
||||
fields = fields.concat(localeFields)
|
||||
}
|
||||
|
||||
// 缓存
|
||||
ctx.res.setHeader(
|
||||
'Cache-Control',
|
||||
'public, max-age=3600, stale-while-revalidate=59'
|
||||
)
|
||||
|
||||
console.log('fff', fields)
|
||||
return getServerSideSitemap(ctx, fields)
|
||||
}
|
||||
|
||||
export default () => { }
|
||||
function generateLocalesSitemap(link, allPages, locale) {
|
||||
if (locale && locale.length > 0 && locale.indexOf('/') !== 0) {
|
||||
locale = '/' + locale
|
||||
}
|
||||
const defaultFields = [
|
||||
{
|
||||
loc: `${link}${locale}`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
},
|
||||
{
|
||||
loc: `${link}${locale}/archive`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
},
|
||||
{
|
||||
loc: `${link}${locale}/category`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
},
|
||||
{
|
||||
loc: `${link}${locale}/feed`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
},
|
||||
{
|
||||
loc: `${link}${locale}/search`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
},
|
||||
{
|
||||
loc: `${link}${locale}/tag`,
|
||||
lastmod: new Date().toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}
|
||||
]
|
||||
const postFields =
|
||||
allPages
|
||||
?.filter(p => p.status === BLOG.NOTION_PROPERTY_NAME.status_publish)
|
||||
?.map(post => {
|
||||
const slugWithoutLeadingSlash = post?.slug.startsWith('/')
|
||||
? post?.slug?.slice(1)
|
||||
: post.slug
|
||||
return {
|
||||
loc: `${link}${locale}/${slugWithoutLeadingSlash}`,
|
||||
lastmod: new Date(post?.publishDay).toISOString().split('T')[0],
|
||||
changefreq: 'daily',
|
||||
priority: '0.7'
|
||||
}
|
||||
}) ?? []
|
||||
|
||||
return defaultFields.concat(postFields)
|
||||
}
|
||||
|
||||
export default () => {}
|
||||
|
||||
@@ -11,14 +11,17 @@ import { useRouter } from 'next/router'
|
||||
*/
|
||||
const Tag = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { tag } }) {
|
||||
export async function getStaticProps({ params: { tag }, locale }) {
|
||||
const from = 'tag-props'
|
||||
const props = await getGlobalData({ from })
|
||||
const props = await getGlobalData({ from, locale })
|
||||
|
||||
// 过滤状态
|
||||
props.posts = props.allPages
|
||||
@@ -39,7 +42,11 @@ export async function getStaticProps({ params: { tag } }) {
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,16 @@ import { useRouter } from 'next/router'
|
||||
|
||||
const Tag = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { tag, page } }) {
|
||||
export async function getStaticProps({ params: { tag, page }, locale }) {
|
||||
const from = 'tag-page-props'
|
||||
const props = await getGlobalData({ from })
|
||||
const props = await getGlobalData({ from, locale })
|
||||
// 过滤状态、标签
|
||||
props.posts = props.allPages
|
||||
?.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||
@@ -20,14 +23,21 @@ export async function getStaticProps({ params: { tag, page } }) {
|
||||
// 处理文章数
|
||||
props.postCount = props.posts.length
|
||||
// 处理分页
|
||||
props.posts = props.posts.slice(siteConfig('POSTS_PER_PAGE') * (page - 1), siteConfig('POSTS_PER_PAGE') * page)
|
||||
props.posts = props.posts.slice(
|
||||
siteConfig('POSTS_PER_PAGE') * (page - 1),
|
||||
siteConfig('POSTS_PER_PAGE') * page
|
||||
)
|
||||
|
||||
props.tag = tag
|
||||
props.page = page
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getGlobalData } from '@/lib/db/getSiteData'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 标签首页
|
||||
@@ -11,17 +11,26 @@ import { siteConfig } from '@/lib/config'
|
||||
*/
|
||||
const TagIndex = props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
|
||||
const Layout = getLayoutByTheme({
|
||||
theme: siteConfig('THEME'),
|
||||
router: useRouter()
|
||||
})
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
export async function getStaticProps(req) {
|
||||
const { locale } = req
|
||||
|
||||
const from = 'tag-index-props'
|
||||
const props = await getGlobalData({ from })
|
||||
const props = await getGlobalData({ from, locale })
|
||||
delete props.allPages
|
||||
return {
|
||||
props,
|
||||
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
|
||||
revalidate: siteConfig(
|
||||
'NEXT_REVALIDATE_SECOND',
|
||||
BLOG.NEXT_REVALIDATE_SECOND,
|
||||
props.NOTION_CONFIG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
209
public/js/giscus.js
Normal file
209
public/js/giscus.js
Normal file
@@ -0,0 +1,209 @@
|
||||
/* eslint-disable */
|
||||
;(function () {
|
||||
var baseUrl = 'https://giscus.app'
|
||||
var giscusIframe = null
|
||||
|
||||
// 错误日志
|
||||
function handleError(a) {
|
||||
return '[giscus] An error occurred. Error message: "'.concat(a, '".')
|
||||
}
|
||||
// 站点元信息
|
||||
function getMetaContent(name, includeProperty) {
|
||||
void 0 === includeProperty && (includeProperty = !1)
|
||||
includeProperty = includeProperty
|
||||
? "meta[property='og:".concat(name, "'],")
|
||||
: ''
|
||||
return (name = document.querySelector(
|
||||
includeProperty + "meta[name='".concat(name, "']")
|
||||
))
|
||||
? name.content
|
||||
: ''
|
||||
}
|
||||
|
||||
// 渲染
|
||||
function render(querySelector) {
|
||||
// const giscusContainer = document.currentScript
|
||||
const giscusContainer = document.querySelector(querySelector)
|
||||
// var k = new URL(m.src).origin
|
||||
let dataset = new URL(location.href)
|
||||
let paramsSession = dataset.searchParams.get('giscus') || ''
|
||||
const localStorageSession = localStorage.getItem('giscus-session')
|
||||
dataset.searchParams.delete('giscus')
|
||||
dataset.hash = ''
|
||||
let url = dataset.toString()
|
||||
if (paramsSession)
|
||||
localStorage.setItem('giscus-session', JSON.stringify(paramsSession)),
|
||||
history.replaceState(void 0, document.title, url)
|
||||
else if (localStorageSession) {
|
||||
try {
|
||||
paramsSession = JSON.parse(localStorageSession)
|
||||
} catch (a) {
|
||||
localStorage.removeItem('giscus-session'),
|
||||
console.warn(
|
||||
''.concat(
|
||||
handleError(a === null || void 0 === a ? void 0 : a.message),
|
||||
' Session has been cleared.'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dataset = giscusContainer.dataset
|
||||
var params = {}
|
||||
params.origin = url
|
||||
params.session = paramsSession
|
||||
params.theme = dataset.theme
|
||||
params.reactionsEnabled = dataset.reactionsEnabled || '1'
|
||||
params.emitMetadata = dataset.emitMetadata || '0'
|
||||
params.inputPosition = dataset.inputPosition || 'bottom'
|
||||
params.repo = dataset.repo
|
||||
params.repoId = dataset.repoId
|
||||
params.category = dataset.category || ''
|
||||
params.categoryId = dataset.categoryId
|
||||
params.strict = dataset.strict || '0'
|
||||
params.description = getMetaContent('description', !0)
|
||||
params.backLink = getMetaContent('giscus:backlink') || url
|
||||
switch (dataset.mapping) {
|
||||
case 'url':
|
||||
params.term = url
|
||||
break
|
||||
case 'title':
|
||||
params.term = document.title
|
||||
break
|
||||
case 'og:title':
|
||||
params.term = getMetaContent('title', !0)
|
||||
break
|
||||
case 'specific':
|
||||
params.term = dataset.term
|
||||
break
|
||||
case 'number':
|
||||
params.number = dataset.term
|
||||
break
|
||||
default:
|
||||
params.term =
|
||||
location.pathname.length < 2
|
||||
? 'index'
|
||||
: location.pathname.substring(1).replace(/\.\w+$/, '')
|
||||
}
|
||||
const q =
|
||||
(paramsSession = document.querySelector('.giscus')) && paramsSession.id
|
||||
q && (params.origin = ''.concat(url, '#').concat(q))
|
||||
url = dataset.lang ? '/'.concat(dataset.lang) : ''
|
||||
url = ''
|
||||
.concat(baseUrl)
|
||||
.concat(url, '/widget?')
|
||||
.concat(new URLSearchParams(params))
|
||||
dataset = dataset.loading === 'lazy' ? 'lazy' : void 0
|
||||
|
||||
// 创建iframe
|
||||
giscusIframe = document.createElement('iframe')
|
||||
Object.entries({
|
||||
class: 'giscus-frame giscus-frame--loading',
|
||||
title: 'Comments',
|
||||
scrolling: 'no',
|
||||
allow: 'clipboard-write',
|
||||
src: url,
|
||||
loading: dataset
|
||||
}).forEach(function (a) {
|
||||
const g = a[0]
|
||||
return (a = a[1]) && giscusIframe.setAttribute(g, a)
|
||||
})
|
||||
giscusIframe.style.opacity = '0'
|
||||
giscusIframe.addEventListener('load', function () {
|
||||
giscusIframe.style.removeProperty('opacity')
|
||||
giscusIframe.classList.remove('giscus-frame--loading')
|
||||
})
|
||||
dataset =
|
||||
document.getElementById('giscus-css') || document.createElement('link')
|
||||
dataset.id = 'giscus-css'
|
||||
dataset.rel = 'stylesheet'
|
||||
dataset.href = ''.concat(baseUrl, '/default.css')
|
||||
document.head.prepend(dataset)
|
||||
if (paramsSession) {
|
||||
for (; paramsSession.firstChild; ) paramsSession.firstChild.remove()
|
||||
paramsSession.appendChild(giscusIframe)
|
||||
} else
|
||||
(paramsSession = document.createElement('div')),
|
||||
paramsSession.setAttribute('class', 'giscus'),
|
||||
paramsSession.appendChild(giscusIframe),
|
||||
giscusContainer.insertAdjacentElement('afterend', paramsSession)
|
||||
}
|
||||
|
||||
// 处理接收消息
|
||||
function handdleMessage(event) {
|
||||
if (!giscusIframe) {
|
||||
return
|
||||
}
|
||||
event.origin === baseUrl &&
|
||||
((event = event.data),
|
||||
typeof event === 'object' &&
|
||||
event.giscus &&
|
||||
(event.giscus.resizeHeight &&
|
||||
(giscusIframe.style.height = ''.concat(
|
||||
event.giscus.resizeHeight,
|
||||
'px'
|
||||
)),
|
||||
event.giscus.signOut
|
||||
? (localStorage.removeItem('giscus-session'),
|
||||
console.log(
|
||||
'[giscus] User has logged out. Session has been cleared.'
|
||||
),
|
||||
p())
|
||||
: event.giscus.error &&
|
||||
((event = event.giscus.error),
|
||||
event.includes('Bad credentials') ||
|
||||
event.includes('Invalid state value') ||
|
||||
event.includes('State has expired')
|
||||
? localStorage.getItem('giscus-session') !== null
|
||||
? (localStorage.removeItem('giscus-session'),
|
||||
console.warn(
|
||||
''.concat(handleError(event), ' Session has been cleared.')
|
||||
),
|
||||
p())
|
||||
: localStorageSession ||
|
||||
console.error(
|
||||
''
|
||||
.concat(
|
||||
handleError(event),
|
||||
' No session is stored initially. '
|
||||
)
|
||||
.concat(
|
||||
'Please consider reporting this error at https://github.com/giscus/giscus/issues/new.'
|
||||
)
|
||||
)
|
||||
: event.includes('Discussion not found')
|
||||
? console.warn(
|
||||
'[giscus] '.concat(
|
||||
event,
|
||||
'. A new discussion will be created if a comment/reaction is submitted.'
|
||||
)
|
||||
)
|
||||
: event.includes('API rate limit exceeded')
|
||||
? console.warn(handleError(event))
|
||||
: console.error(
|
||||
''
|
||||
.concat(handleError(event), ' ')
|
||||
.concat(
|
||||
'Please consider reporting this error at https://github.com/giscus/giscus/issues/new.'
|
||||
)
|
||||
))))
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function initializeGiscus(querySelector) {
|
||||
render(querySelector)
|
||||
window.addEventListener('message', handdleMessage)
|
||||
}
|
||||
|
||||
// 销毁
|
||||
function destroyGiscus() {
|
||||
giscusIframe?.remove()
|
||||
giscusIframe = null
|
||||
}
|
||||
|
||||
// 暴露接口
|
||||
window.Giscus = {
|
||||
init: initializeGiscus,
|
||||
destroy: destroyGiscus
|
||||
}
|
||||
})()
|
||||
@@ -1,84 +0,0 @@
|
||||
/* Make clicks pass-through */
|
||||
#nprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: #29d;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* Fancy blur effect */
|
||||
#nprogress .peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||
opacity: 1;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||
transform: rotate(3deg) translate(0px, -4px);
|
||||
}
|
||||
|
||||
/* Remove these to get rid of the spinner */
|
||||
#nprogress .spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#nprogress .spinner-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #29d;
|
||||
border-left-color: #29d;
|
||||
border-radius: 50%;
|
||||
|
||||
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
||||
animation: nprogress-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent #nprogress .spinner,
|
||||
.nprogress-custom-parent #nprogress .bar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@-webkit-keyframes nprogress-spinner {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nprogress-spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,35 @@
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const MenuGroupCard = (props) => {
|
||||
const MenuGroupCard = props => {
|
||||
const { postCount, categoryOptions, tagOptions } = props
|
||||
const { locale } = useGlobal()
|
||||
const archiveSlot = <div className='text-center'>{postCount}</div>
|
||||
const categorySlot = <div className='text-center'>{categoryOptions?.length}</div>
|
||||
const categorySlot = (
|
||||
<div className='text-center'>{categoryOptions?.length}</div>
|
||||
)
|
||||
const tagSlot = <div className='text-center'>{tagOptions?.length}</div>
|
||||
|
||||
const links = [
|
||||
{ name: locale.COMMON.ARTICLE, to: '/archive', slot: archiveSlot, show: CONFIG.MENU_ARCHIVE },
|
||||
{ name: locale.COMMON.CATEGORY, to: '/category', slot: categorySlot, show: CONFIG.MENU_CATEGORY },
|
||||
{ name: locale.COMMON.TAGS, to: '/tag', slot: tagSlot, show: CONFIG.MENU_TAG }
|
||||
{
|
||||
name: locale.COMMON.ARTICLE,
|
||||
to: '/archive',
|
||||
slot: archiveSlot,
|
||||
show: CONFIG.MENU_ARCHIVE
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.CATEGORY,
|
||||
to: '/category',
|
||||
slot: categorySlot,
|
||||
show: CONFIG.MENU_CATEGORY
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.TAGS,
|
||||
to: '/tag',
|
||||
slot: tagSlot,
|
||||
show: CONFIG.MENU_TAG
|
||||
}
|
||||
]
|
||||
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
@@ -22,29 +39,31 @@ const MenuGroupCard = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<nav id='nav' className='leading-8 flex justify-center dark:text-gray-200 w-full'>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<Link
|
||||
key={`${link.to}`}
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className={'py-1.5 my-1 px-2 duration-300 text-base justify-center items-center cursor-pointer'}>
|
||||
|
||||
<div className='w-full items-center justify-center hover:scale-105 duration-200 transform dark:hover:text-red-400 hover:text-red-600'>
|
||||
<div className='text-center'>{link.name}</div>
|
||||
<div className='text-center font-semibold'>{link.slot}</div>
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
<nav
|
||||
id='nav'
|
||||
className='leading-8 flex justify-center dark:text-gray-200 w-full'>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<Link
|
||||
key={`${link.to}`}
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.target}
|
||||
className={
|
||||
'py-1.5 my-1 px-2 duration-300 text-base justify-center items-center cursor-pointer'
|
||||
}>
|
||||
<div className='w-full items-center justify-center hover:scale-105 duration-200 transform dark:hover:text-red-400 hover:text-red-600'>
|
||||
<div className='text-center'>{link.name}</div>
|
||||
<div className='text-center font-semibold'>{link.slot}</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
export default MenuGroupCard
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -26,30 +26,55 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='w-full px-8 py-3 text-left dark:bg-hexo-black-gray' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="hover:text-[#D2232A] font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' transition-all items-center duration-200'>{link?.icon && <i className={link.icon + ' mr-4'} />}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="hover:text-[#D2232A] font-extralight flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className='transition-all items-center duration-200'>{link?.icon && <i className={link.icon + ' mr-4'} />}{link?.name}</span>
|
||||
<i className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''} text-gray-400`}></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='w-full px-8 py-3 text-left dark:bg-hexo-black-gray'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='hover:text-[#D2232A] font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='hover:text-[#D2232A] font-extralight flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className='transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''} text-gray-400`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='dark:bg-black dark:text-gray-200 text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>{link?.icon && <i className={sLink.icon + ' mr-2'} />} {sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='dark:bg-black dark:text-gray-200 text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
{link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,33 +11,51 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} className='h-full'>
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}
|
||||
className='h-full'>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className={`${selected && 'border-b-2 border-[#D2232A]'} h-full flex space-x-1 whitespace-nowrap items-center font-sans menu-link pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1`}>
|
||||
{link?.icon && <i className={link?.icon} />} <div>{link?.name}</div>
|
||||
{/* {hasSubMenu && <i className='px-2 fa fa-angle-down'></i>} */}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className={`${selected && 'border-b-2 border-[#D2232A]'} h-full flex space-x-1 whitespace-nowrap items-center font-sans menu-link pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1`}>
|
||||
{link?.icon && <i className={link?.icon}/>} <div>{link?.name}</div>
|
||||
{/* {hasSubMenu && <i className='px-2 fa fa-angle-down'></i>} */}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu && <>
|
||||
<div className='h-full flex space-x-1 whitespace-nowrap items-center cursor-pointer font-sans menu-link pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon}/>} <div>{link?.name}</div>
|
||||
{/* <i className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i> */}
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul style={{ backdropFilter: 'blur(3px)' }} className={`${show ? 'visible opacity-100 shadow-lg' : 'invisible opacity-0'} overflow-hidden bg-white transition-all duration-300 z-20 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='cursor-pointer hover:bg-red-300 text-gray-900 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{hasSubMenu && (
|
||||
<>
|
||||
<div className='h-full flex space-x-1 whitespace-nowrap items-center cursor-pointer font-sans menu-link pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} <div>{link?.name}</div>
|
||||
{/* <i className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i> */}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
style={{ backdropFilter: 'blur(3px)' }}
|
||||
className={`${show ? 'visible opacity-100 shadow-lg' : 'invisible opacity-0'} overflow-hidden bg-white transition-all duration-300 z-20 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='cursor-pointer hover:bg-red-300 text-gray-900 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,34 +5,48 @@ export const MenuItemDrop = ({ link }) => {
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
|
||||
return <li className='cursor-pointer' onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<li
|
||||
className='cursor-pointer'
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<div className='rounded px-2 md:pl-0 md:mr-3 my-4 md:pr-3 text-gray-700 dark:text-gray-200 no-underline md:border-r border-gray-light'>
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<div className="rounded px-2 md:pl-0 md:mr-3 my-4 md:pr-3 text-gray-700 dark:text-gray-200 no-underline md:border-r border-gray-light">
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
{hasSubMenu && (
|
||||
<div className='rounded px-2 md:pl-0 md:mr-3 my-4 md:pr-3 text-gray-700 dark:text-gray-200 no-underline md:border-r border-gray-light'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-10'} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className='rounded px-2 md:pl-0 md:mr-3 my-4 md:pr-3 text-gray-700 dark:text-gray-200 no-underline md:border-r border-gray-light'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-10'} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -21,7 +21,7 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
const toggleShow = () => {
|
||||
changeShow(!show)
|
||||
@@ -31,32 +31,67 @@ export const MenuItemCollapse = (props) => {
|
||||
changeIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={ (selected ? 'bg-gray-600 text-white hover:text-white' : 'hover:text-gray-600') + ' px-5 w-full text-left duration-200 dark:bg-hexo-black-gray dark:border-black'} onClick={toggleShow} >
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
(selected
|
||||
? 'bg-gray-600 text-white hover:text-white'
|
||||
: 'hover:text-gray-600') +
|
||||
' px-5 w-full text-left duration-200 dark:bg-hexo-black-gray dark:border-black'
|
||||
}
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='dark:text-gray-200 py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu && <Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='dark:text-gray-200 py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
</Link>}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='py-2 font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest'>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
<div className='inline-flex items-center '>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="py-2 font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest">
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
<div className='inline-flex items-center '><i className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i></div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='whitespace-nowrap dark:text-gray-200
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='whitespace-nowrap dark:text-gray-200
|
||||
not:last-child:border-b-0 border-b dark:border-gray-800 py-2 px-14 cursor-pointer hover:bg-gray-100
|
||||
font-extralight dark:bg-black text-left justify-start text-gray-600 bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<div><div className={`${sLink.icon} text-center w-3 mr-3 text-xs`} />{sLink.title}</div>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<div>
|
||||
<div
|
||||
className={`${sLink.icon} text-center w-3 mr-3 text-xs`}
|
||||
/>
|
||||
{sLink.title}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,34 +5,62 @@ export const MenuItemDrop = ({ link }) => {
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
|
||||
return <li onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)}
|
||||
className='relative py-1 duration-500 justify-between text-gray-500 dark:text-gray-300 hover:text-black hover:underline cursor-pointer flex flex-nowrap items-center '>
|
||||
return (
|
||||
<li
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}
|
||||
className='relative py-1 duration-500 justify-between text-gray-500 dark:text-gray-300 hover:text-black hover:underline cursor-pointer flex flex-nowrap items-center '>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-2`} />
|
||||
{link.name}
|
||||
</div>
|
||||
{link.slot}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='w-full my-auto items-center justify-between flex ' >
|
||||
<div><div className={`${link.icon} text-center w-4 mr-2`} />{link.name}</div>
|
||||
{link.slot}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className='w-full my-auto items-center justify-between flex '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-2`} />{link.name}</div>
|
||||
{link.slot}
|
||||
{hasSubMenu && <div className='text-right'><i className={`px-2 fas fa-chevron-right duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i></div>}
|
||||
</div>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 left-52' : 'invisible opacity-0 left-40'} z-20 py-1 absolute right-0 top-0 w-full border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 drop-shadow-lg `}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return <li key={index}>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='my-auto py-1 px-2 items-center justify-start flex text-gray-500 dark:text-gray-300 hover:text-black hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 '>
|
||||
{sLink.icon && <i className={`${sLink.icon} w-4 text-center `} />}
|
||||
<div className={'ml-2 whitespace-nowrap'}>{sLink.name}</div>
|
||||
{sLink.slot}
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{hasSubMenu && (
|
||||
<div className='w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-2`} />
|
||||
{link.name}
|
||||
</div>
|
||||
{link.slot}
|
||||
{hasSubMenu && (
|
||||
<div className='text-right'>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 left-52' : 'invisible opacity-0 left-40'} z-20 py-1 absolute right-0 top-0 w-full border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 drop-shadow-lg `}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
<Link
|
||||
href={sLink.to}
|
||||
target={link?.target}
|
||||
className='my-auto py-1 px-2 items-center justify-start flex text-gray-500 dark:text-gray-300 hover:text-black hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 '>
|
||||
{sLink.icon && (
|
||||
<i className={`${sLink.icon} w-4 text-center `} />
|
||||
)}
|
||||
<div className={'ml-2 whitespace-nowrap'}>{sLink.name}</div>
|
||||
{sLink.slot}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -26,30 +26,59 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='w-full px-4 py-2 text-left dark:bg-hexo-black-gray dark:border-black' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='w-full px-4 py-2 text-left dark:bg-hexo-black-gray dark:border-black'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='font-extralight dark:bg-black text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='font-extralight dark:bg-black text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const MenuItemDrop = ({ link }) => {
|
||||
<Link
|
||||
href={link?.to}
|
||||
className='flex flex-nowrap'
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
target={link?.target}>
|
||||
<div className='w-6 mr-2 text-center'>
|
||||
{link?.icon && <i className={link?.icon} />}
|
||||
</div>
|
||||
@@ -47,11 +47,7 @@ export const MenuItemDrop = ({ link }) => {
|
||||
<div
|
||||
key={index}
|
||||
className='text-gray-700 dark:text-gray-200 tracking-widest transition-all duration-200 '>
|
||||
<Link
|
||||
href={sLink.to}
|
||||
target={
|
||||
link?.to?.indexOf('http') === 0 ? '_blank' : '_self'
|
||||
}>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -21,7 +21,7 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
const toggleShow = () => {
|
||||
changeShow(!show)
|
||||
@@ -31,31 +31,67 @@ export const MenuItemCollapse = (props) => {
|
||||
changeIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={ (selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600') + ' px-7 w-full text-left duration-200 dark:bg-hexo-black-gray dark:border-black'} onClick={toggleShow} >
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600') +
|
||||
' px-7 w-full text-left duration-200 dark:bg-hexo-black-gray dark:border-black'
|
||||
}
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu && <Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
</Link>}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='py-2 font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest'>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
<div className='inline-flex items-center '>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasSubMenu && <div onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="py-2 font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest">
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
<div className='inline-flex items-center '><i className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i></div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return <div key={index} className='
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='
|
||||
not:last-child:border-b-0 border-b dark:border-gray-800 py-2 px-14 cursor-pointer hover:bg-gray-100 dark:text-gray-200
|
||||
font-extralight dark:bg-black text-left justify-start text-gray-600 bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<div><div className={`${sLink.icon} text-center w-3 mr-3 text-xs`} />{sLink.title}</div>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<div>
|
||||
<div
|
||||
className={`${sLink.icon} text-center w-3 mr-3 text-xs`}
|
||||
/>
|
||||
{sLink.title}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const MenuItemDrop = ({ link }) => {
|
||||
const [show, changeShow] = useState(false)
|
||||
@@ -10,38 +10,64 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <li className='cursor-pointer list-none items-center flex mx-2' onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
return (
|
||||
<li
|
||||
className='cursor-pointer list-none items-center flex mx-2'
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
className={
|
||||
'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600')
|
||||
}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && (
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className={'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!hasSubMenu && (
|
||||
<div
|
||||
className={
|
||||
'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600')
|
||||
}>
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<div className={'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-xs font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return <li key={index} className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-xs font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,44 +1,63 @@
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import CONFIG from '../config'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const MenuGroupCard = (props) => {
|
||||
const MenuGroupCard = props => {
|
||||
const { postCount, categoryOptions, tagOptions } = props
|
||||
const { locale } = useGlobal()
|
||||
const archiveSlot = <div className='text-center'>{postCount}</div>
|
||||
const categorySlot = <div className='text-center'>{categoryOptions?.length}</div>
|
||||
const categorySlot = (
|
||||
<div className='text-center'>{categoryOptions?.length}</div>
|
||||
)
|
||||
const tagSlot = <div className='text-center'>{tagOptions?.length}</div>
|
||||
|
||||
const links = [
|
||||
{ name: locale.COMMON.ARTICLE, to: '/archive', slot: archiveSlot, show: siteConfig('HEO_MENU_ARCHIVE', null, CONFIG) },
|
||||
{ name: locale.COMMON.CATEGORY, to: '/category', slot: categorySlot, show: siteConfig('HEO_MENU_CATEGORY', null, CONFIG) },
|
||||
{ name: locale.COMMON.TAGS, to: '/tag', slot: tagSlot, show: siteConfig('HEO_MENU_TAG', null, CONFIG) }
|
||||
{
|
||||
name: locale.COMMON.ARTICLE,
|
||||
to: '/archive',
|
||||
slot: archiveSlot,
|
||||
show: siteConfig('HEO_MENU_ARCHIVE', null, CONFIG)
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.CATEGORY,
|
||||
to: '/category',
|
||||
slot: categorySlot,
|
||||
show: siteConfig('HEO_MENU_CATEGORY', null, CONFIG)
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.TAGS,
|
||||
to: '/tag',
|
||||
slot: tagSlot,
|
||||
show: siteConfig('HEO_MENU_TAG', null, CONFIG)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<nav id='nav' className='dark:text-gray-200 w-full px-5'>
|
||||
{links.map((link, index) => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<div key={index} className=''>
|
||||
<Link title={link.to}
|
||||
href={link.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className={'w-full flex items-center justify-between py-1 hover:scale-105 duration-200 transform dark:hover:text-indigo-400 hover:text-indigo-600 px-2 cursor-pointer'}>
|
||||
<>
|
||||
<div>{link.name} :</div>
|
||||
<div className='font-semibold'>{link.slot}</div>
|
||||
</>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
<nav id='nav' className='dark:text-gray-200 w-full px-5'>
|
||||
{links.map((link, index) => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<div key={index} className=''>
|
||||
<Link
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.target}
|
||||
className={
|
||||
'w-full flex items-center justify-between py-1 hover:scale-105 duration-200 transform dark:hover:text-indigo-400 hover:text-indigo-600 px-2 cursor-pointer'
|
||||
}>
|
||||
<>
|
||||
<div>{link.name} :</div>
|
||||
<div className='font-semibold'>{link.slot}</div>
|
||||
</>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
export default MenuGroupCard
|
||||
|
||||
@@ -25,30 +25,55 @@ export const MenuItemCollapse = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='select-none w-full px-2 py-2 border rounded-xl text-left dark:bg-hexo-black-gray' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest">
|
||||
<span className=' transition-all items-center duration-200'>{link?.icon && <i className={link.icon + ' mr-4'} />}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest">
|
||||
<span className='transition-all items-center duration-200'>{link?.icon && <i className={link.icon + ' mr-4'} />}{link?.name}</span>
|
||||
<i className={`select-none px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''} text-gray-400`}></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='select-none w-full px-2 py-2 border rounded-xl text-left dark:bg-hexo-black-gray'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest'>
|
||||
<span className=' transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='font-extralight flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest'>
|
||||
<span className='transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i
|
||||
className={`select-none px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''} text-gray-400`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} className='rounded-xl'>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='dark:bg-black dark:text-gray-200 text-left px-3 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>{link?.icon && <i className={sLink.icon + ' mr-2'} />} {sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} className='rounded-xl'>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='dark:bg-black dark:text-gray-200 text-left px-3 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
{link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,34 +9,50 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{/* 不含子菜单 */}
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
target={link?.target}
|
||||
href={link?.to}
|
||||
className=' hover:bg-black hover:bg-opacity-10 rounded-2xl flex justify-center items-center px-3 py-1 no-underline tracking-widest'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* 不含子菜单 */}
|
||||
{!hasSubMenu &&
|
||||
<Link
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
href={link?.to}
|
||||
className=" hover:bg-black hover:bg-opacity-10 rounded-2xl flex justify-center items-center px-3 py-1 no-underline tracking-widest">
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>}
|
||||
|
||||
{/* 含子菜单的按钮 */}
|
||||
{hasSubMenu && <>
|
||||
<div className='cursor-pointer hover:bg-black hover:bg-opacity-10 rounded-2xl flex justify-center items-center px-3 py-1 no-underline tracking-widest'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul style={{ backdropFilter: 'blur(3px)' }} className={`${show ? 'visible opacity-100 top-14' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-xl bg-white transition-all duration-300 z-20 absolute`}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='cursor-pointer hover:bg-blue-600 hover:text-white text-gray-900 tracking-widest transition-all duration-200 dark:border-gray-700 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{/* 含子菜单的按钮 */}
|
||||
{hasSubMenu && (
|
||||
<>
|
||||
<div className='cursor-pointer hover:bg-black hover:bg-opacity-10 rounded-2xl flex justify-center items-center px-3 py-1 no-underline tracking-widest'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
style={{ backdropFilter: 'blur(3px)' }}
|
||||
className={`${show ? 'visible opacity-100 top-14' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-xl bg-white transition-all duration-300 z-20 absolute`}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='cursor-pointer hover:bg-blue-600 hover:text-white text-gray-900 tracking-widest transition-all duration-200 dark:border-gray-700 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// import Image from 'next/image'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Typed from 'typed.js'
|
||||
import CONFIG from '../config'
|
||||
import NavButtonGroup from './NavButtonGroup'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CONFIG from '../config'
|
||||
import NavButtonGroup from './NavButtonGroup'
|
||||
|
||||
let wrapperTop = 0
|
||||
|
||||
@@ -20,21 +20,29 @@ const Hero = props => {
|
||||
const scrollToWrapper = () => {
|
||||
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
const GREETING_WORDS = siteConfig('GREETING_WORDS').split(',')
|
||||
useEffect(() => {
|
||||
updateHeaderHeight()
|
||||
|
||||
if (!typed && window && document.getElementById('typed')) {
|
||||
changeType(
|
||||
new Typed('#typed', {
|
||||
strings: GREETING_WORDS,
|
||||
typeSpeed: 200,
|
||||
backSpeed: 100,
|
||||
backDelay: 400,
|
||||
showCursor: true,
|
||||
smartBackspace: true
|
||||
})
|
||||
)
|
||||
loadExternalResource(
|
||||
'https://cdn.jsdelivr.net/npm/typed.js@2.0.12',
|
||||
'js'
|
||||
).then(() => {
|
||||
if (window.Typed) {
|
||||
changeType(
|
||||
new window.Typed('#typed', {
|
||||
strings: GREETING_WORDS,
|
||||
typeSpeed: 200,
|
||||
backSpeed: 100,
|
||||
backDelay: 400,
|
||||
showCursor: true,
|
||||
smartBackspace: true
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('resize', updateHeaderHeight)
|
||||
@@ -51,33 +59,43 @@ const Hero = props => {
|
||||
}
|
||||
|
||||
return (
|
||||
<header
|
||||
id="header" style={{ zIndex: 1 }}
|
||||
className="w-full h-screen relative bg-black"
|
||||
>
|
||||
<header
|
||||
id='header'
|
||||
style={{ zIndex: 1 }}
|
||||
className='w-full h-screen relative bg-black'>
|
||||
<div className='text-white absolute bottom-0 flex flex-col h-full items-center justify-center w-full '>
|
||||
{/* 站点标题 */}
|
||||
<div className='font-black text-4xl md:text-5xl shadow-text'>
|
||||
{siteConfig('TITLE')}
|
||||
</div>
|
||||
{/* 站点欢迎语 */}
|
||||
<div className='mt-2 h-12 items-center text-center font-medium shadow-text text-lg'>
|
||||
<span id='typed' />
|
||||
</div>
|
||||
|
||||
<div className="text-white absolute bottom-0 flex flex-col h-full items-center justify-center w-full ">
|
||||
{/* 站点标题 */}
|
||||
<div className='font-black text-4xl md:text-5xl shadow-text'>{siteConfig('TITLE')}</div>
|
||||
{/* 站点欢迎语 */}
|
||||
<div className='mt-2 h-12 items-center text-center font-medium shadow-text text-lg'>
|
||||
<span id='typed' />
|
||||
</div>
|
||||
{/* 首页导航大按钮 */}
|
||||
{siteConfig('HEXO_HOME_NAV_BUTTONS', null, CONFIG) && (
|
||||
<NavButtonGroup {...props} />
|
||||
)}
|
||||
|
||||
{/* 首页导航大按钮 */}
|
||||
{siteConfig('HEXO_HOME_NAV_BUTTONS', null, CONFIG) && <NavButtonGroup {...props} />}
|
||||
{/* 滚动按钮 */}
|
||||
<div
|
||||
onClick={scrollToWrapper}
|
||||
className='z-10 cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white'>
|
||||
<div className='opacity-70 animate-bounce text-xs'>
|
||||
{siteConfig('HEXO_SHOW_START_READING', null, CONFIG) &&
|
||||
locale.COMMON.START_READING}
|
||||
</div>
|
||||
<i className='opacity-70 animate-bounce fas fa-angle-down' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 滚动按钮 */}
|
||||
<div onClick={scrollToWrapper} className="z-10 cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white">
|
||||
<div className="opacity-70 animate-bounce text-xs">{siteConfig('HEXO_SHOW_START_READING', null, CONFIG) && locale.COMMON.START_READING}</div>
|
||||
<i className='opacity-70 animate-bounce fas fa-angle-down' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyImage id='header-cover' src={siteInfo?.pageCover}
|
||||
className={`header-cover w-full h-screen object-cover object-center ${siteConfig('HEXO_HOME_NAV_BACKGROUND_IMG_FIXED', null, CONFIG) ? 'fixed' : ''}`} />
|
||||
|
||||
</header>
|
||||
<LazyImage
|
||||
id='header-cover'
|
||||
src={siteInfo?.pageCover}
|
||||
className={`header-cover w-full h-screen object-cover object-center ${siteConfig('HEXO_HOME_NAV_BACKGROUND_IMG_FIXED', null, CONFIG) ? 'fixed' : ''}`}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import CONFIG from '../config'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const MenuGroupCard = (props) => {
|
||||
const MenuGroupCard = props => {
|
||||
const { postCount, categoryOptions, tagOptions } = props
|
||||
const { locale } = useGlobal()
|
||||
const archiveSlot = <div className='text-center'>{postCount}</div>
|
||||
const categorySlot = <div className='text-center'>{categoryOptions?.length}</div>
|
||||
const categorySlot = (
|
||||
<div className='text-center'>{categoryOptions?.length}</div>
|
||||
)
|
||||
const tagSlot = <div className='text-center'>{tagOptions?.length}</div>
|
||||
|
||||
const links = [
|
||||
{ name: locale.COMMON.ARTICLE, to: '/archive', slot: archiveSlot, show: siteConfig('HEXO_MENU_ARCHIVE', null, CONFIG) },
|
||||
{ name: locale.COMMON.CATEGORY, to: '/category', slot: categorySlot, show: siteConfig('HEXO_MENU_CATEGORY', null, CONFIG) },
|
||||
{ name: locale.COMMON.TAGS, to: '/tag', slot: tagSlot, show: siteConfig('HEXO_MENU_TAG', null, CONFIG) }
|
||||
{
|
||||
name: locale.COMMON.ARTICLE,
|
||||
to: '/archive',
|
||||
slot: archiveSlot,
|
||||
show: siteConfig('HEXO_MENU_ARCHIVE', null, CONFIG)
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.CATEGORY,
|
||||
to: '/category',
|
||||
slot: categorySlot,
|
||||
show: siteConfig('HEXO_MENU_CATEGORY', null, CONFIG)
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.TAGS,
|
||||
to: '/tag',
|
||||
slot: tagSlot,
|
||||
show: siteConfig('HEXO_MENU_TAG', null, CONFIG)
|
||||
}
|
||||
]
|
||||
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
@@ -23,29 +40,31 @@ const MenuGroupCard = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<nav id='nav' className='leading-8 flex justify-center dark:text-gray-200 w-full'>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<Link
|
||||
key={`${link.to}`}
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className={'py-1.5 my-1 px-2 duration-300 text-base justify-center items-center cursor-pointer'}>
|
||||
|
||||
<div className='w-full items-center justify-center hover:scale-105 duration-200 transform dark:hover:text-indigo-400 hover:text-indigo-600'>
|
||||
<div className='text-center'>{link.name}</div>
|
||||
<div className='text-center font-semibold'>{link.slot}</div>
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
<nav
|
||||
id='nav'
|
||||
className='leading-8 flex justify-center dark:text-gray-200 w-full'>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<Link
|
||||
key={`${link.to}`}
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.target}
|
||||
className={
|
||||
'py-1.5 my-1 px-2 duration-300 text-base justify-center items-center cursor-pointer'
|
||||
}>
|
||||
<div className='w-full items-center justify-center hover:scale-105 duration-200 transform dark:hover:text-indigo-400 hover:text-indigo-600'>
|
||||
<div className='text-center'>{link.name}</div>
|
||||
<div className='text-center font-semibold'>{link.slot}</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
export default MenuGroupCard
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -26,30 +26,55 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='w-full px-8 py-3 text-left dark:bg-hexo-black-gray' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' transition-all items-center duration-200'>{link?.icon && <i className={link.icon + ' mr-4'} />}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className='transition-all items-center duration-200'>{link?.icon && <i className={link.icon + ' mr-4'} />}{link?.name}</span>
|
||||
<i className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''} text-gray-400`}></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='w-full px-8 py-3 text-left dark:bg-hexo-black-gray'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='font-extralight flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className='transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''} text-gray-400`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='dark:bg-black dark:text-gray-200 text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>{link?.icon && <i className={sLink.icon + ' mr-2'} />} {sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='dark:bg-black dark:text-gray-200 text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
{link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,33 +9,51 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className=' menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className=" menu-link pl-2 pr-4 no-underline tracking-widest pb-1">
|
||||
{link?.icon && <i className={link?.icon}/>} {link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu && <>
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon}/>} {link?.name}
|
||||
<i className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul style={{ backdropFilter: 'blur(3px)' }} className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-md text-black dark:text-white bg-white dark:bg-black transition-all duration-300 z-20 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='cursor-pointer hover:bg-indigo-300 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{hasSubMenu && (
|
||||
<>
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i
|
||||
className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
style={{ backdropFilter: 'blur(3px)' }}
|
||||
className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-md text-black dark:text-white bg-white dark:bg-black transition-all duration-300 z-20 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='cursor-pointer hover:bg-indigo-300 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,39 +1,42 @@
|
||||
import CONFIG from './config'
|
||||
import { createContext, useContext, useEffect, useRef } from 'react'
|
||||
import Footer from './components/Footer'
|
||||
import SideRight from './components/SideRight'
|
||||
import TopNav from './components/TopNav'
|
||||
import Comment from '@/components/Comment'
|
||||
import replaceSearchResult from '@/components/Mark'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import Hero from './components/Hero'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import Card from './components/Card'
|
||||
import RightFloatArea from './components/RightFloatArea'
|
||||
import SearchNav from './components/SearchNav'
|
||||
import BlogPostArchive from './components/BlogPostArchive'
|
||||
import { ArticleLock } from './components/ArticleLock'
|
||||
import PostHeader from './components/PostHeader'
|
||||
import JumpToCommentButton from './components/JumpToCommentButton'
|
||||
import TocDrawer from './components/TocDrawer'
|
||||
import TocDrawerButton from './components/TocDrawerButton'
|
||||
import Comment from '@/components/Comment'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import { createContext, useContext, useEffect, useRef } from 'react'
|
||||
import ArticleAdjacent from './components/ArticleAdjacent'
|
||||
import ArticleCopyright from './components/ArticleCopyright'
|
||||
import { ArticleLock } from './components/ArticleLock'
|
||||
import ArticleRecommend from './components/ArticleRecommend'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import TagItemMini from './components/TagItemMini'
|
||||
import Link from 'next/link'
|
||||
import BlogPostArchive from './components/BlogPostArchive'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import Card from './components/Card'
|
||||
import Footer from './components/Footer'
|
||||
import Hero from './components/Hero'
|
||||
import JumpToCommentButton from './components/JumpToCommentButton'
|
||||
import PostHeader from './components/PostHeader'
|
||||
import RightFloatArea from './components/RightFloatArea'
|
||||
import SearchNav from './components/SearchNav'
|
||||
import SideRight from './components/SideRight'
|
||||
import SlotBar from './components/SlotBar'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import TagItemMini from './components/TagItemMini'
|
||||
import TocDrawer from './components/TocDrawer'
|
||||
import TocDrawerButton from './components/TocDrawerButton'
|
||||
import TopNav from './components/TopNav'
|
||||
import CONFIG from './config'
|
||||
import { Style } from './style'
|
||||
import replaceSearchResult from '@/components/Mark'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const AlgoliaSearchModal = dynamic(() => import('@/components/AlgoliaSearchModal'), { ssr: false })
|
||||
const AlgoliaSearchModal = dynamic(
|
||||
() => import('@/components/AlgoliaSearchModal'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
// 主题全局状态
|
||||
const ThemeGlobalHexo = createContext()
|
||||
@@ -50,93 +53,110 @@ const LayoutBase = props => {
|
||||
const { onLoading, fullWidth } = useGlobal()
|
||||
|
||||
const router = useRouter()
|
||||
const headerSlot = post
|
||||
? <PostHeader {...props} />
|
||||
: (router.route === '/' && siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG)
|
||||
? <Hero {...props} />
|
||||
: null)
|
||||
const headerSlot = post ? (
|
||||
<PostHeader {...props} />
|
||||
) : router.route === '/' &&
|
||||
siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? (
|
||||
<Hero {...props} />
|
||||
) : null
|
||||
|
||||
const drawerRight = useRef(null)
|
||||
const tocRef = isBrowser ? document.getElementById('article-wrapper') : null
|
||||
|
||||
const floatSlot = <>
|
||||
{post?.toc?.length > 1 && <div className="block lg:hidden">
|
||||
<TocDrawerButton
|
||||
onClick={() => {
|
||||
drawerRight?.current?.handleSwitchVisible()
|
||||
}}
|
||||
/>
|
||||
</div>}
|
||||
<JumpToCommentButton />
|
||||
const floatSlot = (
|
||||
<>
|
||||
{post?.toc?.length > 1 && (
|
||||
<div className='block lg:hidden'>
|
||||
<TocDrawerButton
|
||||
onClick={() => {
|
||||
drawerRight?.current?.handleSwitchVisible()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{post && <JumpToCommentButton />}
|
||||
</>
|
||||
)
|
||||
|
||||
// Algolia搜索框
|
||||
const searchModal = useRef(null)
|
||||
|
||||
return (
|
||||
<ThemeGlobalHexo.Provider value={{ searchModal }}>
|
||||
<div id='theme-hexo' className={`${siteConfig('FONT_STYLE')} dark:bg-black scroll-smooth`}>
|
||||
<Style/>
|
||||
<div
|
||||
id='theme-hexo'
|
||||
className={`${siteConfig('FONT_STYLE')} dark:bg-black scroll-smooth`}>
|
||||
<Style />
|
||||
|
||||
{/* 顶部导航 */}
|
||||
<TopNav {...props} />
|
||||
{/* 顶部导航 */}
|
||||
<TopNav {...props} />
|
||||
|
||||
{/* 顶部嵌入 */}
|
||||
<Transition
|
||||
{/* 顶部嵌入 */}
|
||||
<Transition
|
||||
show={!onLoading}
|
||||
appear={true}
|
||||
enter='transition ease-in-out duration-700 transform order-first'
|
||||
enterFrom='opacity-0 -translate-y-16'
|
||||
enterTo='opacity-100'
|
||||
leave='transition ease-in-out duration-300 transform'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0 translate-y-16'
|
||||
unmount={false}>
|
||||
{headerSlot}
|
||||
</Transition>
|
||||
|
||||
{/* 主区块 */}
|
||||
<main
|
||||
id='wrapper'
|
||||
className={`${siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? '' : 'pt-16'} bg-hexo-background-gray dark:bg-black w-full py-8 md:px-8 lg:px-24 min-h-screen relative`}>
|
||||
<div
|
||||
id='container-inner'
|
||||
className={
|
||||
(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE'))
|
||||
? 'flex-row-reverse'
|
||||
: '') +
|
||||
' w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10'
|
||||
}>
|
||||
<div
|
||||
className={`${className || ''} w-full ${fullWidth ? '' : 'max-w-4xl'} h-full overflow-hidden`}>
|
||||
<Transition
|
||||
show={!onLoading}
|
||||
appear={true}
|
||||
enter="transition ease-in-out duration-700 transform order-first"
|
||||
enterFrom="opacity-0 -translate-y-16"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in-out duration-300 transform"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0 translate-y-16"
|
||||
unmount={false}
|
||||
>
|
||||
{headerSlot}
|
||||
</Transition>
|
||||
enter='transition ease-in-out duration-700 transform order-first'
|
||||
enterFrom='opacity-0 translate-y-16'
|
||||
enterTo='opacity-100'
|
||||
leave='transition ease-in-out duration-300 transform'
|
||||
leaveFrom='opacity-100 translate-y-0'
|
||||
leaveTo='opacity-0 -translate-y-16'
|
||||
unmount={false}>
|
||||
{/* 主区上部嵌入 */}
|
||||
{slotTop}
|
||||
|
||||
{/* 主区块 */}
|
||||
<main id="wrapper" className={`${siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? '' : 'pt-16'} bg-hexo-background-gray dark:bg-black w-full py-8 md:px-8 lg:px-24 min-h-screen relative`}>
|
||||
<div id="container-inner" className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + ' w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10'} >
|
||||
<div className={`${className || ''} w-full ${fullWidth ? '' : 'max-w-4xl'} h-full overflow-hidden`}>
|
||||
|
||||
<Transition
|
||||
show={!onLoading}
|
||||
appear={true}
|
||||
enter="transition ease-in-out duration-700 transform order-first"
|
||||
enterFrom="opacity-0 translate-y-16"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in-out duration-300 transform"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 -translate-y-16"
|
||||
unmount={false}
|
||||
>
|
||||
{/* 主区上部嵌入 */}
|
||||
{slotTop}
|
||||
|
||||
{children}
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
{/* 右侧栏 */}
|
||||
<SideRight {...props} className={`space-y-4 lg:w-80 pt-4 ${post ? 'lg:pt-0' : 'lg:pt-4'}`} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className='block lg:hidden'>
|
||||
<TocDrawer post={post} cRef={drawerRight} targetRef={tocRef} />
|
||||
{children}
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
{/* 悬浮菜单 */}
|
||||
<RightFloatArea floatSlot={floatSlot} />
|
||||
{/* 右侧栏 */}
|
||||
<SideRight
|
||||
{...props}
|
||||
className={`space-y-4 lg:w-80 pt-4 ${post ? 'lg:pt-0' : 'lg:pt-4'}`}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* 全文搜索 */}
|
||||
<AlgoliaSearchModal cRef={searchModal} {...props}/>
|
||||
|
||||
{/* 页脚 */}
|
||||
<Footer title={siteConfig('TITLE') } />
|
||||
<div className='block lg:hidden'>
|
||||
<TocDrawer post={post} cRef={drawerRight} targetRef={tocRef} />
|
||||
</div>
|
||||
|
||||
{/* 悬浮菜单 */}
|
||||
<RightFloatArea floatSlot={floatSlot} />
|
||||
|
||||
{/* 全文搜索 */}
|
||||
<AlgoliaSearchModal cRef={searchModal} {...props} />
|
||||
|
||||
{/* 页脚 */}
|
||||
<Footer title={siteConfig('TITLE')} />
|
||||
</div>
|
||||
</ThemeGlobalHexo.Provider>
|
||||
)
|
||||
}
|
||||
@@ -147,7 +167,7 @@ const LayoutBase = props => {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutIndex = (props) => {
|
||||
const LayoutIndex = props => {
|
||||
return <LayoutPostList {...props} className='pt-8' />
|
||||
}
|
||||
|
||||
@@ -156,11 +176,17 @@ const LayoutIndex = (props) => {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutPostList = (props) => {
|
||||
return <div className='pt-8'>
|
||||
<SlotBar {...props} />
|
||||
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
const LayoutPostList = props => {
|
||||
return (
|
||||
<div className='pt-8'>
|
||||
<SlotBar {...props} />
|
||||
{siteConfig('POST_LIST_STYLE') === 'page' ? (
|
||||
<BlogPostListPage {...props} />
|
||||
) : (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,11 +213,20 @@ const LayoutSearch = props => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='pt-8'>
|
||||
{!currentSearch
|
||||
? <SearchNav {...props} />
|
||||
: <div id="posts-wrapper"> {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} </div>}
|
||||
<div className='pt-8'>
|
||||
{!currentSearch ? (
|
||||
<SearchNav {...props} />
|
||||
) : (
|
||||
<div id='posts-wrapper'>
|
||||
{' '}
|
||||
{siteConfig('POST_LIST_STYLE') === 'page' ? (
|
||||
<BlogPostListPage {...props} />
|
||||
) : (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}{' '}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -200,21 +235,23 @@ const LayoutSearch = props => {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutArchive = (props) => {
|
||||
const LayoutArchive = props => {
|
||||
const { archivePosts } = props
|
||||
return <div className='pt-8'>
|
||||
<Card className='w-full'>
|
||||
<div className="mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray">
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive
|
||||
key={archiveTitle}
|
||||
posts={archivePosts[archiveTitle]}
|
||||
archiveTitle={archiveTitle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
return (
|
||||
<div className='pt-8'>
|
||||
<Card className='w-full'>
|
||||
<div className='mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray'>
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive
|
||||
key={archiveTitle}
|
||||
posts={archivePosts[archiveTitle]}
|
||||
archiveTitle={archiveTitle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,51 +265,60 @@ const LayoutSlug = props => {
|
||||
useEffect(() => {
|
||||
// 404
|
||||
if (!post) {
|
||||
setTimeout(() => {
|
||||
if (isBrowser) {
|
||||
const article = document.getElementById('notion-article')
|
||||
if (!article) {
|
||||
router.push('/404').then(() => {
|
||||
console.warn('找不到页面', router.asPath)
|
||||
})
|
||||
setTimeout(
|
||||
() => {
|
||||
if (isBrowser) {
|
||||
const article = document.getElementById('notion-article')
|
||||
if (!article) {
|
||||
router.push('/404').then(() => {
|
||||
console.warn('找不到页面', router.asPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}, siteConfig('POST_WAITING_TIME_FOR_404') * 1000)
|
||||
},
|
||||
siteConfig('POST_WAITING_TIME_FOR_404') * 1000
|
||||
)
|
||||
}
|
||||
}, [post])
|
||||
return (
|
||||
<>
|
||||
<div className="w-full lg:hover:shadow lg:border rounded-t-xl lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black article">
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
<>
|
||||
<div className='w-full lg:hover:shadow lg:border rounded-t-xl lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black article'>
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && <div id="article-wrapper" className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 ">
|
||||
{!lock && (
|
||||
<div
|
||||
id='article-wrapper'
|
||||
className='overflow-x-auto flex-grow mx-auto md:w-full md:px-5 '>
|
||||
<article
|
||||
itemScope
|
||||
itemType='https://schema.org/Movie'
|
||||
className='subpixel-antialiased overflow-y-hidden'>
|
||||
{/* Notion文章主体 */}
|
||||
<section className='px-5 justify-center mx-auto max-w-2xl lg:max-w-full'>
|
||||
{post && <NotionPage post={post} />}
|
||||
</section>
|
||||
|
||||
<article itemScope itemType="https://schema.org/Movie" className="subpixel-antialiased overflow-y-hidden" >
|
||||
{/* Notion文章主体 */}
|
||||
<section className='px-5 justify-center mx-auto max-w-2xl lg:max-w-full'>
|
||||
{post && <NotionPage post={post} />}
|
||||
</section>
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{post?.type === 'Post' && (
|
||||
<>
|
||||
<ArticleCopyright {...props} />
|
||||
<ArticleRecommend {...props} />
|
||||
<ArticleAdjacent {...props} />
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{post?.type === 'Post' && <>
|
||||
<ArticleCopyright {...props} />
|
||||
<ArticleRecommend {...props} />
|
||||
<ArticleAdjacent {...props} />
|
||||
</>}
|
||||
<div className='pt-4 border-dashed'></div>
|
||||
|
||||
</article>
|
||||
|
||||
<div className='pt-4 border-dashed'></div>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="duration-200 overflow-x-auto bg-white dark:bg-hexo-black-gray px-3">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>}
|
||||
{/* 评论互动 */}
|
||||
<div className='duration-200 overflow-x-auto bg-white dark:bg-hexo-black-gray px-3'>
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -297,18 +343,18 @@ const Layout404 = props => {
|
||||
}, 3000)
|
||||
})
|
||||
return (
|
||||
<>
|
||||
<div className="text-black w-full h-screen text-center justify-center content-center items-center flex flex-col">
|
||||
<div className="dark:text-gray-200">
|
||||
<h2 className="inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top">
|
||||
404
|
||||
</h2>
|
||||
<div className="inline-block text-left h-32 leading-10 items-center">
|
||||
<h2 className="m-0 p-0">页面未找到</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
<div className='text-black w-full h-screen text-center justify-center content-center items-center flex flex-col'>
|
||||
<div className='dark:text-gray-200'>
|
||||
<h2 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'>
|
||||
404
|
||||
</h2>
|
||||
<div className='inline-block text-left h-32 leading-10 items-center'>
|
||||
<h2 className='m-0 p-0'>页面未找到</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -321,24 +367,32 @@ const LayoutCategoryIndex = props => {
|
||||
const { categoryOptions } = props
|
||||
const { locale } = useGlobal()
|
||||
return (
|
||||
<div className='mt-8'>
|
||||
<Card className="w-full min-h-screen">
|
||||
<div className="dark:text-gray-200 mb-5 mx-3">
|
||||
<i className="mr-4 fas fa-th" /> {locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id="category-list" className="duration-200 flex flex-wrap mx-8">
|
||||
{categoryOptions?.map(category => {
|
||||
return (
|
||||
<Link key={category.name} href={`/category/${category.name}`} passHref legacyBehavior>
|
||||
<div className={' duration-300 dark:hover:text-white px-5 cursor-pointer py-2 hover:text-indigo-400'}>
|
||||
<i className="mr-4 fas fa-folder" /> {category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
<div className='mt-8'>
|
||||
<Card className='w-full min-h-screen'>
|
||||
<div className='dark:text-gray-200 mb-5 mx-3'>
|
||||
<i className='mr-4 fas fa-th' /> {locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id='category-list' className='duration-200 flex flex-wrap mx-8'>
|
||||
{categoryOptions?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
href={`/category/${category.name}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
<div
|
||||
className={
|
||||
' duration-300 dark:hover:text-white px-5 cursor-pointer py-2 hover:text-indigo-400'
|
||||
}>
|
||||
<i className='mr-4 fas fa-folder' /> {category.name}(
|
||||
{category.count})
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -351,30 +405,32 @@ const LayoutTagIndex = props => {
|
||||
const { tagOptions } = props
|
||||
const { locale } = useGlobal()
|
||||
return (
|
||||
<div className='mt-8'>
|
||||
<Card className='w-full'>
|
||||
<div className="dark:text-gray-200 mb-5 ml-4">
|
||||
<i className="mr-4 fas fa-tag" /> {locale.COMMON.TAGS}:
|
||||
</div>
|
||||
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
|
||||
{tagOptions.map(tag => <div key={tag.name} className="p-2">
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
</div>)}
|
||||
</div>
|
||||
</Card>
|
||||
<div className='mt-8'>
|
||||
<Card className='w-full'>
|
||||
<div className='dark:text-gray-200 mb-5 ml-4'>
|
||||
<i className='mr-4 fas fa-tag' /> {locale.COMMON.TAGS}:
|
||||
</div>
|
||||
<div id='tags-list' className='duration-200 flex flex-wrap ml-8'>
|
||||
{tagOptions.map(tag => (
|
||||
<div key={tag.name} className='p-2'>
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
CONFIG as THEME_CONFIG,
|
||||
LayoutBase,
|
||||
LayoutIndex,
|
||||
LayoutSearch,
|
||||
LayoutArchive,
|
||||
LayoutSlug,
|
||||
Layout404,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutTagIndex
|
||||
LayoutSearch,
|
||||
LayoutSlug,
|
||||
LayoutTagIndex,
|
||||
CONFIG as THEME_CONFIG
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// import Image from 'next/image'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Typed from 'typed.js'
|
||||
import CONFIG from '../config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CONFIG from '../config'
|
||||
|
||||
let wrapperTop = 0
|
||||
|
||||
@@ -21,16 +21,23 @@ const Hero = props => {
|
||||
useEffect(() => {
|
||||
updateHeaderHeight()
|
||||
if (!typed && window && document.getElementById('typed')) {
|
||||
changeType(
|
||||
new Typed('#typed', {
|
||||
strings: GREETING_WORDS,
|
||||
typeSpeed: 200,
|
||||
backSpeed: 100,
|
||||
backDelay: 400,
|
||||
showCursor: true,
|
||||
smartBackspace: true
|
||||
})
|
||||
)
|
||||
loadExternalResource(
|
||||
'https://cdn.jsdelivr.net/npm/typed.js@2.0.12',
|
||||
'js'
|
||||
).then(() => {
|
||||
if (window.Typed) {
|
||||
changeType(
|
||||
new window.Typed('#typed', {
|
||||
strings: GREETING_WORDS,
|
||||
typeSpeed: 200,
|
||||
backSpeed: 100,
|
||||
backDelay: 400,
|
||||
showCursor: true,
|
||||
smartBackspace: true
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('resize', updateHeaderHeight)
|
||||
@@ -47,29 +54,40 @@ const Hero = props => {
|
||||
}
|
||||
|
||||
return (
|
||||
<header
|
||||
id="header" style={{ zIndex: 1 }}
|
||||
className=" w-full h-screen relative bg-black"
|
||||
>
|
||||
<header
|
||||
id='header'
|
||||
style={{ zIndex: 1 }}
|
||||
className=' w-full h-screen relative bg-black'>
|
||||
<div className='text-white absolute flex flex-col h-full items-center justify-center w-full '>
|
||||
{/* 站点标题 */}
|
||||
<div className='text-4xl md:text-5xl shadow-text'>
|
||||
{siteConfig('TITLE')}
|
||||
</div>
|
||||
{/* 站点欢迎语 */}
|
||||
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
|
||||
<span id='typed' />
|
||||
</div>
|
||||
{/* 滚动按钮 */}
|
||||
<div
|
||||
onClick={() => {
|
||||
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
|
||||
}}
|
||||
className='mt-12 border cursor-pointer w-40 text-center pt-4 pb-3 text-md text-white hover:bg-orange-600 duration-300 rounded-3xl z-40'>
|
||||
<i className='animate-bounce fas fa-angle-double-down' />{' '}
|
||||
<span>
|
||||
{siteConfig('MATERY_SHOW_START_READING', null, CONFIG) &&
|
||||
locale.COMMON.START_READING}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-white absolute flex flex-col h-full items-center justify-center w-full ">
|
||||
{/* 站点标题 */}
|
||||
<div className='text-4xl md:text-5xl shadow-text'>{siteConfig('TITLE')}</div>
|
||||
{/* 站点欢迎语 */}
|
||||
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
|
||||
<span id='typed' />
|
||||
</div>
|
||||
{/* 滚动按钮 */}
|
||||
<div onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
|
||||
className="mt-12 border cursor-pointer w-40 text-center pt-4 pb-3 text-md text-white hover:bg-orange-600 duration-300 rounded-3xl z-40">
|
||||
<i className='animate-bounce fas fa-angle-double-down' /> <span>{siteConfig('MATERY_SHOW_START_READING', null, CONFIG) && locale.COMMON.START_READING}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyImage priority={true} id='header-cover'src={siteInfo?.pageCover}
|
||||
className={`header-cover object-center w-full h-screen object-cover ${siteConfig('MATERY_HOME_NAV_BACKGROUND_IMG_FIXED', null, CONFIG) ? 'fixed' : ''}`} />
|
||||
|
||||
</header>
|
||||
<LazyImage
|
||||
priority={true}
|
||||
id='header-cover'
|
||||
src={siteInfo?.pageCover}
|
||||
className={`header-cover object-center w-full h-screen object-cover ${siteConfig('MATERY_HOME_NAV_BACKGROUND_IMG_FIXED', null, CONFIG) ? 'fixed' : ''}`}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import CONFIG from '../config'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const MenuGroupCard = (props) => {
|
||||
const MenuGroupCard = props => {
|
||||
const { postCount, categories, tags } = props
|
||||
const { locale } = useGlobal()
|
||||
const archiveSlot = <div className='text-center'>{postCount}</div>
|
||||
@@ -11,35 +11,50 @@ const MenuGroupCard = (props) => {
|
||||
const tagSlot = <div className='text-center'>{tags?.length}</div>
|
||||
|
||||
const links = [
|
||||
{ name: locale.COMMON.ARTICLE, to: '/archive', slot: archiveSlot, show: siteConfig('MATERY_MENU_ARCHIVE', null, CONFIG) },
|
||||
{ name: locale.COMMON.CATEGORY, to: '/category', slot: categorySlot, show: siteConfig('MATERY_MENU_CATEGORY', null, CONFIG) },
|
||||
{ name: locale.COMMON.TAGS, to: '/tag', slot: tagSlot, show: siteConfig('MATERY_MENU_TAG', null, CONFIG) }
|
||||
{
|
||||
name: locale.COMMON.ARTICLE,
|
||||
to: '/archive',
|
||||
slot: archiveSlot,
|
||||
show: siteConfig('MATERY_MENU_ARCHIVE', null, CONFIG)
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.CATEGORY,
|
||||
to: '/category',
|
||||
slot: categorySlot,
|
||||
show: siteConfig('MATERY_MENU_CATEGORY', null, CONFIG)
|
||||
},
|
||||
{
|
||||
name: locale.COMMON.TAGS,
|
||||
to: '/tag',
|
||||
slot: tagSlot,
|
||||
show: siteConfig('MATERY_MENU_TAG', null, CONFIG)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<nav id='nav' className='leading-8 flex justify-center w-full'>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<Link
|
||||
key={`${link.to}`}
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className={'py-1.5 my-1 px-2 duration-300 text-base justify-center items-center cursor-pointer'}>
|
||||
|
||||
<div className='w-full items-center justify-center hover:scale-105 duration-200 transform dark:hover:text-indigo-400 hover:text-indigo-600'>
|
||||
<div className='text-center'>{link.name}</div>
|
||||
<div className='text-center font-semibold'>{link.slot}</div>
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
return (
|
||||
<Link
|
||||
key={`${link.to}`}
|
||||
title={link.to}
|
||||
href={link.to}
|
||||
target={link?.target}
|
||||
className={
|
||||
'py-1.5 my-1 px-2 duration-300 text-base justify-center items-center cursor-pointer'
|
||||
}>
|
||||
<div className='w-full items-center justify-center hover:scale-105 duration-200 transform dark:hover:text-indigo-400 hover:text-indigo-600'>
|
||||
<div className='text-center'>{link.name}</div>
|
||||
<div className='text-center font-semibold'>{link.slot}</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
export default MenuGroupCard
|
||||
|
||||
@@ -27,35 +27,63 @@ export const MenuItemCollapse = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
return <>
|
||||
<div onClick={toggleShow} className={'py-2 px-5 duration-300 text-base justify-between hover:bg-indigo-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-indigo-500 text-white ' : ' text-black dark:text-white ')}>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={toggleShow}
|
||||
className={
|
||||
'py-2 px-5 duration-300 text-base justify-between hover:bg-indigo-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-indigo-500 text-white '
|
||||
: ' text-black dark:text-white ')
|
||||
}>
|
||||
{!hasSubMenu && (
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
<div className='my-auto items-center justify-between flex '>
|
||||
{link.icon && (
|
||||
<i className={`${link.icon} w-4 mr-6 text-center`} />
|
||||
)}
|
||||
<div>{link.name}</div>
|
||||
</div>
|
||||
{link.slot}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu && <Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<div className='my-auto items-center justify-between flex '>
|
||||
{link.icon && <i className={`${link.icon} w-4 mr-6 text-center`} />}
|
||||
<div >{link.name}</div>
|
||||
</div>
|
||||
{link.slot}
|
||||
</Link>}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='my-auto items-center w-full justify-between flex '>
|
||||
<div className=''>
|
||||
<i className={`${link.icon} w-4 mr-6 text-center`} />
|
||||
{link?.name}
|
||||
</div>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''}`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasSubMenu && <div onClick={hasSubMenu ? toggleOpenSubMenu : null} className='my-auto items-center w-full justify-between flex '>
|
||||
<div className=''><i className={`${link.icon} w-4 mr-6 text-center`} />{link?.name}</div>
|
||||
<i className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''}`}></i>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='cursor-pointer whitespace-nowrap dark:text-gray-200 w-full font-extralight dark:bg-black text-left px-5 justify-start bg-gray-100 hover:bg-indigo-700 hover:text-white dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm'><i className={`${sLink.icon} w-4 mr-3 text-center`} />{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='cursor-pointer whitespace-nowrap dark:text-gray-200 w-full font-extralight dark:bg-black text-left px-5 justify-start bg-gray-100 hover:bg-indigo-700 hover:text-white dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm'>
|
||||
<i className={`${sLink.icon} w-4 mr-3 text-center`} />
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,33 +9,51 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className=' menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className=" menu-link pl-2 pr-4 no-underline tracking-widest pb-1">
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu && <>
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul style={{ backdropFilter: 'blur(3px)' }} className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-md bg-white transition-all duration-300 z-20 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='cursor-pointer hover:bg-indigo-300 text-gray-900 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{hasSubMenu && (
|
||||
<>
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i
|
||||
className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
style={{ backdropFilter: 'blur(3px)' }}
|
||||
className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-md bg-white transition-all duration-300 z-20 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='cursor-pointer hover:bg-indigo-300 text-gray-900 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -21,7 +21,7 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
const toggleShow = () => {
|
||||
changeShow(!show)
|
||||
@@ -31,32 +31,67 @@ export const MenuItemCollapse = (props) => {
|
||||
changeIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={ (selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600') + ' px-5 w-full text-left duration-200 dark:bg-hexo-black-gray dark:border-black'} onClick={toggleShow} >
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600') +
|
||||
' px-5 w-full text-left duration-200 dark:bg-hexo-black-gray dark:border-black'
|
||||
}
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu && <Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
</Link>}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='py-2 font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest'>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
<div className='inline-flex items-center '>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="py-2 font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest">
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
<div className='inline-flex items-center '><i className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i></div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link?.subMenus?.map(sLink => {
|
||||
return <div key={sLink.id} className='
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link?.subMenus?.map(sLink => {
|
||||
return (
|
||||
<div
|
||||
key={sLink.id}
|
||||
className='
|
||||
not:last-child:border-b-0 border-b dark:border-gray-800 py-2 px-14 cursor-pointer hover:bg-gray-100 dark:text-gray-200
|
||||
font-extralight dark:bg-black text-left justify-start text-gray-600 bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<div><div className={`${sLink.icon} text-center w-3 mr-3 text-xs`} />{sLink.title}</div>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<div>
|
||||
<div
|
||||
className={`${sLink.icon} text-center w-3 mr-3 text-xs`}
|
||||
/>
|
||||
{sLink.title}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const MenuItemDrop = ({ link }) => {
|
||||
const [show, changeShow] = useState(false)
|
||||
@@ -12,39 +12,65 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
return <li className='cursor-pointer list-none items-center flex mx-2' onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<li
|
||||
className='cursor-pointer list-none items-center flex mx-2'
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
className={
|
||||
'px-3 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600')
|
||||
}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && (
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className={'px-3 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!hasSubMenu && (
|
||||
<div
|
||||
className={
|
||||
'px-3 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600')
|
||||
}>
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<div className={'px-3 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link?.subMenus?.map(sLink => {
|
||||
return (
|
||||
<li
|
||||
key={sLink.id}
|
||||
className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-xs font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link?.subMenus?.map(sLink => {
|
||||
return <li key={sLink.id} className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-xs font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,11 +28,13 @@ export const MenuItemCollapse = props => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='select-none w-full px-6 py-2 text-left ' onClick={toggleShow}>
|
||||
<div
|
||||
className='select-none w-full px-6 py-2 text-left '
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
target={link?.target}
|
||||
className='flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest'>
|
||||
<span className=' transition-all items-center duration-200'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
@@ -56,15 +58,19 @@ export const MenuItemCollapse = props => {
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange} className='rounded-xl'>
|
||||
<Collapse
|
||||
isOpen={isOpen}
|
||||
onHeightChange={props.onHeightChange}
|
||||
className='rounded-xl'>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='dark:text-gray-200 text-left px-3 justify-start tracking-widest transition-all duration-200 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
{link?.icon && <i className={sLink.icon + ' mr-2'} />} {sLink.title}
|
||||
{link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -12,26 +12,23 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}
|
||||
>
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="select-none menu-link pl-2 pr-4 no-underline tracking-widest pb-1"
|
||||
>
|
||||
target={link?.target}
|
||||
className='select-none menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className="px-2 fa fa-angle-down"></i>}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasSubMenu && (
|
||||
<>
|
||||
<div className="cursor-pointer menu-link pl-2 pr-4 no-underline tracking-widest pb-1">
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i
|
||||
className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}
|
||||
></i>
|
||||
className={`px-2 fa fa-angle-down duration-300 ${show ? 'rotate-180' : 'rotate-0'}`}></i>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -40,19 +37,14 @@ export const MenuItemDrop = ({ link }) => {
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
style={{ backdropFilter: 'blur(3px)' }}
|
||||
className={`${show ? 'visible opacity-100 top-14' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-md text-black dark:text-white bg-white dark:bg-black transition-all duration-300 z-30 absolute block `}
|
||||
>
|
||||
className={`${show ? 'visible opacity-100 top-14' : 'invisible opacity-0 top-20'} drop-shadow-md overflow-hidden rounded-md text-black dark:text-white bg-white dark:bg-black transition-all duration-300 z-30 absolute block `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className="cursor-pointer text-start dark:bg-hexo-black-gray dark:hover:bg-gray-300 hover:bg-gray-300 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3"
|
||||
>
|
||||
<Link
|
||||
href={sLink.to}
|
||||
target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
>
|
||||
<span className="text-sm">
|
||||
className='cursor-pointer text-start dark:bg-hexo-black-gray dark:hover:bg-gray-300 hover:bg-gray-300 hover:text-black tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -21,7 +21,7 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
const toggleShow = () => {
|
||||
changeShow(!show)
|
||||
@@ -31,32 +31,67 @@ export const MenuItemCollapse = (props) => {
|
||||
changeIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={ 'text-black' + (selected ? 'text-white hover:text-white' : 'hover:text-gray-600') + ' px-7 w-full text-left duration-200 dark:border-black'} onClick={toggleShow} >
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'text-black' +
|
||||
(selected ? 'text-white hover:text-white' : 'hover:text-gray-600') +
|
||||
' px-7 w-full text-left duration-200 dark:border-black'
|
||||
}
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu && <Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='py-2 w-full my-auto items-center justify-between flex '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
</Link>}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='py-2 flex justify-between cursor-pointer dark:text-gray-400 dark:hover:text-white font-bold no-underline tracking-widest'>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-2`} />
|
||||
{link.name}
|
||||
</div>
|
||||
<div className='inline-flex items-center '>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasSubMenu && <div onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="py-2 flex justify-between cursor-pointer dark:text-gray-400 dark:hover:text-white font-bold no-underline tracking-widest">
|
||||
<div><div className={`${link.icon} text-center w-4 mr-2`} />{link.name}</div>
|
||||
<div className='inline-flex items-center '><i className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i></div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return <div key={index} className='
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='
|
||||
py-2 px-14 cursor-pointer hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white font-bold
|
||||
dark:bg-black text-left justify-start text-gray-600 bg-gray-50 bg-opacity-20 dark:hover:bg-gray-600 tracking-widest transition-all duration-200'>
|
||||
{/* <Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}> */}
|
||||
<a href={`/#${sLink.title}`} target={'_self'}>
|
||||
<div><div className={`${sLink?.icon ? sLink?.icon : 'fas fa-hashtag'} text-center w-3 mr-2 text-xs`} />{sLink.title}</div>
|
||||
</a>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* <Link href={sLink.to} target={link?.target}> */}
|
||||
<a href={`/#${sLink.title}`} target={'_self'}>
|
||||
<div>
|
||||
<div
|
||||
className={`${sLink?.icon ? sLink?.icon : 'fas fa-hashtag'} text-center w-3 mr-2 text-xs`}
|
||||
/>
|
||||
{sLink.title}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const MenuItemDrop = ({ link }) => {
|
||||
const [show, changeShow] = useState(false)
|
||||
@@ -12,39 +12,65 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
const selected = router.pathname === link.to || router.asPath === link.to
|
||||
|
||||
return <li className='cursor-pointer list-none items-center flex mx-2' onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<li
|
||||
className='cursor-pointer list-none items-center flex mx-2'
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
className={
|
||||
'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600')
|
||||
}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && (
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className={'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{hasSubMenu && <i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!hasSubMenu && (
|
||||
<div
|
||||
className={
|
||||
'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected
|
||||
? 'bg-green-600 text-white hover:text-white'
|
||||
: 'hover:text-green-600')
|
||||
}>
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<div className={'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-xs font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link?.subMenus?.map((sLink, index) => {
|
||||
return <li key={index} className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-xs font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -22,32 +22,64 @@ export const MenuItemCollapse = (props) => {
|
||||
changeIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='px-5 py-2 w-full text-left duration-200 hover:bg-gray-700 hover:text-white not:last-child:border-b-0 border-b dark:bg-hexo-black-gray dark:border-black' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className='w-full my-auto items-center justify-between flex dark:text-gray-200 '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
</Link>}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='px-5 py-2 w-full text-left duration-200 hover:bg-gray-700 hover:text-white not:last-child:border-b-0 border-b dark:bg-hexo-black-gray dark:border-black'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='w-full my-auto items-center justify-between flex dark:text-gray-200 '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest">
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
<div className='inline-flex items-center '><i className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i></div>
|
||||
</div>}
|
||||
</div>
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='font-extralight flex justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest'>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
<div className='inline-flex items-center '>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right transition-all duration-200 ${isOpen ? 'rotate-90' : ''}`}></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='whitespace-nowrap dark:text-gray-200
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='whitespace-nowrap dark:text-gray-200
|
||||
not:last-child:border-b-0 border-b dark:border-gray-800 py-2 px-14 cursor-pointer hover:bg-gray-100
|
||||
font-extralight dark:bg-black text-left justify-start text-gray-600 bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<div>{sLink.icon && <div className={`${sLink.icon} text-center w-3 mr-3 text-xs`} />}{sLink.title}</div>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<div>
|
||||
{sLink.icon && (
|
||||
<div
|
||||
className={`${sLink.icon} text-center w-3 mr-3 text-xs`}
|
||||
/>
|
||||
)}
|
||||
{sLink.title}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,34 +5,62 @@ export const MenuItemDrop = ({ link }) => {
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
|
||||
return <li onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)}
|
||||
className='relative py-1.5 px-5 duration-300 text-base justify-between hover:bg-gray-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center '>
|
||||
return (
|
||||
<li
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}
|
||||
className='relative py-1.5 px-5 duration-300 text-base justify-between hover:bg-gray-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center '>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
{link.slot}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='w-full my-auto items-center justify-between flex ' >
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
{link.slot}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className='w-full my-auto items-center justify-between flex '>
|
||||
<div><div className={`${link.icon} text-center w-4 mr-4`} />{link.name}</div>
|
||||
{link.slot}
|
||||
{hasSubMenu && <div className='text-right'><i className={`px-2 fas fa-chevron-right duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i></div>}
|
||||
</div>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 left-56' : 'invisible opacity-0 left-40'} whitespace-nowrap absolute right-0 top-0 w-full border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 drop-shadow-lg `}>
|
||||
{link?.subMenus?.map(sLink => {
|
||||
return <li key={sLink.id} >
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className='my-auto h-9 px-2 items-center justify-start flex not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 '>
|
||||
{sLink.icon && <i className={`${sLink.icon} w-4 text-center`} />}
|
||||
<div className={'ml-4'}>{sLink.name}</div>
|
||||
{sLink.slot}
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{hasSubMenu && (
|
||||
<div className='w-full my-auto items-center justify-between flex '>
|
||||
<div>
|
||||
<div className={`${link.icon} text-center w-4 mr-4`} />
|
||||
{link.name}
|
||||
</div>
|
||||
{link.slot}
|
||||
{hasSubMenu && (
|
||||
<div className='text-right'>
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-right duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 left-56' : 'invisible opacity-0 left-40'} whitespace-nowrap absolute right-0 top-0 w-full border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 drop-shadow-lg `}>
|
||||
{link?.subMenus?.map(sLink => {
|
||||
return (
|
||||
<li key={sLink.id}>
|
||||
<Link
|
||||
href={sLink.to}
|
||||
target={link?.target}
|
||||
className='my-auto h-9 px-2 items-center justify-start flex not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 '>
|
||||
{sLink.icon && (
|
||||
<i className={`${sLink.icon} w-4 text-center`} />
|
||||
)}
|
||||
<div className={'ml-4'}>{sLink.name}</div>
|
||||
{sLink.slot}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -26,30 +26,59 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='w-full px-4 py-2 text-left dark:bg-hexo-black-gray dark:border-black' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='w-full px-4 py-2 text-left dark:bg-hexo-black-gray dark:border-black'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='font-extralight dark:bg-black text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='font-extralight dark:bg-black text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,35 +11,49 @@ export const MenuItemDrop = ({ link }) => {
|
||||
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
|
||||
return <li className='mx-3 my-2' >
|
||||
<div className='cursor-pointer ' onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu &&
|
||||
<div className="block text-black dark:text-gray-50 nav" >
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} >
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
return (
|
||||
<li className='mx-3 my-2'>
|
||||
<div
|
||||
className='cursor-pointer '
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<div className='block text-black dark:text-gray-50 nav'>
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSubMenu && (
|
||||
<div className='block text-black dark:text-gray-50 nav'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className='block text-black dark:text-gray-50 nav'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 top-12 ' : 'invisible opacity-0 top-10 '} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='not:last-child:border-b-0 border-b text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} >
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -26,30 +26,59 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='w-full px-4 py-2 text-left dark:bg-hexo-black-gray dark:border-black' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='w-full px-4 py-2 text-left dark:bg-hexo-black-gray dark:border-black'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='font-extralight flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className=' hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i className='px-2 fa fa-plus text-gray-400'></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='font-extralight dark:bg-black text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='font-extralight dark:bg-black text-left px-10 justify-start bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-xs'>{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,32 +9,47 @@ export const MenuItemDrop = ({ link }) => {
|
||||
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
|
||||
return <li className='cursor-pointer py-2 px-3' onMouseEnter={() => changeShow(true)} onMouseLeave={() => changeShow(false)}>
|
||||
{!hasSubMenu &&
|
||||
<div className="block text-black dark:text-gray-50 nav" >
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} >
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<li
|
||||
className='cursor-pointer py-2 px-3'
|
||||
onMouseEnter={() => changeShow(true)}
|
||||
onMouseLeave={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<div className='block text-black dark:text-gray-50 nav'>
|
||||
<Link href={link?.to} target={link?.target}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className='block text-black dark:text-gray-50 nav'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
}
|
||||
{hasSubMenu && (
|
||||
<div className='block text-black dark:text-gray-50 nav'>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 bottom-16 ' : 'invisible opacity-0 bottom-14'} border-gray-100 bg-white rounded-lg overflow-hidden dark:bg-black dark:border-gray-800 bg-opacity-60 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap font-extralight'>{link?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
|
||||
</li>
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 bottom-16 ' : 'invisible opacity-0 bottom-14'} border-gray-100 bg-white rounded-lg overflow-hidden dark:bg-black dark:border-gray-800 bg-opacity-60 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap font-extralight'>
|
||||
{link?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from 'react'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export const MenuItemCollapse = (props) => {
|
||||
export const MenuItemCollapse = props => {
|
||||
const { link } = props
|
||||
const [show, changeShow] = useState(false)
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
@@ -26,30 +26,67 @@ export const MenuItemCollapse = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='w-full px-8 py-3 text-left border-b dark:bg-hexo-black-gray dark:border-black' onClick={toggleShow} >
|
||||
{!hasSubMenu && <Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className="items-center flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className='text-blue-600 dark:text-blue-300 hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
</Link>}
|
||||
{hasSubMenu && <div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className="items-center flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
<span className='text-blue-600 dark:text-blue-300 hover:text-red-400 transition-all items-center duration-200'>{link?.icon && <span className='mr-2'><i className={link.icon}/></span>}{link?.name}</span>
|
||||
<i className={`px-2 fa fa-plus transition-all duration-200 ${isOpen && 'rotate-45'} text-gray-400`}></i>
|
||||
</div>}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='w-full px-8 py-3 text-left border-b dark:bg-hexo-black-gray dark:border-black'
|
||||
onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className='items-center flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className='text-blue-600 dark:text-blue-300 hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
onClick={hasSubMenu ? toggleOpenSubMenu : null}
|
||||
className='items-center flex justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
<span className='text-blue-600 dark:text-blue-300 hover:text-red-400 transition-all items-center duration-200'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
</span>
|
||||
<i
|
||||
className={`px-2 fa fa-plus transition-all duration-200 ${isOpen && 'rotate-45'} text-gray-400`}></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <div key={index} className='dark:bg-black text-left px-10 justify-start text-blue-600 dark:text-blue-300 bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='ml-4 text-sm'>{sLink?.icon && <span className='mr-2 w-4'><i className={sLink.icon}/></span>}{sLink.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
})}
|
||||
</Collapse>}
|
||||
{/* 折叠子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='dark:bg-black text-left px-10 justify-start text-blue-600 dark:text-blue-300 bg-gray-50 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 border-b dark:border-gray-800 py-3 pr-6'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='ml-4 text-sm'>
|
||||
{sLink?.icon && (
|
||||
<span className='mr-2 w-4'>
|
||||
<i className={sLink.icon} />
|
||||
</span>
|
||||
)}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,33 +9,60 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => changeShow(true)}
|
||||
onMouseOut={() => changeShow(false)}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className=' menu-link pl-2 pr-4 text-gray-700 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}
|
||||
{link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<Link
|
||||
href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className=" menu-link pl-2 pr-4 text-gray-700 dark:text-gray-200 no-underline tracking-widest pb-1">
|
||||
{link?.icon && <span className='mr-2'><i className={link.icon} /></span>}{link?.name}
|
||||
{hasSubMenu && <i className='px-2 fa fa-angle-down'></i>}
|
||||
</Link>}
|
||||
|
||||
{hasSubMenu && <>
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 text-gray-700 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && <span className='mr-2'><i className={link.icon} /></span>} {link?.name}
|
||||
<i className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && <ul className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-10'} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <li key={index} className='not:last-child:border-b-0 border-b text-blue-600 dark:text-blue-300 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-2'>
|
||||
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>
|
||||
<span className='text-sm text-nowrap'>{sLink?.icon && <i className={sLink?.icon} > </i>}{sLink.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
{hasSubMenu && (
|
||||
<>
|
||||
<div className='cursor-pointer menu-link pl-2 pr-4 text-gray-700 dark:text-gray-200 no-underline tracking-widest pb-1'>
|
||||
{link?.icon && (
|
||||
<span className='mr-2'>
|
||||
<i className={link.icon} />
|
||||
</span>
|
||||
)}{' '}
|
||||
{link?.name}
|
||||
<i
|
||||
className={`px-2 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 子菜单 */}
|
||||
{hasSubMenu && (
|
||||
<ul
|
||||
className={`${show ? 'visible opacity-100 top-12' : 'invisible opacity-0 top-10'} border-gray-100 bg-white dark:bg-black dark:border-gray-800 transition-all duration-300 z-20 absolute block drop-shadow-lg `}>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className='not:last-child:border-b-0 border-b text-blue-600 dark:text-blue-300 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-2'>
|
||||
<Link href={sLink.to} target={link?.target}>
|
||||
<span className='text-sm text-nowrap'>
|
||||
{sLink?.icon && <i className={sLink?.icon}> </i>}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,164 +1,187 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
|
||||
import SocialButton from '@/themes/fukasawa/components/SocialButton'
|
||||
import CONFIG from '../config'
|
||||
import { Logo } from './Logo'
|
||||
import SocialButton from '@/themes/fukasawa/components/SocialButton'
|
||||
import { SVGFooterCircleBG } from './svg/SVGFooterCircleBG'
|
||||
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
|
||||
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
export const Footer = (props) => {
|
||||
export const Footer = props => {
|
||||
const latestPosts = props?.latestPosts ? props?.latestPosts.slice(0, 2) : []
|
||||
|
||||
return <>
|
||||
{/* <!-- ====== Footer Section Start --> */}
|
||||
<footer
|
||||
className="wow fadeInUp relative z-10 bg-[#090E34] pt-20 lg:pt-[100px]"
|
||||
data-wow-delay=".15s"
|
||||
>
|
||||
<div className="container">
|
||||
<div className="-mx-4 flex flex-wrap">
|
||||
<div className="w-full px-4 sm:w-1/2 md:w-1/2 lg:w-4/12 xl:w-3/12">
|
||||
<div className="mb-10 w-full">
|
||||
<a
|
||||
className="-mx-4 mb-6 inline-block max-w-[160px]"
|
||||
>
|
||||
<Logo white={true}/>
|
||||
</a>
|
||||
<p className="mb-8 max-w-[270px] text-base text-gray-7">
|
||||
{siteConfig('STARTER_FOOTER_SLOGAN', null, CONFIG)}
|
||||
</p>
|
||||
<div className="-mx-3 flex items-center">
|
||||
<div className='mx-3'><SocialButton/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 中间三列菜单组 */}
|
||||
{CONFIG.STARTER_FOOTER_LINK_GROUP?.map((item, index) => {
|
||||
return <div key={index} className="w-full px-4 sm:w-1/2 md:w-1/2 lg:w-2/12 xl:w-2/12">
|
||||
<div className="mb-10 w-full">
|
||||
<h4 className="mb-9 text-lg font-semibold text-white">
|
||||
{item.TITLE}
|
||||
</h4>
|
||||
<ul>
|
||||
{item?.LINK_GROUP?.map((l, i) => {
|
||||
return <li key={i}>
|
||||
<a href={l.URL}
|
||||
className="mb-3 inline-block text-base text-gray-7 hover:text-primary"
|
||||
>
|
||||
{l.TITLE}
|
||||
</a>
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
|
||||
{/* 页脚右侧最新博文 */}
|
||||
<div className="w-full px-4 md:w-2/3 lg:w-6/12 xl:w-3/12">
|
||||
<div className="mb-10 w-full">
|
||||
<h4 className="mb-9 text-lg font-semibold text-white">
|
||||
{siteConfig('STARTER_FOOTER_BLOG_LATEST_TITLE', null, CONFIG)}
|
||||
</h4>
|
||||
{/* 展示两条最新博客文章 */}
|
||||
<div className="flex flex-col gap-8">
|
||||
|
||||
{latestPosts?.map((item, index) => {
|
||||
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
|
||||
|
||||
return <a key={index}
|
||||
href={url}
|
||||
className="group flex items-center gap-[22px]"
|
||||
>
|
||||
<div className="overflow-hidden rounded w-20 h-12">
|
||||
<img
|
||||
src={item.pageCoverThumbnail}
|
||||
alt={item.title}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="line-clamp-2 max-w-[180px] text-base text-gray-7 group-hover:text-white"
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
const STARTER_FOOTER_LINK_GROUP = siteConfig(
|
||||
'STARTER_FOOTER_LINK_GROUP',
|
||||
[],
|
||||
CONFIG
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{/* <!-- ====== Footer Section Start --> */}
|
||||
<footer
|
||||
className='wow fadeInUp relative z-10 bg-[#090E34] pt-20 lg:pt-[100px]'
|
||||
data-wow-delay='.15s'>
|
||||
<div className='container'>
|
||||
<div className='-mx-4 flex flex-wrap'>
|
||||
<div className='w-full px-4 sm:w-1/2 md:w-1/2 lg:w-4/12 xl:w-3/12'>
|
||||
<div className='mb-10 w-full'>
|
||||
<a className='-mx-4 mb-6 inline-block max-w-[160px]'>
|
||||
<Logo white={true} />
|
||||
</a>
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部版权信息相关 */}
|
||||
|
||||
<div
|
||||
className="mt-12 border-t border-[#8890A4] border-opacity-40 py-8 lg:mt-[60px]"
|
||||
>
|
||||
<div className="container">
|
||||
<div className="-mx-4 flex flex-wrap">
|
||||
<div className="w-full px-4 md:w-2/3 lg:w-1/2">
|
||||
<div className="my-1">
|
||||
<div
|
||||
className="-mx-3 flex items-center justify-center md:justify-start"
|
||||
>
|
||||
<a
|
||||
href= {siteConfig('STARTER_FOOTER_PRIVACY_POLICY_URL', null, CONFIG)}
|
||||
className="px-3 text-base text-gray-7 hover:text-white hover:underline"
|
||||
>
|
||||
{siteConfig('STARTER_FOOTER_PRIVACY_POLICY_TEXT', null, CONFIG)}
|
||||
</a>
|
||||
<a
|
||||
href= {siteConfig('STARTER_FOOTER_PRIVACY_LEGAL_NOTICE_URL', null, CONFIG)}
|
||||
className="px-3 text-base text-gray-7 hover:text-white hover:underline"
|
||||
>
|
||||
{siteConfig('STARTER_FOOTER_PRIVACY_LEGAL_NOTICE_TEXT', null, CONFIG)}
|
||||
</a>
|
||||
<a
|
||||
href= {siteConfig('STARTER_FOOTER_PRIVACY_TERMS_OF_SERVICE_URL', null, CONFIG)}
|
||||
className="px-3 text-base text-gray-7 hover:text-white hover:underline"
|
||||
>
|
||||
{siteConfig('STARTER_FOOTER_PRIVACY_TERMS_OF_SERVICE_TEXT', null, CONFIG)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full px-4 md:w-1/3 lg:w-1/2">
|
||||
<div className="my-1 flex justify-center md:justify-end">
|
||||
<p className="text-base text-gray-7">
|
||||
Designed and Developed by
|
||||
<a
|
||||
href="https://github.com/tangly1024/NotionNext"
|
||||
rel="nofollow noopner noreferrer"
|
||||
target="_blank"
|
||||
className="px-1 text-gray-1 hover:underline"
|
||||
>
|
||||
NotionNext {siteConfig('VERSION')}
|
||||
</a>
|
||||
<p className='mb-8 max-w-[270px] text-base text-gray-7'>
|
||||
{siteConfig('STARTER_FOOTER_SLOGAN', null, CONFIG)}
|
||||
</p>
|
||||
<div className='-mx-3 flex items-center'>
|
||||
<div className='mx-3'>
|
||||
<SocialButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 中间三列菜单组 */}
|
||||
{STARTER_FOOTER_LINK_GROUP?.map((item, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='w-full px-4 sm:w-1/2 md:w-1/2 lg:w-2/12 xl:w-2/12'>
|
||||
<div className='mb-10 w-full'>
|
||||
<h4 className='mb-9 text-lg font-semibold text-white'>
|
||||
{item.TITLE}
|
||||
</h4>
|
||||
<ul>
|
||||
{item?.LINK_GROUP?.map((l, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
href={l.URL}
|
||||
className='mb-3 inline-block text-base text-gray-7 hover:text-primary'>
|
||||
{l.TITLE}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* 页脚右侧最新博文 */}
|
||||
<div className='w-full px-4 md:w-2/3 lg:w-6/12 xl:w-3/12'>
|
||||
<div className='mb-10 w-full'>
|
||||
<h4 className='mb-9 text-lg font-semibold text-white'>
|
||||
{siteConfig('STARTER_FOOTER_BLOG_LATEST_TITLE', null, CONFIG)}
|
||||
</h4>
|
||||
{/* 展示两条最新博客文章 */}
|
||||
<div className='flex flex-col gap-8'>
|
||||
{latestPosts?.map((item, index) => {
|
||||
const url = checkContainHttp(item.slug)
|
||||
? sliceUrlFromHttp(item.slug)
|
||||
: `${siteConfig('SUB_PATH', '')}/${item.slug}`
|
||||
|
||||
return (
|
||||
<a
|
||||
key={index}
|
||||
href={url}
|
||||
className='group flex items-center gap-[22px]'>
|
||||
<div className='overflow-hidden rounded w-20 h-12'>
|
||||
<img src={item.pageCoverThumbnail} alt={item.title} />
|
||||
</div>
|
||||
<span className='line-clamp-2 max-w-[180px] text-base text-gray-7 group-hover:text-white'>
|
||||
{item.title}
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer 背景 */}
|
||||
<div>
|
||||
<span className="absolute left-0 top-0 z-[-1]">
|
||||
<img src="/images/starter/footer/shape-1.svg" alt="" />
|
||||
</span>
|
||||
{/* 底部版权信息相关 */}
|
||||
|
||||
<span className="absolute bottom-0 right-0 z-[-1]">
|
||||
<img src="/images/starter/footer/shape-3.svg" alt="" />
|
||||
</span>
|
||||
<div className='mt-12 border-t border-[#8890A4] border-opacity-40 py-8 lg:mt-[60px]'>
|
||||
<div className='container'>
|
||||
<div className='-mx-4 flex flex-wrap'>
|
||||
<div className='w-full px-4 md:w-2/3 lg:w-1/2'>
|
||||
<div className='my-1'>
|
||||
<div className='-mx-3 flex items-center justify-center md:justify-start'>
|
||||
<a
|
||||
href={siteConfig(
|
||||
'STARTER_FOOTER_PRIVACY_POLICY_URL',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
className='px-3 text-base text-gray-7 hover:text-white hover:underline'>
|
||||
{siteConfig(
|
||||
'STARTER_FOOTER_PRIVACY_POLICY_TEXT',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
</a>
|
||||
<a
|
||||
href={siteConfig(
|
||||
'STARTER_FOOTER_PRIVACY_LEGAL_NOTICE_URL',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
className='px-3 text-base text-gray-7 hover:text-white hover:underline'>
|
||||
{siteConfig(
|
||||
'STARTER_FOOTER_PRIVACY_LEGAL_NOTICE_TEXT',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
</a>
|
||||
<a
|
||||
href={siteConfig(
|
||||
'STARTER_FOOTER_PRIVACY_TERMS_OF_SERVICE_URL',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
className='px-3 text-base text-gray-7 hover:text-white hover:underline'>
|
||||
{siteConfig(
|
||||
'STARTER_FOOTER_PRIVACY_TERMS_OF_SERVICE_TEXT',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full px-4 md:w-1/3 lg:w-1/2'>
|
||||
<div className='my-1 flex justify-center md:justify-end'>
|
||||
<p className='text-base text-gray-7'>
|
||||
Designed and Developed by
|
||||
<a
|
||||
href='https://github.com/tangly1024/NotionNext'
|
||||
rel='nofollow noopner noreferrer'
|
||||
target='_blank'
|
||||
className='px-1 text-gray-1 hover:underline'>
|
||||
NotionNext {siteConfig('VERSION')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="absolute right-0 top-0 z-[-1]">
|
||||
<SVGFooterCircleBG/>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
{/* <!-- ====== Footer Section End --> */}
|
||||
{/* Footer 背景 */}
|
||||
<div>
|
||||
<span className='absolute left-0 top-0 z-[-1]'>
|
||||
<img src='/images/starter/footer/shape-1.svg' alt='' />
|
||||
</span>
|
||||
|
||||
<span className='absolute bottom-0 right-0 z-[-1]'>
|
||||
<img src='/images/starter/footer/shape-3.svg' alt='' />
|
||||
</span>
|
||||
|
||||
<span className='absolute right-0 top-0 z-[-1]'>
|
||||
<SVGFooterCircleBG />
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
{/* <!-- ====== Footer Section End --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
/* eslint-disable @next/next/no-html-link-for-pages */
|
||||
import { siteConfig } from '@/lib/config';
|
||||
import { useGlobal } from '@/lib/global';
|
||||
import throttle from 'lodash.throttle';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import CONFIG from '../config';
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CONFIG from '../config'
|
||||
|
||||
/**
|
||||
* 站点图标
|
||||
@@ -21,10 +22,9 @@ export const Logo = ({ white }) => {
|
||||
// 滚动监听
|
||||
const throttleMs = 200
|
||||
const navBarScrollListener = throttle(() => {
|
||||
const scrollY = window.scrollY;
|
||||
const scrollY = window.scrollY
|
||||
// 何时显示浅色或白底的logo
|
||||
const homePageNavBar = router.route === '/' && scrollY < 10 // 在首页并且视窗在页面顶部
|
||||
console.log('白色', homePageNavBar, router.route, scrollY < 10)
|
||||
if (white || isDarkMode || homePageNavBar) {
|
||||
setLogo(siteConfig('STARTER_LOGO_WHITE', null, CONFIG))
|
||||
setLogoTextColor('text-white')
|
||||
@@ -41,21 +41,29 @@ export const Logo = ({ white }) => {
|
||||
}
|
||||
}, [isDarkMode, router])
|
||||
|
||||
return <div className="w-60 max-w-full px-4">
|
||||
<div className="navbar-logo flex items-center w-full py-5 cursor-pointer">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{logo && <img
|
||||
onClick={() => {
|
||||
router.push('/')
|
||||
}}
|
||||
src={logo}
|
||||
alt="logo"
|
||||
className="header-logo w-full"
|
||||
/>}
|
||||
{/* logo文字 */}
|
||||
<span onClick={() => { router.push('/') }} className={`${logoTextColor} dark:text-white py-1.5 header-logo-text whitespace-nowrap text-2xl font-semibold`}>
|
||||
{siteConfig('TITLE')}
|
||||
</span>
|
||||
</div>
|
||||
return (
|
||||
<div className='w-60 max-w-full px-4'>
|
||||
<div className='navbar-logo flex items-center w-full py-5 cursor-pointer'>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{logo && (
|
||||
<img
|
||||
onClick={() => {
|
||||
router.push('/')
|
||||
}}
|
||||
src={logo}
|
||||
alt='logo'
|
||||
className='header-logo w-full'
|
||||
/>
|
||||
)}
|
||||
{/* logo文字 */}
|
||||
<span
|
||||
onClick={() => {
|
||||
router.push('/')
|
||||
}}
|
||||
className={`${logoTextColor} dark:text-white py-1.5 header-logo-text whitespace-nowrap text-2xl font-semibold`}>
|
||||
{siteConfig('TITLE')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,44 +4,61 @@ import { useRouter } from 'next/router'
|
||||
export const MenuItem = ({ link }) => {
|
||||
const hasSubMenu = link?.subMenus?.length > 0
|
||||
const router = useRouter()
|
||||
return <>
|
||||
{/* MenuItem */}
|
||||
{!hasSubMenu && <li className="group relative whitespace-nowrap">
|
||||
<Link href={link?.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}
|
||||
className={`ud-menu-scroll mx-8 flex py-2 text-base font-medium text-dark group-hover:text-primary dark:text-white lg:mr-0 lg:inline-flex lg:px-0 lg:py-6 ${router.route === '/' ? 'lg:text-white lg:group-hover:text-white' : ''} lg:group-hover:opacity-70`}
|
||||
>
|
||||
{link?.icon && <i className={link.icon + ' mr-2 my-auto'} />}{link?.name}
|
||||
</Link>
|
||||
</li>}
|
||||
return (
|
||||
<>
|
||||
{/* MenuItem */}
|
||||
{!hasSubMenu && (
|
||||
<li className='group relative whitespace-nowrap'>
|
||||
<Link
|
||||
href={link?.to}
|
||||
target={link?.target}
|
||||
className={`ud-menu-scroll mx-8 flex py-2 text-base font-medium text-dark group-hover:text-primary dark:text-white lg:mr-0 lg:inline-flex lg:px-0 lg:py-6 ${router.route === '/' ? 'lg:text-white lg:group-hover:text-white' : ''} lg:group-hover:opacity-70`}>
|
||||
{link?.icon && <i className={link.icon + ' mr-2 my-auto'} />}
|
||||
{link?.name}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{hasSubMenu && <li className="submenu-item group relative whitespace-nowrap">
|
||||
{hasSubMenu && (
|
||||
<li className='submenu-item group relative whitespace-nowrap'>
|
||||
{/* 有子菜单的MenuItem */}
|
||||
<a
|
||||
className={`relative mx-8 flex items-center justify-between py-2 text-base font-medium text-dark group-hover:text-primary dark:text-white lg:ml-8 lg:mr-0 lg:inline-flex lg:py-6 lg:pl-0 lg:pr-4 ${router.route === '/' ? 'lg:text-white lg:group-hover:text-white' : ''} lg:group-hover:opacity-70 xl:ml-10`}>
|
||||
{link?.icon && <i className={link.icon + ' mr-2 my-auto'} />}
|
||||
{link?.name}
|
||||
|
||||
{/* 有子菜单的MenuItem */}
|
||||
<a className={`relative mx-8 flex items-center justify-between py-2 text-base font-medium text-dark group-hover:text-primary dark:text-white lg:ml-8 lg:mr-0 lg:inline-flex lg:py-6 lg:pl-0 lg:pr-4 ${router.route === '/' ? 'lg:text-white lg:group-hover:text-white' : ''} lg:group-hover:opacity-70 xl:ml-10`}>
|
||||
{link?.icon && <i className={link.icon + ' mr-2 my-auto'} />}{link?.name}
|
||||
<svg
|
||||
className='ml-2 fill-current'
|
||||
width='16'
|
||||
height='20'
|
||||
viewBox='0 0 16 20'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'>
|
||||
<path d='M7.99999 14.9C7.84999 14.9 7.72499 14.85 7.59999 14.75L1.84999 9.10005C1.62499 8.87505 1.62499 8.52505 1.84999 8.30005C2.07499 8.07505 2.42499 8.07505 2.64999 8.30005L7.99999 13.525L13.35 8.25005C13.575 8.02505 13.925 8.02505 14.15 8.25005C14.375 8.47505 14.375 8.82505 14.15 9.05005L8.39999 14.7C8.27499 14.825 8.14999 14.9 7.99999 14.9Z' />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<svg
|
||||
className="ml-2 fill-current"
|
||||
width="16"
|
||||
height="20"
|
||||
viewBox="0 0 16 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.99999 14.9C7.84999 14.9 7.72499 14.85 7.59999 14.75L1.84999 9.10005C1.62499 8.87505 1.62499 8.52505 1.84999 8.30005C2.07499 8.07505 2.42499 8.07505 2.64999 8.30005L7.99999 13.525L13.35 8.25005C13.575 8.02505 13.925 8.02505 14.15 8.25005C14.375 8.47505 14.375 8.82505 14.15 9.05005L8.39999 14.7C8.27499 14.825 8.14999 14.9 7.99999 14.9Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div className="submenu relative left-0 top-full hidden w-[250px] rounded-sm bg-white p-4 transition-[top] duration-300 group-hover:opacity-100 dark:bg-dark-2 lg:invisible lg:absolute lg:top-[110%] lg:block lg:opacity-0 lg:shadow-lg lg:group-hover:visible lg:group-hover:top-full" >
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return <Link key={index} href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'} className="block rounded px-4 py-[10px] text-sm text-body-color hover:text-primary dark:text-dark-6 dark:hover:text-primary" >
|
||||
{/* 子菜单SubMenuItem */}
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>{link?.icon && <i className={sLink.icon + ' mr-2 my-auto'} />} {sLink.title}</span>
|
||||
<div className='submenu relative left-0 top-full hidden w-[250px] rounded-sm bg-white p-4 transition-[top] duration-300 group-hover:opacity-100 dark:bg-dark-2 lg:invisible lg:absolute lg:top-[110%] lg:block lg:opacity-0 lg:shadow-lg lg:group-hover:visible lg:group-hover:top-full'>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
href={sLink.to}
|
||||
target={link?.target}
|
||||
className='block rounded px-4 py-[10px] text-sm text-body-color hover:text-primary dark:text-dark-6 dark:hover:text-primary'>
|
||||
{/* 子菜单SubMenuItem */}
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
{link?.icon && (
|
||||
<i className={sLink.icon + ' mr-2 my-auto'} />
|
||||
)}{' '}
|
||||
{sLink.title}
|
||||
</span>
|
||||
</Link>
|
||||
})}
|
||||
</div>
|
||||
</li>}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,94 +2,85 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import CONFIG from '../config'
|
||||
import { SVGAvatarBG } from './svg/SVGAvatarBG'
|
||||
import { SVGFacebook } from './svg/SVGFacebook'
|
||||
import { SVGTwitter } from './svg/SVGTwitter'
|
||||
import { SVGInstagram } from './svg/SVGInstagram'
|
||||
|
||||
export const Team = () => {
|
||||
return <>
|
||||
{/* <!-- ====== Team Section Start --> */}
|
||||
<section
|
||||
id="team"
|
||||
className="overflow-hidden bg-gray-1 pb-12 pt-20 dark:bg-dark-2 lg:pb-[90px] lg:pt-[120px]"
|
||||
>
|
||||
<div className="container mx-auto">
|
||||
<div className="-mx-4 flex flex-wrap">
|
||||
<div className="w-full px-4">
|
||||
<div className="mx-auto mb-[60px] max-w-[485px] text-center">
|
||||
<span className="mb-2 block text-lg font-semibold text-primary">
|
||||
{siteConfig('STARTER_TEAM_TITLE', null, CONFIG)}
|
||||
</span>
|
||||
<h2
|
||||
className="mb-3 text-3xl font-bold leading-[1.2] text-dark dark:text-white sm:text-4xl md:text-[40px]"
|
||||
>
|
||||
{siteConfig('STARTER_TEAM_TEXT_1', null, CONFIG)}
|
||||
</h2>
|
||||
<p dangerouslySetInnerHTML={
|
||||
{ __html: siteConfig('STARTER_TEAM_TEXT_2', null, CONFIG) }
|
||||
} className="text-base text-body-color dark:text-dark-6">
|
||||
</p>
|
||||
return (
|
||||
<>
|
||||
{/* <!-- ====== Team Section Start --> */}
|
||||
<section
|
||||
id='team'
|
||||
className='overflow-hidden bg-gray-1 pb-12 pt-20 dark:bg-dark-2 lg:pb-[90px] lg:pt-[120px]'>
|
||||
<div className='container mx-auto'>
|
||||
<div className='-mx-4 flex flex-wrap'>
|
||||
<div className='w-full px-4'>
|
||||
<div className='mx-auto mb-[60px] max-w-[485px] text-center'>
|
||||
<span className='mb-2 block text-lg font-semibold text-primary'>
|
||||
{siteConfig('STARTER_TEAM_TITLE', null, CONFIG)}
|
||||
</span>
|
||||
<h2 className='mb-3 text-3xl font-bold leading-[1.2] text-dark dark:text-white sm:text-4xl md:text-[40px]'>
|
||||
{siteConfig('STARTER_TEAM_TEXT_1', null, CONFIG)}
|
||||
</h2>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: siteConfig('STARTER_TEAM_TEXT_2', null, CONFIG)
|
||||
}}
|
||||
className='text-base text-body-color dark:text-dark-6'></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 团队成员排列矩阵 */}
|
||||
<div className="-mx-4 flex flex-wrap justify-center">
|
||||
{/* 团队成员排列矩阵 */}
|
||||
<div className='-mx-4 flex flex-wrap justify-center'>
|
||||
{CONFIG.STARTER_TEAM_ITEMS.map((item, index) => {
|
||||
return <div key={index} className="w-full px-4 sm:w-1/2 lg:w-1/4 xl:w-1/4">
|
||||
return (
|
||||
<div
|
||||
className="group mb-8 rounded-xl bg-white px-5 pb-10 pt-12 shadow-testimonial dark:bg-dark dark:shadow-none"
|
||||
>
|
||||
{/* 头像 */}
|
||||
<div className="relative z-10 mx-auto mb-5 h-[120px] w-[120px]">
|
||||
<img
|
||||
src={item.STARTER_TEAM_ITEM_AVATAR}
|
||||
alt="team image"
|
||||
className="h-[120px] w-[120px] rounded-full"
|
||||
/>
|
||||
<span
|
||||
className="absolute bottom-0 left-0 -z-10 h-10 w-10 rounded-full bg-secondary opacity-0 transition-all group-hover:opacity-100"
|
||||
></span>
|
||||
<span
|
||||
className="absolute right-0 top-0 -z-10 opacity-0 transition-all group-hover:opacity-100"
|
||||
>
|
||||
<SVGAvatarBG/>
|
||||
</span>
|
||||
</div>
|
||||
key={index}
|
||||
className='w-full px-4 sm:w-1/2 lg:w-1/4 xl:w-1/4'>
|
||||
<div className='group mb-8 rounded-xl bg-white px-5 pb-10 pt-12 shadow-testimonial dark:bg-dark dark:shadow-none'>
|
||||
{/* 头像 */}
|
||||
<div className='relative z-10 mx-auto mb-5 h-[120px] w-[120px]'>
|
||||
<img
|
||||
src={item.STARTER_TEAM_ITEM_AVATAR}
|
||||
alt='team image'
|
||||
className='h-[120px] w-[120px] rounded-full'
|
||||
/>
|
||||
<span className='absolute bottom-0 left-0 -z-10 h-10 w-10 rounded-full bg-secondary opacity-0 transition-all group-hover:opacity-100'></span>
|
||||
<span className='absolute right-0 top-0 -z-10 opacity-0 transition-all group-hover:opacity-100'>
|
||||
<SVGAvatarBG />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 文字介绍 */}
|
||||
<div className="text-center">
|
||||
{/* 文字介绍 */}
|
||||
<div className='text-center'>
|
||||
<h4 className='mb-1 text-lg font-semibold text-dark dark:text-white'>
|
||||
{item.STARTER_TEAM_ITEM_NICKNAME}
|
||||
</h4>
|
||||
|
||||
<h4
|
||||
className="mb-1 text-lg font-semibold text-dark dark:text-white"
|
||||
>
|
||||
{item.STARTER_TEAM_ITEM_NICKNAME}
|
||||
</h4>
|
||||
<p className='mb-5 text-sm text-body-color dark:text-dark-6'>
|
||||
{item.STARTER_TEAM_ITEM_DESCRIPTION}
|
||||
</p>
|
||||
|
||||
<p className="mb-5 text-sm text-body-color dark:text-dark-6">
|
||||
{item.STARTER_TEAM_ITEM_DESCRIPTION}
|
||||
</p>
|
||||
|
||||
{/* 社交链接 */}
|
||||
<div className="flex items-center justify-center gap-5">
|
||||
<a className="text-dark-6 hover:text-primary" >
|
||||
<SVGFacebook/>
|
||||
</a>
|
||||
<a className="text-dark-6 hover:text-primary" >
|
||||
<SVGTwitter/>
|
||||
</a>
|
||||
<a className="text-dark-6 hover:text-primary" >
|
||||
<SVGInstagram/>
|
||||
</a>
|
||||
{/* 社交链接 */}
|
||||
{/* <div className='flex items-center justify-center gap-5'>
|
||||
<a className='text-dark-6 hover:text-primary'>
|
||||
<SVGFacebook className='fill-current' />
|
||||
</a>
|
||||
<a className='text-dark-6 hover:text-primary'>
|
||||
<SVGTwitter className='fill-current' />
|
||||
</a>
|
||||
<a className='text-dark-6 hover:text-primary'>
|
||||
<SVGInstagram className='fill-current' />
|
||||
</a>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* <!-- ====== Team Section End --> */}
|
||||
</section>
|
||||
{/* <!-- ====== Team Section End --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
/* eslint-disable react/no-unescaped-entities */
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
|
||||
import { siteConfig } from '@/lib/config';
|
||||
import { loadExternalResource } from '@/lib/utils';
|
||||
import { useEffect } from 'react';
|
||||
import CONFIG from '../config';
|
||||
import { SVGLeftArrow } from './svg/SVGLeftArrow';
|
||||
import { SVGRightArrow } from './svg/SVGRightArrow';
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
import CONFIG from '../config'
|
||||
import { SVGLeftArrow } from './svg/SVGLeftArrow'
|
||||
import { SVGRightArrow } from './svg/SVGRightArrow'
|
||||
|
||||
/**
|
||||
* 一些外部js
|
||||
*/
|
||||
const loadExternal = async () => {
|
||||
await loadExternalResource('https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.css', 'css');
|
||||
await loadExternalResource('https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.js', 'js');
|
||||
await loadExternalResource(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.css',
|
||||
'css'
|
||||
)
|
||||
await loadExternalResource(
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.js',
|
||||
'js'
|
||||
)
|
||||
|
||||
const Swiper = window.Swiper
|
||||
if (!Swiper) {
|
||||
@@ -45,101 +51,109 @@ const loadExternal = async () => {
|
||||
spaceBetween: 30
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
export const Testimonials = () => {
|
||||
useEffect(() => {
|
||||
loadExternal()
|
||||
}, [])
|
||||
// 用户评分
|
||||
const ratings = [1, 2, 3, 4, 5];
|
||||
return <>
|
||||
{/* <!-- ====== Testimonial Section Start --> */}
|
||||
<section
|
||||
id="testimonials"
|
||||
className="overflow-hidden bg-gray-1 py-20 dark:bg-dark-2 md:py-[120px]"
|
||||
>
|
||||
<div className="container mx-auto">
|
||||
<div className="-mx-4 flex flex-wrap justify-center">
|
||||
<div className="w-full px-4">
|
||||
<div className="mx-auto mb-[60px] max-w-[485px] text-center">
|
||||
<span className="mb-2 block text-lg font-semibold text-primary">
|
||||
{siteConfig('STARTER_TESTIMONIALS_TITLE', null, CONFIG)}
|
||||
</span>
|
||||
<h2
|
||||
className="mb-3 text-3xl font-bold leading-[1.2] text-dark dark:text-white sm:text-4xl md:text-[40px]"
|
||||
>
|
||||
{siteConfig('STARTER_TESTIMONIALS_TEXT_1', null, CONFIG)}
|
||||
</h2>
|
||||
<p className="text-base text-body-color dark:text-dark-6">
|
||||
{siteConfig('STARTER_TESTIMONIALS_TEXT_2', null, CONFIG)}
|
||||
</p>
|
||||
const ratings = [1, 2, 3, 4, 5]
|
||||
const STARTER_TESTIMONIALS_ITEMS = siteConfig(
|
||||
'STARTER_TESTIMONIALS_ITEMS',
|
||||
[],
|
||||
CONFIG
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{/* <!-- ====== Testimonial Section Start --> */}
|
||||
<section
|
||||
id='testimonials'
|
||||
className='overflow-hidden bg-gray-1 py-20 dark:bg-dark-2 md:py-[120px]'>
|
||||
<div className='container mx-auto'>
|
||||
<div className='-mx-4 flex flex-wrap justify-center'>
|
||||
<div className='w-full px-4'>
|
||||
<div className='mx-auto mb-[60px] max-w-[485px] text-center'>
|
||||
<span className='mb-2 block text-lg font-semibold text-primary'>
|
||||
{siteConfig('STARTER_TESTIMONIALS_TITLE', null, CONFIG)}
|
||||
</span>
|
||||
<h2 className='mb-3 text-3xl font-bold leading-[1.2] text-dark dark:text-white sm:text-4xl md:text-[40px]'>
|
||||
{siteConfig('STARTER_TESTIMONIALS_TEXT_1', null, CONFIG)}
|
||||
</h2>
|
||||
<p className='text-base text-body-color dark:text-dark-6'>
|
||||
{siteConfig('STARTER_TESTIMONIALS_TEXT_2', null, CONFIG)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="-m-5">
|
||||
<div className="swiper testimonial-carousel common-carousel p-5">
|
||||
<div className="swiper-wrapper">
|
||||
|
||||
{/* 用户评价卡牌 */}
|
||||
{CONFIG.STARTER_TESTIMONIALS_ITEMS.map((item, index) => {
|
||||
return <div key={index} className="swiper-slide">
|
||||
<div
|
||||
className="rounded-xl bg-white px-4 py-[30px] shadow-testimonial dark:bg-dark sm:px-[30px]"
|
||||
>
|
||||
<div className="mb-[18px] flex items-center gap-[2px]">
|
||||
{ratings.map((rating, index) => (
|
||||
<img key={index} alt="star icon"// 为每个图片设置唯一的 key 属性
|
||||
src={siteConfig('STARTER_TESTIMONIALS_STAR_ICON', null, CONFIG)}/>
|
||||
))}
|
||||
<div className='-m-5'>
|
||||
<div className='swiper testimonial-carousel common-carousel p-5'>
|
||||
<div className='swiper-wrapper'>
|
||||
{/* 用户评价卡牌 */}
|
||||
{STARTER_TESTIMONIALS_ITEMS.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className='swiper-slide'>
|
||||
<div className='rounded-xl bg-white px-4 py-[30px] shadow-testimonial dark:bg-dark sm:px-[30px]'>
|
||||
<div className='mb-[18px] flex items-center gap-[2px]'>
|
||||
{ratings.map((rating, index) => (
|
||||
<img
|
||||
key={index}
|
||||
alt='star icon' // 为每个图片设置唯一的 key 属性
|
||||
src={siteConfig(
|
||||
'STARTER_TESTIMONIALS_STAR_ICON',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="mb-6 text-base text-body-color dark:text-dark-6">
|
||||
“{item.STARTER_TESTIMONIALS_ITEM_TEXT}”
|
||||
<p className='mb-6 text-base text-body-color dark:text-dark-6'>
|
||||
“{item.STARTER_TESTIMONIALS_ITEM_TEXT}”
|
||||
</p>
|
||||
|
||||
<a href={item.STARTER_TESTIMONIALS_ITEM_URL} className="flex items-center gap-4">
|
||||
<div className="h-[50px] w-[50px] overflow-hidden rounded-full">
|
||||
<img
|
||||
src={item.STARTER_TESTIMONIALS_ITEM_AVATAR}
|
||||
alt="author"
|
||||
className="h-[50px] w-[50px] overflow-hidden rounded-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
href={item.STARTER_TESTIMONIALS_ITEM_URL}
|
||||
className='flex items-center gap-4'>
|
||||
<div className='h-[50px] w-[50px] overflow-hidden rounded-full'>
|
||||
<img
|
||||
src={item.STARTER_TESTIMONIALS_ITEM_AVATAR}
|
||||
alt='author'
|
||||
className='h-[50px] w-[50px] overflow-hidden rounded-full object-cover'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3
|
||||
className="text-sm font-semibold text-dark dark:text-white"
|
||||
>
|
||||
{item.STARTER_TESTIMONIALS_ITEM_NICKNAME}
|
||||
</h3>
|
||||
<p className="text-xs text-body-secondary">
|
||||
{item.STARTER_TESTIMONIALS_ITEM_DESCRIPTION}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className='text-sm font-semibold text-dark dark:text-white'>
|
||||
{item.STARTER_TESTIMONIALS_ITEM_NICKNAME}
|
||||
</h3>
|
||||
<p className='text-xs text-body-secondary'>
|
||||
{item.STARTER_TESTIMONIALS_ITEM_DESCRIPTION}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 切换按钮 */}
|
||||
<div className='mt-[60px] flex items-center justify-center gap-1'>
|
||||
<div className='swiper-button-prev'>
|
||||
<SVGLeftArrow />
|
||||
</div>
|
||||
<div className='swiper-button-next'>
|
||||
<SVGRightArrow />
|
||||
</div>
|
||||
})}
|
||||
|
||||
</div>
|
||||
|
||||
{/* 切换按钮 */}
|
||||
<div className="mt-[60px] flex items-center justify-center gap-1">
|
||||
<div className="swiper-button-prev">
|
||||
<SVGLeftArrow/>
|
||||
</div>
|
||||
<div className="swiper-button-next">
|
||||
<SVGRightArrow/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* <!-- ====== Testimonial Section End --> */}
|
||||
</section>
|
||||
{/* <!-- ====== Testimonial Section End --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,34 +9,35 @@
|
||||
* 2. 内容大部分是在此文件中写死,notion数据从props参数中传进来
|
||||
* 3. 您可在此网站找到更多喜欢的组件 https://www.tailwind-kit.com/
|
||||
*/
|
||||
import { useRouter } from 'next/router'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import CONFIG from './config'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import Loading from '@/components/Loading'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { Style } from './style'
|
||||
import { NavBar } from './components/NavBar'
|
||||
import { Hero } from './components/Hero'
|
||||
import { Features } from './components/Features'
|
||||
import { About } from './components/About'
|
||||
import { Pricing } from './components/Pricing'
|
||||
import { Testimonials } from './components/Testimonials'
|
||||
import { FAQ } from './components/FAQ'
|
||||
import { Team } from './components/Team'
|
||||
import { Blog } from './components/Blog'
|
||||
import { Contact } from './components/Contact'
|
||||
import { Brand } from './components/Brand'
|
||||
import { Footer } from './components/Footer'
|
||||
import { BackToTopButton } from './components/BackToTopButton'
|
||||
import { Blog } from './components/Blog'
|
||||
import { Brand } from './components/Brand'
|
||||
import { Contact } from './components/Contact'
|
||||
import { FAQ } from './components/FAQ'
|
||||
import { Features } from './components/Features'
|
||||
import { Footer } from './components/Footer'
|
||||
import { Hero } from './components/Hero'
|
||||
import { NavBar } from './components/NavBar'
|
||||
import { Pricing } from './components/Pricing'
|
||||
import { Team } from './components/Team'
|
||||
import { Testimonials } from './components/Testimonials'
|
||||
import CONFIG from './config'
|
||||
import { Style } from './style'
|
||||
// import { MadeWithButton } from './components/MadeWithButton'
|
||||
import { SVG404 } from './components/svg/SVG404'
|
||||
import BLOG from '@/blog.config'
|
||||
import { loadWowJS } from '@/lib/plugins/wow'
|
||||
import Link from 'next/link'
|
||||
import { Banner } from './components/Banner'
|
||||
import { SignInForm } from './components/SignInForm'
|
||||
import { SignUpForm } from './components/SignUpForm'
|
||||
import Link from 'next/link'
|
||||
import { loadWowJS } from '@/lib/plugins/wow'
|
||||
import { SVG404 } from './components/svg/SVG404'
|
||||
|
||||
/**
|
||||
* 布局框架
|
||||
@@ -46,7 +47,7 @@ import { loadWowJS } from '@/lib/plugins/wow'
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutBase = (props) => {
|
||||
const LayoutBase = props => {
|
||||
const { children } = props
|
||||
|
||||
// 加载wow动画
|
||||
@@ -54,18 +55,22 @@ const LayoutBase = (props) => {
|
||||
loadWowJS()
|
||||
}, [])
|
||||
|
||||
return <div id='theme-starter' className={`${siteConfig('FONT_STYLE')} min-h-screen flex flex-col dark:bg-[#212b36] scroll-smooth`}>
|
||||
<Style/>
|
||||
<NavBar {...props}/>
|
||||
return (
|
||||
<div
|
||||
id='theme-starter'
|
||||
className={`${siteConfig('FONT_STYLE', BLOG.FONT_STYLE, props.NOTION_CONFIG)} min-h-screen flex flex-col dark:bg-[#212b36] scroll-smooth`}>
|
||||
<Style />
|
||||
<NavBar {...props} />
|
||||
|
||||
{children}
|
||||
{children}
|
||||
|
||||
<Footer {...props}/>
|
||||
<Footer {...props} />
|
||||
|
||||
{/* 悬浮按钮 */}
|
||||
<BackToTopButton/>
|
||||
{/* <MadeWithButton/> */}
|
||||
</div>
|
||||
{/* 悬浮按钮 */}
|
||||
<BackToTopButton />
|
||||
{/* <MadeWithButton/> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,34 +78,36 @@ const LayoutBase = (props) => {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutIndex = (props) => {
|
||||
const LayoutIndex = props => {
|
||||
const count = siteConfig('STARTER_BLOG_COUNT', 3, CONFIG)
|
||||
const posts = props?.allNavPages
|
||||
? props.allNavPages.slice(0, count)
|
||||
: []
|
||||
const posts = props?.allNavPages ? props.allNavPages.slice(0, count) : []
|
||||
return (
|
||||
<>
|
||||
{/* 英雄区 */}
|
||||
{siteConfig('STARTER_HERO_ENABLE', null, CONFIG) && <Hero/>}
|
||||
{/* 产品特性 */}
|
||||
{siteConfig('STARTER_FEATURE_ENABLE', null, CONFIG) && <Features/>}
|
||||
{/* 关于 */}
|
||||
{siteConfig('STARTER_ABOUT_ENABLE', null, CONFIG) && <About/>}
|
||||
{/* 价格 */}
|
||||
{siteConfig('STARTER_PRICING_ENABLE', null, CONFIG) && <Pricing/>}
|
||||
{/* 评价展示 */}
|
||||
{siteConfig('STARTER_TESTIMONIALS_ENABLE', null, CONFIG) && <Testimonials/>}
|
||||
{/* 常见问题 */}
|
||||
{siteConfig('STARTER_FAQ_ENABLE', null, CONFIG) && <FAQ/>}
|
||||
{/* 团队介绍 */}
|
||||
{siteConfig('团队成员区块', null, CONFIG) && <Team/>}
|
||||
{/* 博文列表 */}
|
||||
{siteConfig('STARTER_BLOG_ENABLE', null, CONFIG) && <Blog posts={posts}/>}
|
||||
{/* 联系方式 */}
|
||||
{siteConfig('STARTER_CONTACT_ENABLE', null, CONFIG) && <Contact/>}
|
||||
{/* 合作伙伴 */}
|
||||
{siteConfig('STARTER_BRANDS_ENABLE', null, CONFIG) && <Brand/>}
|
||||
</>
|
||||
<>
|
||||
{/* 英雄区 */}
|
||||
{siteConfig('STARTER_HERO_ENABLE', null, CONFIG) && <Hero />}
|
||||
{/* 产品特性 */}
|
||||
{siteConfig('STARTER_FEATURE_ENABLE', null, CONFIG) && <Features />}
|
||||
{/* 关于 */}
|
||||
{siteConfig('STARTER_ABOUT_ENABLE', null, CONFIG) && <About />}
|
||||
{/* 价格 */}
|
||||
{siteConfig('STARTER_PRICING_ENABLE', null, CONFIG) && <Pricing />}
|
||||
{/* 评价展示 */}
|
||||
{siteConfig('STARTER_TESTIMONIALS_ENABLE', null, CONFIG) && (
|
||||
<Testimonials />
|
||||
)}
|
||||
{/* 常见问题 */}
|
||||
{siteConfig('STARTER_FAQ_ENABLE', null, CONFIG) && <FAQ />}
|
||||
{/* 团队介绍 */}
|
||||
{siteConfig('STARTER_TEAM_ENABLE', null, CONFIG) && <Team />}
|
||||
{/* 博文列表 */}
|
||||
{siteConfig('STARTER_BLOG_ENABLE', null, CONFIG) && (
|
||||
<Blog posts={posts} />
|
||||
)}
|
||||
{/* 联系方式 */}
|
||||
{siteConfig('STARTER_CONTACT_ENABLE', null, CONFIG) && <Contact />}
|
||||
{/* 合作伙伴 */}
|
||||
{siteConfig('STARTER_BRANDS_ENABLE', null, CONFIG) && <Brand />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,106 +116,126 @@ const LayoutIndex = (props) => {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutSlug = (props) => {
|
||||
const LayoutSlug = props => {
|
||||
const { post } = props
|
||||
|
||||
// 如果 是 /article/[slug] 的文章路径则視情況进行重定向到另一个域名
|
||||
const router = useRouter()
|
||||
if (!post && siteConfig('STARTER_POST_REDIRECT_ENABLE', null, CONFIG) && isBrowser && router.route === '/[prefix]/[slug]') {
|
||||
const redirectUrl = siteConfig('STARTER_POST_REDIRECT_URL', null, CONFIG) + router.asPath.replace('?theme=landing', '')
|
||||
if (
|
||||
!post &&
|
||||
siteConfig('STARTER_POST_REDIRECT_ENABLE', null, CONFIG) &&
|
||||
isBrowser &&
|
||||
router.route === '/[prefix]/[slug]'
|
||||
) {
|
||||
const redirectUrl =
|
||||
siteConfig('STARTER_POST_REDIRECT_URL', null, CONFIG) +
|
||||
router.asPath.replace('?theme=landing', '')
|
||||
router.push(redirectUrl)
|
||||
return <div id='theme-starter'><Loading /></div>
|
||||
return (
|
||||
<div id='theme-starter'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <>
|
||||
<Banner title={post?.title} description={post?.summary}/>
|
||||
<div className="container grow">
|
||||
<div className="flex flex-wrap justify-center -mx-4">
|
||||
<div className="w-full p-4">
|
||||
<div id='container-inner' className='mx-auto'>
|
||||
<NotionPage {...props} />
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<Banner title={post?.title} description={post?.summary} />
|
||||
<div className='container grow'>
|
||||
<div className='flex flex-wrap justify-center -mx-4'>
|
||||
<div className='w-full p-4'>
|
||||
<div id='container-inner' className='mx-auto'>
|
||||
<NotionPage {...props} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const LayoutSearch = (props) => <></>
|
||||
const LayoutSearch = props => <></>
|
||||
|
||||
/**
|
||||
* 文章归档
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutArchive = (props) => <>
|
||||
{/* 博文列表 */}
|
||||
<Blog {...props}/>
|
||||
</>
|
||||
const LayoutArchive = props => (
|
||||
<>
|
||||
{/* 博文列表 */}
|
||||
<Blog {...props} />
|
||||
</>
|
||||
)
|
||||
|
||||
/**
|
||||
* 404页面
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const Layout404 = (props) => {
|
||||
return <>
|
||||
{/* <!-- ====== 404 Section Start --> */}
|
||||
<section className="bg-white py-20 dark:bg-dark-2 lg:py-[110px]">
|
||||
<div className="container mx-auto">
|
||||
<div className="flex flex-wrap items-center -mx-4">
|
||||
<div className="w-full px-4 md:w-5/12 lg:w-6/12">
|
||||
<div className="text-center">
|
||||
<img
|
||||
src="/images/starter/404.svg"
|
||||
alt="image"
|
||||
className="max-w-full mx-auto"
|
||||
/>
|
||||
</div>
|
||||
const Layout404 = props => {
|
||||
return (
|
||||
<>
|
||||
{/* <!-- ====== 404 Section Start --> */}
|
||||
<section className='bg-white py-20 dark:bg-dark-2 lg:py-[110px]'>
|
||||
<div className='container mx-auto'>
|
||||
<div className='flex flex-wrap items-center -mx-4'>
|
||||
<div className='w-full px-4 md:w-5/12 lg:w-6/12'>
|
||||
<div className='text-center'>
|
||||
<img
|
||||
src='/images/starter/404.svg'
|
||||
alt='image'
|
||||
className='max-w-full mx-auto'
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full px-4 md:w-7/12 lg:w-6/12 xl:w-5/12">
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<SVG404/>
|
||||
</div>
|
||||
<h3 className="mb-5 text-2xl font-semibold text-dark dark:text-white">
|
||||
{siteConfig('STARTER_404_TITLE', null, CONFIG)}
|
||||
</h3>
|
||||
<p className="mb-8 text-base text-body-color dark:text-dark-6">
|
||||
{siteConfig('STARTER_404_TEXT', null, CONFIG)}
|
||||
|
||||
</p>
|
||||
<Link href='/'
|
||||
className="py-3 text-base font-medium text-white transition rounded-md bg-dark px-7 hover:bg-primary"
|
||||
>
|
||||
{siteConfig('STARTER_404_BACK', null, CONFIG)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className='w-full px-4 md:w-7/12 lg:w-6/12 xl:w-5/12'>
|
||||
<div>
|
||||
<div className='mb-8'>
|
||||
<SVG404 />
|
||||
</div>
|
||||
<h3 className='mb-5 text-2xl font-semibold text-dark dark:text-white'>
|
||||
{siteConfig('STARTER_404_TITLE', null, CONFIG)}
|
||||
</h3>
|
||||
<p className='mb-8 text-base text-body-color dark:text-dark-6'>
|
||||
{siteConfig('STARTER_404_TEXT', null, CONFIG)}
|
||||
</p>
|
||||
<Link
|
||||
href='/'
|
||||
className='py-3 text-base font-medium text-white transition rounded-md bg-dark px-7 hover:bg-primary'>
|
||||
{siteConfig('STARTER_404_BACK', null, CONFIG)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* <!-- ====== 404 Section End --> */}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{/* <!-- ====== 404 Section End --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const LayoutCategoryIndex = (props) => <></>
|
||||
const LayoutPostList = (props) => <></>
|
||||
const LayoutTagIndex = (props) => <></>
|
||||
const LayoutCategoryIndex = props => <></>
|
||||
const LayoutPostList = props => <></>
|
||||
const LayoutTagIndex = props => <></>
|
||||
|
||||
/**
|
||||
* 登录页面
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutSignIn = (props) => {
|
||||
return <>
|
||||
<div className='grow mt-20'>
|
||||
<Banner title='登录' description='这里是演示页面,NotionNext目前不提供会员登录功能'/>
|
||||
<SignInForm/>
|
||||
</div>
|
||||
const LayoutSignIn = props => {
|
||||
return (
|
||||
<>
|
||||
<div className='grow mt-20'>
|
||||
<Banner
|
||||
title='登录'
|
||||
description='这里是演示页面,NotionNext目前不提供会员登录功能'
|
||||
/>
|
||||
<SignInForm />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,24 +243,29 @@ const LayoutSignIn = (props) => {
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutSignUp = (props) => <>
|
||||
<div className='grow mt-20'>
|
||||
<Banner title='注册' description='这里是演示页面,NotionNext目前不提供会员注册功能'/>
|
||||
<SignUpForm/>
|
||||
</div>
|
||||
</>
|
||||
const LayoutSignUp = props => (
|
||||
<>
|
||||
<div className='grow mt-20'>
|
||||
<Banner
|
||||
title='注册'
|
||||
description='这里是演示页面,NotionNext目前不提供会员注册功能'
|
||||
/>
|
||||
<SignUpForm />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
export {
|
||||
CONFIG as THEME_CONFIG,
|
||||
LayoutBase,
|
||||
LayoutIndex,
|
||||
LayoutSearch,
|
||||
LayoutArchive,
|
||||
LayoutSlug,
|
||||
Layout404,
|
||||
LayoutPostList,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutTagIndex,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
LayoutSignIn,
|
||||
LayoutSignUp
|
||||
LayoutSignUp,
|
||||
LayoutSlug,
|
||||
LayoutTagIndex,
|
||||
CONFIG as THEME_CONFIG
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user