mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
Merge branch 'main' into deploy/preview.tangly1024.com
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
# 可在此添加环境变量,去掉最左边的(# )注释即可
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,16 +12,13 @@ assignees: tangly1024
|
||||
-->
|
||||
|
||||
**描述bug**
|
||||
【此项必填】简单说明bug的现象、相关的错误提示、日志等
|
||||
|
||||
**复现步骤**
|
||||
【此项必填】出现这个bug的操作步骤
|
||||
【此项必填】简单说明目前出现的现象、相关的错误提示、日志等、截图
|
||||
|
||||
**期望的正常结果**
|
||||
【此项必填】希望按这个步骤,正常操作结果是什么
|
||||
【此项必填】按这个步骤,预期出现的现象应该是什么
|
||||
|
||||
**截图**
|
||||
【可选】相关的页面,应该的结果
|
||||
**复现步骤**
|
||||
【此项必填】你的操作步骤,按此步骤理应在我的开发环境出现一样的bug。
|
||||
|
||||
**环境**
|
||||
|
||||
@@ -32,4 +29,4 @@ assignees: tangly1024
|
||||
- 【可选】浏览器 [例如. chrome, safari, firefox]
|
||||
|
||||
**补充说明**
|
||||
【可选】与问题相关的其它说明
|
||||
【可选】与问题相关的其它说明
|
||||
|
||||
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()
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -1,43 +1,79 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import dynamic from 'next/dynamic'
|
||||
import LA51 from './LA51'
|
||||
import WebWhiz from './Webwhiz'
|
||||
import TianLiGPT from './TianliGPT'
|
||||
import { GlobalStyle } from './GlobalStyle'
|
||||
import LA51 from './LA51'
|
||||
import TianLiGPT from './TianliGPT'
|
||||
import WebWhiz from './Webwhiz'
|
||||
|
||||
import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS } from '@/blog.config'
|
||||
import { isBrowser, loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
import { initGoogleAdsense } from './GoogleAdsense'
|
||||
|
||||
const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false })
|
||||
const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { ssr: false })
|
||||
const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), { ssr: false })
|
||||
const Fireworks = dynamic(() => import('@/components/Fireworks'), { ssr: false })
|
||||
const TwikooCommentCounter = dynamic(
|
||||
() => import('@/components/TwikooCommentCounter'),
|
||||
{ ssr: false }
|
||||
)
|
||||
const DebugPanel = dynamic(() => import('@/components/DebugPanel'), {
|
||||
ssr: false
|
||||
})
|
||||
const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), {
|
||||
ssr: false
|
||||
})
|
||||
const Fireworks = dynamic(() => import('@/components/Fireworks'), {
|
||||
ssr: false
|
||||
})
|
||||
const Nest = dynamic(() => import('@/components/Nest'), { ssr: false })
|
||||
const FlutteringRibbon = dynamic(() => import('@/components/FlutteringRibbon'), { ssr: false })
|
||||
const FlutteringRibbon = dynamic(
|
||||
() => import('@/components/FlutteringRibbon'),
|
||||
{ ssr: false }
|
||||
)
|
||||
const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false })
|
||||
const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false })
|
||||
const StarrySky = dynamic(() => import('@/components/StarrySky'), { ssr: false })
|
||||
const DifyChatbot = dynamic(() => import('@/components/DifyChatbot'), { ssr: false });
|
||||
const Analytics = dynamic(() => import('@vercel/analytics/react').then(async (m) => { return m.Analytics }), { ssr: false })
|
||||
const StarrySky = dynamic(() => import('@/components/StarrySky'), {
|
||||
ssr: false
|
||||
})
|
||||
const DifyChatbot = dynamic(() => import('@/components/DifyChatbot'), {
|
||||
ssr: false
|
||||
})
|
||||
const Analytics = dynamic(
|
||||
() =>
|
||||
import('@vercel/analytics/react').then(async m => {
|
||||
return m.Analytics
|
||||
}),
|
||||
{ ssr: false }
|
||||
)
|
||||
const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false })
|
||||
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
|
||||
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
|
||||
const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false })
|
||||
const GoogleAdsense = dynamic(() => import('@/components/GoogleAdsense'), { ssr: false })
|
||||
const Messenger = dynamic(() => import('@/components/FacebookMessenger'), { ssr: false })
|
||||
const Messenger = dynamic(() => import('@/components/FacebookMessenger'), {
|
||||
ssr: false
|
||||
})
|
||||
const VConsole = dynamic(() => import('@/components/VConsole'), { ssr: false })
|
||||
const CustomContextMenu = dynamic(() => import('@/components/CustomContextMenu'), { ssr: false })
|
||||
const DisableCopy = dynamic(() => import('@/components/DisableCopy'), { ssr: false })
|
||||
const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { ssr: false })
|
||||
const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), { ssr: false })
|
||||
const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), { ssr: false })
|
||||
const CustomContextMenu = dynamic(
|
||||
() => import('@/components/CustomContextMenu'),
|
||||
{ ssr: false }
|
||||
)
|
||||
const DisableCopy = dynamic(() => import('@/components/DisableCopy'), {
|
||||
ssr: false
|
||||
})
|
||||
const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), {
|
||||
ssr: false
|
||||
})
|
||||
const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), {
|
||||
ssr: false
|
||||
})
|
||||
const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), {
|
||||
ssr: false
|
||||
})
|
||||
|
||||
/**
|
||||
* 各种插件脚本
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const ExternalPlugin = (props) => {
|
||||
const ExternalPlugin = props => {
|
||||
const DISABLE_PLUGIN = siteConfig('DISABLE_PLUGIN')
|
||||
const THEME_SWITCH = siteConfig('THEME_SWITCH')
|
||||
const DEBUG = siteConfig('DEBUG')
|
||||
@@ -55,7 +91,9 @@ const ExternalPlugin = (props) => {
|
||||
const FLUTTERINGRIBBON = siteConfig('FLUTTERINGRIBBON')
|
||||
const COMMENT_TWIKOO_COUNT_ENABLE = siteConfig('COMMENT_TWIKOO_COUNT_ENABLE')
|
||||
const RIBBON = siteConfig('RIBBON')
|
||||
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU = siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU')
|
||||
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU = siteConfig(
|
||||
'CUSTOM_RIGHT_CLICK_CONTEXT_MENU'
|
||||
)
|
||||
const CAN_COPY = siteConfig('CAN_COPY')
|
||||
const WEB_WHIZ_ENABLED = siteConfig('WEB_WHIZ_ENABLED')
|
||||
const AD_WWADS_BLOCK_DETECT = siteConfig('AD_WWADS_BLOCK_DETECT')
|
||||
@@ -113,88 +151,122 @@ const ExternalPlugin = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ADSENSE_GOOGLE_ID) {
|
||||
setTimeout(() => {
|
||||
// 异步渲染谷歌广告
|
||||
initGoogleAdsense()
|
||||
}, 1000)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (DISABLE_PLUGIN) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
return (
|
||||
<>
|
||||
{/* 全局样式嵌入 */}
|
||||
<GlobalStyle />
|
||||
|
||||
{/* 全局样式嵌入 */}
|
||||
<GlobalStyle/>
|
||||
{THEME_SWITCH && <ThemeSwitch />}
|
||||
{DEBUG && <DebugPanel />}
|
||||
{ANALYTICS_ACKEE_TRACKER && <Ackee />}
|
||||
{ANALYTICS_GOOGLE_ID && <Gtag />}
|
||||
{ANALYTICS_VERCEL && <Analytics />}
|
||||
{ANALYTICS_BUSUANZI_ENABLE && <Busuanzi />}
|
||||
{FACEBOOK_APP_ID && FACEBOOK_PAGE_ID && <Messenger />}
|
||||
{FIREWORKS && <Fireworks />}
|
||||
{SAKURA && <Sakura />}
|
||||
{STARRY_SKY && <StarrySky />}
|
||||
{MUSIC_PLAYER && <MusicPlayer />}
|
||||
{NEST && <Nest />}
|
||||
{FLUTTERINGRIBBON && <FlutteringRibbon />}
|
||||
{COMMENT_TWIKOO_COUNT_ENABLE && <TwikooCommentCounter {...props} />}
|
||||
{RIBBON && <Ribbon />}
|
||||
{DIFY_CHATBOT_ENABLED && <DifyChatbot />}
|
||||
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU && <CustomContextMenu {...props} />}
|
||||
{!CAN_COPY && <DisableCopy />}
|
||||
{WEB_WHIZ_ENABLED && <WebWhiz />}
|
||||
{AD_WWADS_BLOCK_DETECT && <AdBlockDetect />}
|
||||
{TIANLI_KEY && <TianLiGPT />}
|
||||
<VConsole />
|
||||
<LoadingProgress />
|
||||
<AosAnimation />
|
||||
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && <LA51 />}
|
||||
|
||||
{THEME_SWITCH && <ThemeSwitch />}
|
||||
{DEBUG && <DebugPanel />}
|
||||
{ANALYTICS_ACKEE_TRACKER && <Ackee />}
|
||||
{ANALYTICS_GOOGLE_ID && <Gtag />}
|
||||
{ANALYTICS_VERCEL && <Analytics />}
|
||||
{ANALYTICS_BUSUANZI_ENABLE && <Busuanzi />}
|
||||
{ADSENSE_GOOGLE_ID && <GoogleAdsense />}
|
||||
{FACEBOOK_APP_ID && FACEBOOK_PAGE_ID && <Messenger />}
|
||||
{FIREWORKS && <Fireworks />}
|
||||
{SAKURA && <Sakura />}
|
||||
{STARRY_SKY && <StarrySky />}
|
||||
{MUSIC_PLAYER && <MusicPlayer />}
|
||||
{NEST && <Nest />}
|
||||
{FLUTTERINGRIBBON && <FlutteringRibbon />}
|
||||
{COMMENT_TWIKOO_COUNT_ENABLE && <TwikooCommentCounter {...props} />}
|
||||
{RIBBON && <Ribbon />}
|
||||
{DIFY_CHATBOT_ENABLED && <DifyChatbot />}
|
||||
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU && <CustomContextMenu {...props} />}
|
||||
{!CAN_COPY && <DisableCopy />}
|
||||
{WEB_WHIZ_ENABLED && <WebWhiz />}
|
||||
{AD_WWADS_BLOCK_DETECT && <AdBlockDetect />}
|
||||
{TIANLI_KEY && <TianLiGPT/>}
|
||||
<VConsole />
|
||||
<LoadingProgress />
|
||||
<AosAnimation />
|
||||
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && <LA51/>}
|
||||
|
||||
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && (<>
|
||||
<script id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js" defer/>
|
||||
{/* <script async dangerouslySetInnerHTML={{
|
||||
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && (
|
||||
<>
|
||||
<script id='LA_COLLECT' src='//sdk.51.la/js-sdk-pro.min.js' defer />
|
||||
{/* <script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
LA.init({id:"${ANALYTICS_51LA_ID}",ck:"${ANALYTICS_51LA_CK}",hashMode:true,autoTrack:true})
|
||||
`
|
||||
}} /> */}
|
||||
</>)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 注入JS脚本 */}
|
||||
{GLOBAL_JS && <script async dangerouslySetInnerHTML={{
|
||||
__html: GLOBAL_JS
|
||||
}} />}
|
||||
{/* 注入JS脚本 */}
|
||||
{GLOBAL_JS && (
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: GLOBAL_JS
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{CHATBASE_ID && (<>
|
||||
<script id={CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer />
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
{CHATBASE_ID && (
|
||||
<>
|
||||
<script
|
||||
id={CHATBASE_ID}
|
||||
src='https://www.chatbase.co/embed.min.js'
|
||||
defer
|
||||
/>
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.chatbaseConfig = {
|
||||
chatbotId: "${CHATBASE_ID}",
|
||||
}
|
||||
`
|
||||
}} />
|
||||
</>)}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{CLARITY_ID && (<>
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{CLARITY_ID && (
|
||||
<>
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function(c,l,a,r,i,t,y){
|
||||
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
||||
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
||||
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
||||
})(window, document, "clarity", "script", "${CLARITY_ID}");
|
||||
`
|
||||
}} />
|
||||
</>)}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{COMMENT_DAO_VOICE_ID && (<>
|
||||
{/* DaoVoice 反馈 */}
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
{COMMENT_DAO_VOICE_ID && (
|
||||
<>
|
||||
{/* DaoVoice 反馈 */}
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/daf1a94b.js","daovoice")
|
||||
`
|
||||
}}
|
||||
/>
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
/>
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
daovoice('init', {
|
||||
app_id: "${COMMENT_DAO_VOICE_ID}"
|
||||
@@ -202,34 +274,52 @@ const ExternalPlugin = (props) => {
|
||||
daovoice('update');
|
||||
`
|
||||
}}
|
||||
/>
|
||||
</>)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{AD_WWADS_ID && <script type="text/javascript" src="https://cdn.wwads.cn/js/makemoney.js" async></script>}
|
||||
{AD_WWADS_ID && (
|
||||
<script
|
||||
type='text/javascript'
|
||||
src='https://cdn.wwads.cn/js/makemoney.js'
|
||||
async></script>
|
||||
)}
|
||||
|
||||
{COMMENT_TWIKOO_ENV_ID && <script defer src={COMMENT_TWIKOO_CDN_URL} />}
|
||||
{COMMENT_TWIKOO_ENV_ID && <script defer src={COMMENT_TWIKOO_CDN_URL} />}
|
||||
|
||||
{COMMENT_ARTALK_SERVER && <script defer src={COMMENT_ARTALK_JS} />}
|
||||
{COMMENT_ARTALK_SERVER && <script defer src={COMMENT_ARTALK_JS} />}
|
||||
|
||||
{COMMENT_TIDIO_ID && <script async src={`//code.tidio.co/${COMMENT_TIDIO_ID}.js`} />}
|
||||
{COMMENT_TIDIO_ID && (
|
||||
<script async src={`//code.tidio.co/${COMMENT_TIDIO_ID}.js`} />
|
||||
)}
|
||||
|
||||
{/* gitter聊天室 */}
|
||||
{COMMENT_GITTER_ROOM && (<>
|
||||
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer />
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
{/* gitter聊天室 */}
|
||||
{COMMENT_GITTER_ROOM && (
|
||||
<>
|
||||
<script
|
||||
src='https://sidecar.gitter.im/dist/sidecar.v1.js'
|
||||
async
|
||||
defer
|
||||
/>
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: '${COMMENT_GITTER_ROOM}'
|
||||
};
|
||||
`
|
||||
}} />
|
||||
</>)}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 百度统计 */}
|
||||
{ANALYTICS_BAIDU_ID && (
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* 百度统计 */}
|
||||
{ANALYTICS_BAIDU_ID && (
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
@@ -238,29 +328,33 @@ const ExternalPlugin = (props) => {
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 站长统计 */}
|
||||
{ANALYTICS_CNZZ_ID && (
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* 站长统计 */}
|
||||
{ANALYTICS_CNZZ_ID && (
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_${ANALYTICS_CNZZ_ID}'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D${ANALYTICS_CNZZ_ID}' type='text/javascript'%3E%3C/script%3E"));
|
||||
`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 谷歌统计 */}
|
||||
{ANALYTICS_GOOGLE_ID && (<>
|
||||
<script async
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${ANALYTICS_GOOGLE_ID}`}
|
||||
/>
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* 谷歌统计 */}
|
||||
{ANALYTICS_GOOGLE_ID && (
|
||||
<>
|
||||
<script
|
||||
async
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${ANALYTICS_GOOGLE_ID}`}
|
||||
/>
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
@@ -268,14 +362,17 @@ const ExternalPlugin = (props) => {
|
||||
page_path: window.location.pathname,
|
||||
});
|
||||
`
|
||||
}}
|
||||
/>
|
||||
</>)}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Matomo 统计 */}
|
||||
{MATOMO_HOST_URL && MATOMO_SITE_ID && (
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* Matomo 统计 */}
|
||||
{MATOMO_HOST_URL && MATOMO_SITE_ID && (
|
||||
<script
|
||||
async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
@@ -287,10 +384,11 @@ const ExternalPlugin = (props) => {
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
`
|
||||
}} />
|
||||
)}
|
||||
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExternalPlugin
|
||||
|
||||
@@ -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,48 +1,105 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
function requestAd() {
|
||||
const ads = document.getElementsByClassName('adsbygoogle')
|
||||
/**
|
||||
* 请求广告元素
|
||||
* 调用后,实际只有当广告单元在页面中可见时才会真正获取
|
||||
*/
|
||||
function requestAd(ads) {
|
||||
if (!ads || ads.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const adsbygoogle = window.adsbygoogle
|
||||
if (adsbygoogle && ads.length > 0) {
|
||||
for (let i = 0; i <= ads.length; i++) {
|
||||
try {
|
||||
const adStatus = ads[i].getAttribute('data-adsbygoogle-status')
|
||||
if (!adStatus || adStatus !== 'done') {
|
||||
adsbygoogle.push(ads[i])
|
||||
const observerOptions = {
|
||||
root: null, // use the viewport as the root
|
||||
threshold: 0.5 // element is considered visible when 50% visible
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const adStatus = entry.target.getAttribute('data-adsbygoogle-status')
|
||||
if (!adStatus || adStatus !== 'done') {
|
||||
adsbygoogle.push(entry.target)
|
||||
observer.unobserve(entry.target) // stop observing once ad is loaded
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
})
|
||||
}, observerOptions)
|
||||
|
||||
ads.forEach(ad => {
|
||||
observer.observe(ad)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点或其子节点中包含 adsbygoogle 类的节点
|
||||
function getNodesWithAdsByGoogleClass(node) {
|
||||
const adsNodes = []
|
||||
// 检查节点及其子节点是否包含 adsbygoogle 类
|
||||
function checkNodeForAds(node) {
|
||||
if (
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
node.classList.contains('adsbygoogle')
|
||||
) {
|
||||
adsNodes.push(node)
|
||||
} else {
|
||||
// 递归检查子节点
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
checkNodeForAds(node.childNodes[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
checkNodeForAds(node)
|
||||
return adsNodes
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化谷歌广告
|
||||
* @returns
|
||||
*/
|
||||
export default function GoogleAdsense() {
|
||||
const initGoogleAdsense = () => {
|
||||
loadExternalResource(
|
||||
`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${siteConfig('ADSENSE_GOOGLE_ID')}`,
|
||||
'js'
|
||||
).then(url => {
|
||||
setTimeout(() => {
|
||||
requestAd()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
// 延迟3秒加载
|
||||
export const initGoogleAdsense = async () => {
|
||||
console.log('Load Adsense')
|
||||
loadExternalResource(
|
||||
`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${siteConfig('ADSENSE_GOOGLE_ID')}`,
|
||||
'js'
|
||||
).then(url => {
|
||||
setTimeout(() => {
|
||||
initGoogleAdsense()
|
||||
}, 3000)
|
||||
}, [router])
|
||||
// 页面加载完成后加载一次广告
|
||||
const ads = document.getElementsByClassName('adsbygoogle')
|
||||
if (window.adsbygoogle && ads.length > 0) {
|
||||
requestAd(Array.from(ads))
|
||||
}
|
||||
|
||||
return null
|
||||
// 创建一个 MutationObserver 实例,监听页面上新出现的广告单元
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
// 检查每个添加到DOM中的节点
|
||||
mutation.addedNodes.forEach(node => {
|
||||
// 如果节点是adsbygoogle元素,则请求广告
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const adsNodes = getNodesWithAdsByGoogleClass(node)
|
||||
if (adsNodes.length > 0) {
|
||||
requestAd(adsNodes)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 配置 MutationObserver 监听特定类型的 DOM 变化
|
||||
const observerConfig = {
|
||||
childList: true, // 观察目标子节点的变化
|
||||
subtree: true // 包括目标节点的所有后代节点
|
||||
}
|
||||
|
||||
// 启动 MutationObserver
|
||||
observer.observe(document.body, observerConfig)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,9 +185,18 @@ const AdEmbed = () => {
|
||||
const newInsElement = document.createElement('ins')
|
||||
newInsElement.className = 'adsbygoogle w-full py-1'
|
||||
newInsElement.style.display = 'block'
|
||||
newInsElement.setAttribute('data-ad-client', siteConfig('ADSENSE_GOOGLE_ID'))
|
||||
newInsElement.setAttribute('data-adtest', siteConfig('ADSENSE_GOOGLE_TEST') ? 'on' : 'off')
|
||||
newInsElement.setAttribute('data-ad-slot', siteConfig('ADSENSE_GOOGLE_SLOT_AUTO'))
|
||||
newInsElement.setAttribute(
|
||||
'data-ad-client',
|
||||
siteConfig('ADSENSE_GOOGLE_ID')
|
||||
)
|
||||
newInsElement.setAttribute(
|
||||
'data-adtest',
|
||||
siteConfig('ADSENSE_GOOGLE_TEST') ? 'on' : 'off'
|
||||
)
|
||||
newInsElement.setAttribute(
|
||||
'data-ad-slot',
|
||||
siteConfig('ADSENSE_GOOGLE_SLOT_AUTO')
|
||||
)
|
||||
newInsElement.setAttribute('data-ad-format', 'auto')
|
||||
newInsElement.setAttribute('data-full-width-responsive', 'true')
|
||||
|
||||
|
||||
@@ -12,12 +12,16 @@ export default function Live2D() {
|
||||
const { theme, switchTheme } = useGlobal()
|
||||
const showPet = JSON.parse(siteConfig('WIDGET_PET'))
|
||||
const petLink = siteConfig('WIDGET_PET_LINK')
|
||||
const petSwitchTheme = siteConfig('WIDGET_PET_SWITCH_THEME')
|
||||
|
||||
useEffect(() => {
|
||||
if (showPet && !isMobile()) {
|
||||
Promise.all([
|
||||
loadExternalResource('https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js', 'js')
|
||||
]).then((e) => {
|
||||
loadExternalResource(
|
||||
'https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js',
|
||||
'js'
|
||||
)
|
||||
]).then(e => {
|
||||
if (typeof window?.loadlive2d !== 'undefined') {
|
||||
// https://github.com/xiazeyu/live2d-widget-models
|
||||
try {
|
||||
@@ -31,7 +35,7 @@ export default function Live2D() {
|
||||
}, [theme])
|
||||
|
||||
function handleClick() {
|
||||
if (JSON.parse(siteConfig('WIDGET_PET_SWITCH_THEME'))) {
|
||||
if (petSwitchTheme) {
|
||||
switchTheme()
|
||||
}
|
||||
}
|
||||
@@ -40,9 +44,15 @@ export default function Live2D() {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <canvas id="live2d" width="280" height="250" onClick={handleClick}
|
||||
className="cursor-grab"
|
||||
onMouseDown={(e) => e.target.classList.add('cursor-grabbing')}
|
||||
onMouseUp={(e) => e.target.classList.remove('cursor-grabbing')}
|
||||
return (
|
||||
<canvas
|
||||
id='live2d'
|
||||
width='280'
|
||||
height='250'
|
||||
onClick={handleClick}
|
||||
className='cursor-grab'
|
||||
onMouseDown={e => e.target.classList.add('cursor-grabbing')}
|
||||
onMouseUp={e => e.target.classList.remove('cursor-grabbing')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Full Screen iFrame</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#myIframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
/* 可选:移除边框 */
|
||||
}
|
||||
#myIframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
/* 可选:移除边框 */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<!-- <div style="position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
@@ -32,17 +31,18 @@
|
||||
<button onclick="toggleFullScreen()">Toggle Full Screen</button>
|
||||
</div> -->
|
||||
|
||||
<iframe id="myIframe" allowfullscreen="allowfullscreen" allow="autoplay" scrolling="no"></iframe>
|
||||
|
||||
<iframe
|
||||
id="myIframe"
|
||||
allowfullscreen="true"
|
||||
allow="autoplay;fullscreen"
|
||||
scrolling="auto"
|
||||
></iframe>
|
||||
|
||||
<!-- https://letsplay247.github.io/cz.html?n=space-wars-battleground -->
|
||||
<script>
|
||||
var myParam = location.search.split('n=')[1]
|
||||
document.getElementById("myIframe").src = myParam;
|
||||
var myParam = location.search.split('n=')[1]
|
||||
document.getElementById('myIframe').src = myParam
|
||||
</script>
|
||||
<script src="/js/fullscreen.js" type="text/javascript"></script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ export const Footer = props => {
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={`z-10 relative mt-6 flex-shrink-0 m-auto w-full dark:text-gray-200 `}>
|
||||
<hr className='my-2 border-black dark:border-gray-100' />
|
||||
className={`z-10 dark:bg-black bg-white p-2 rounded-lg relative mt-6 flex-shrink-0 mb-4 w-full shadow dark:text-gray-200 `}>
|
||||
{/* <hr className='my-2 border-black dark:border-gray-100' /> */}
|
||||
{/* 页面底部 */}
|
||||
<div className='w-full flex justify-between p-4 '>
|
||||
<p>
|
||||
|
||||
188
themes/game/components/GameEmbed.js
Normal file
188
themes/game/components/GameEmbed.js
Normal file
@@ -0,0 +1,188 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { Draggable } from '@/components/Draggable'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { deepClone } from '@/lib/utils'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import DownloadButton from './DownloadButton'
|
||||
import FullScreenButton from './FullScreenButton'
|
||||
|
||||
/**
|
||||
* 嵌入游戏
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function GameEmbed({ post, siteInfo }) {
|
||||
const game = deepClone(post)
|
||||
const newWindow = game?.ext?.new_window || false
|
||||
const originUrl = game?.ext?.href
|
||||
|
||||
// 提示用户在新窗口打开
|
||||
const [tipNewWindow, setTipNewWindow] = useState(newWindow)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
/**
|
||||
* 新窗口中打开游戏。
|
||||
* 并且在回到此页面后后刷新iframe,尝试重新加载iframe
|
||||
*/
|
||||
const openInNewWindow = () => {
|
||||
// 关闭提示框
|
||||
setTipNewWindow(false)
|
||||
// 添加监听器
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
|
||||
// 定义监听器函数
|
||||
function handleVisibilityChange() {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
// console.log("用户切换到了其他标签页");
|
||||
} else {
|
||||
// console.log("用户回到了当前页面");
|
||||
setLoading(true)
|
||||
// 刷新网页
|
||||
reloadIframe()
|
||||
// 移除监听器
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏提示框
|
||||
*/
|
||||
const hiddenTips = () => {
|
||||
setTipNewWindow(false)
|
||||
}
|
||||
|
||||
function reloadIframe() {
|
||||
var iframe = document.getElementById('game-wrapper')
|
||||
iframe.contentWindow.location.reload()
|
||||
}
|
||||
|
||||
// 定义一个函数来处理iframe加载成功事件
|
||||
function iframeLoaded() {
|
||||
if (game) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// 是否弹窗提示新网页打开
|
||||
setTipNewWindow(newWindow)
|
||||
|
||||
const iframe = document.getElementById('game-wrapper')
|
||||
|
||||
// 绑定加载事件
|
||||
if (iframe?.attachEvent) {
|
||||
iframe?.attachEvent('onload', iframeLoaded)
|
||||
} else if (iframe) {
|
||||
iframe.onload = iframeLoaded
|
||||
}
|
||||
|
||||
// 更改iFrame的title
|
||||
if (
|
||||
document
|
||||
?.getElementById('game-wrapper')
|
||||
?.contentDocument.querySelector('title')?.textContent
|
||||
) {
|
||||
document
|
||||
.getElementById('game-wrapper')
|
||||
.contentDocument.querySelector('title').textContent = `${
|
||||
game?.title || ''
|
||||
} - Play ${game?.title || ''} on ${siteConfig('TITLE')}`
|
||||
}
|
||||
}, [post])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${originUrl ? '' : 'hidden'} bg-black w-full xl:h-[calc(100vh-8rem)] h-screen rounded-md relative`}>
|
||||
{/* 移动端返回主页按钮 */}
|
||||
<Draggable stick='left'>
|
||||
<div
|
||||
style={{ left: '0px', top: '1rem' }}
|
||||
className='text-white fixed xl:hidden group space-x-1 flex items-center z-20 pr-3 pl-1 bg-[#202030] rounded-r-2xl shadow-lg '>
|
||||
<Link
|
||||
href='/'
|
||||
className='px-1 py-3 hover:scale-125 duration-200 transition-all'
|
||||
passHref>
|
||||
<i className='fas fa-chevron-left' />
|
||||
</Link>{' '}
|
||||
<span
|
||||
className='font-serif px-1'
|
||||
onClick={() => {
|
||||
document.querySelector('.game-info').scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
})
|
||||
}}>
|
||||
{/* Title首字母 */}
|
||||
<i className='fas fa-info' />
|
||||
</span>
|
||||
</div>
|
||||
</Draggable>
|
||||
|
||||
{/* 提示框新窗口打开 */}
|
||||
{tipNewWindow && (
|
||||
<div
|
||||
id='open-tips'
|
||||
className={`animate__animated animate__fadeIn bottom-8 right-4 absolute z-20 flex items-end justify-end`}>
|
||||
<div className='relative w-96 h-auto bg-white rounded-lg p-2'>
|
||||
<div className='absolute right-2'>
|
||||
<button className='text-xl p-2' onClick={hiddenTips}>
|
||||
<i className='fas fa-times'></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className='p-2 text-lg'>
|
||||
If the game fails to load, please try accessing the{' '}
|
||||
<a
|
||||
className='underline text-blue-500'
|
||||
rel='noReferrer'
|
||||
href={`${originUrl?.replace('/games-external/common/index.htm?n=', '')}`}
|
||||
target='_blank'
|
||||
onClick={openInNewWindow}>
|
||||
source webpage
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading遮罩 */}
|
||||
{loading && (
|
||||
<div className='absolute z-20 w-full xl:h-[calc(100vh-8rem)] h-screen rounded-md overflow-hidden '>
|
||||
<div className='z-20 absolute bg-black bg-opacity-75 w-full h-full flex flex-col gap-4 justify-center items-center'>
|
||||
<h2 className='text-3xl text-white flex gap-2 items-center'>
|
||||
<i className='fas fa-spinner animate-spin'></i>
|
||||
{siteInfo?.title || siteConfig('TITLE')}
|
||||
</h2>
|
||||
<h3 className='text-xl text-white'>
|
||||
{siteInfo?.description || siteConfig('DESCRIPTION')}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* 游戏封面图 */}
|
||||
{game?.pageCoverThumbnail && (
|
||||
<img
|
||||
src={game?.pageCoverThumbnail}
|
||||
className='w-full h-full object-cover blur-md absolute top-0 left-0 z-0'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
id='game-wrapper'
|
||||
src={originUrl}
|
||||
className={`relative w-full xl:h-[calc(100vh-8rem)] h-screen md:rounded-md overflow-hidden`}
|
||||
/>
|
||||
|
||||
{/* 游戏窗口装饰器 */}
|
||||
{originUrl && !loading && (
|
||||
<div className='game-decorator bg-[#0B0D14] right-0 bottom-0 flex justify-center z-10 md:absolute'>
|
||||
<DownloadButton />
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { AdSlot } from '@/components/GoogleAdsense'
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
|
||||
import Link from 'next/link'
|
||||
@@ -88,8 +89,8 @@ export const GameListIndexCombine = ({ posts }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='game-list-wrapper flex justify-center w-full px-2'>
|
||||
<div className='game-grid mx-auto w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2'>
|
||||
<div className='game-list-wrapper flex justify-center w-full'>
|
||||
<div className='game-grid mx-auto w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 px-2 md:p-0'>
|
||||
{components?.map((ItemComponent, index) => {
|
||||
return ItemComponent
|
||||
})}
|
||||
@@ -104,20 +105,24 @@ export const GameListIndexCombine = ({ posts }) => {
|
||||
*/
|
||||
const GameAd = () => {
|
||||
return (
|
||||
<div className='card-group border border-gray-600 rounded game-ad h-[20rem] w-full overflow-hidden'>
|
||||
<div className='card-group relative rounded-lg game-ad h-80 w-full overflow-hidden'>
|
||||
<AdSlot type='flow' />
|
||||
<div className='absolute left-0 right-0 w-full h-full flex flex-col justify-center items-center bg-white'>
|
||||
<p className='text-2xl'>{siteConfig('TITLE')}</p>
|
||||
<p>{siteConfig('DESCRIPTION')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 4卡组成一个大卡
|
||||
* 大卡由2行2列小卡构成
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const GameItemGroup = ({ items }) => {
|
||||
return (
|
||||
<div className='card-group h-[20rem] w-full grid grid-cols-2 grid-rows-2 gap-2'>
|
||||
<div className='card-group h-80 w-full grid grid-cols-2 grid-rows-2 gap-3'>
|
||||
{items.map((item, index) => (
|
||||
<GameItem key={index} item={item} />
|
||||
))}
|
||||
@@ -140,38 +145,39 @@ const GameItem = ({ item, isLargeCard }) => {
|
||||
const video = item?.ext?.video
|
||||
return (
|
||||
<Link
|
||||
title={title}
|
||||
href={`${url}`}
|
||||
className={`card-single ${isLargeCard ? 'h-80 ' : 'h-full text-xs'} w-full transition-all duration-200 hover:scale-105 shadow-md hover:shadow-lg relative rounded-lg overflow-hidden flex justify-center items-center
|
||||
group hover:border-purple-400`}
|
||||
onMouseOver={() => {
|
||||
setShowType('video')
|
||||
}}
|
||||
onMouseOut={() => {
|
||||
setShowType('img')
|
||||
}}
|
||||
title={title}
|
||||
className={`card-single ${
|
||||
isLargeCard ? 'h-[20rem]' : 'h-full'
|
||||
} w-full relative shadow rounded-md overflow-hidden flex justify-center items-center
|
||||
group hover:border-purple-400`}>
|
||||
}}>
|
||||
<div className='text-center absolute bottom-0 invisible group-hover:bottom-2 group-hover:visible transition-all duration-200 text-white z-30'>
|
||||
{title}
|
||||
</div>
|
||||
<div className='h-1/2 w-full absolute left-0 bottom-0 z-20 opacity-0 group-hover:opacity-75 transition-all duration-200'>
|
||||
|
||||
<div className='h-2/3 w-full absolute left-0 bottom-0 z-20 opacity-0 group-hover:opacity-75 transition-all duration-200'>
|
||||
<div className='h-full w-full absolute bg-gradient-to-b from-transparent to-black'></div>
|
||||
</div>
|
||||
|
||||
{showType === 'video' && (
|
||||
<video
|
||||
className={`z-10 object-cover w-full ${isLargeCard ? 'h-[20rem]' : 'h-full'} absolute overflow-hidden`}
|
||||
className={`z-10 object-cover w-full ${isLargeCard ? 'h-80' : 'h-full'} absolute overflow-hidden`}
|
||||
loop='true'
|
||||
autoPlay
|
||||
preload='none'>
|
||||
<source src={video} type='video/mp4' />
|
||||
</video>
|
||||
)}
|
||||
<img
|
||||
<LazyImage
|
||||
className='w-full h-full absolute object-cover group-hover:scale-105 duration-100 transition-all'
|
||||
src={img}
|
||||
priority
|
||||
alt={title}
|
||||
fill='full'
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -43,7 +43,9 @@ export const GameListRelate = ({ posts }) => {
|
||||
const GameItem = ({ item }) => {
|
||||
const { title } = item
|
||||
const [showType, setShowType] = useState('img') // img or video
|
||||
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
|
||||
const url = checkContainHttp(item.slug)
|
||||
? sliceUrlFromHttp(item.slug)
|
||||
: `${siteConfig('SUB_PATH', '')}/${item.slug}`
|
||||
|
||||
const img = item?.pageCoverThumbnail
|
||||
const video = item?.ext?.video
|
||||
@@ -60,15 +62,19 @@ const GameItem = ({ item }) => {
|
||||
title={title}
|
||||
className={`card-single w-24 h-24 relative shadow rounded-md overflow-hidden flex justify-center items-center
|
||||
group hover:border-purple-400`}>
|
||||
<div className='text-sm text-center absolute bottom-0 invisible group-hover:bottom-2 group-hover:visible transition-all duration-200 text-white z-30'>
|
||||
<div className='text-xs text-center absolute bottom-0 invisible group-hover:bottom-2 group-hover:visible transition-all duration-200 text-white z-30'>
|
||||
{title}
|
||||
</div>
|
||||
<div className='h-1/2 w-full absolute left-0 bottom-0 z-20 opacity-0 group-hover:opacity-75 transition-all duration-200'>
|
||||
<div className='h-2/3 w-full absolute left-0 bottom-0 z-20 opacity-0 group-hover:opacity-75 transition-all duration-200'>
|
||||
<div className='h-full w-full absolute bg-gradient-to-b from-transparent to-black'></div>
|
||||
</div>
|
||||
|
||||
{showType === 'video' && (
|
||||
<video className={`z-10 object-cover w-full h-24 absolute overflow-hidden`} loop='true' autoPlay preload='none'>
|
||||
<video
|
||||
className={`z-10 object-cover w-full h-24 absolute overflow-hidden`}
|
||||
loop='true'
|
||||
autoPlay
|
||||
preload='none'>
|
||||
<source src={video} type='video/mp4' />
|
||||
</video>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { useGameGlobal } from '..'
|
||||
|
||||
@@ -32,7 +32,7 @@ export const GameListRecent = ({ maxCount = 14 }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='game-list-recent-wrapper w-full max-w-full overflow-x-auto pt-4 px-2'>
|
||||
<div className='game-list-recent-wrapper w-full max-w-full overflow-x-auto pt-4 px-2 md:px-0'>
|
||||
<div className='game-grid md:flex grid grid-flow-col gap-2'>
|
||||
{components?.map((ItemComponent, index) => {
|
||||
return ItemComponent
|
||||
@@ -49,17 +49,43 @@ export const GameListRecent = ({ maxCount = 14 }) => {
|
||||
* @returns
|
||||
*/
|
||||
const GameItem = ({ item }) => {
|
||||
const router = useRouter()
|
||||
const { recentGames, setRecentGames } = useGameGlobal()
|
||||
const { title } = item || {}
|
||||
const [showType, setShowType] = useState('img') // img or video
|
||||
const url = checkContainHttp(item.slug)
|
||||
? sliceUrlFromHttp(item.slug)
|
||||
: `${siteConfig('SUB_PATH', '')}/${item.slug}`
|
||||
|
||||
const [isClockVisible, setClockVisible] = useState(true)
|
||||
const toggleIcons = () => {
|
||||
setClockVisible(!isClockVisible)
|
||||
}
|
||||
/**
|
||||
* 移除最近
|
||||
*/
|
||||
const removeRecent = () => {
|
||||
const updatedRecentGames = deepClone(recentGames) // 创建一个 recentGames 的副本
|
||||
const indexToRemove = updatedRecentGames.findIndex(
|
||||
game => game?.title === item.title
|
||||
) // 找到要移除的项的索引
|
||||
if (indexToRemove !== -1) {
|
||||
updatedRecentGames.splice(indexToRemove, 1) // 使用 splice 方法删除项
|
||||
setRecentGames(updatedRecentGames) // 更新 recentGames 状态
|
||||
localStorage.setItem('recent_games', JSON.stringify(updatedRecentGames))
|
||||
}
|
||||
}
|
||||
|
||||
const handleButtonClick = () => {
|
||||
router.push(url) // 如果是 Next.js
|
||||
}
|
||||
|
||||
const img = item?.pageCoverThumbnail
|
||||
const video = item?.ext?.video
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`${url}`}
|
||||
<div
|
||||
onClick={handleButtonClick}
|
||||
onMouseOver={() => {
|
||||
setShowType('video')
|
||||
}}
|
||||
@@ -67,11 +93,23 @@ const GameItem = ({ item }) => {
|
||||
setShowType('img')
|
||||
}}
|
||||
title={title}
|
||||
className={`card-single h-28 w-28 relative shadow rounded-md overflow-hidden flex justify-center items-center
|
||||
group hover:border-purple-400`}>
|
||||
<div className='absolute right-0.5 top-1 z-20'>
|
||||
<i className='fas fa-clock-rotate-left w-6 h-6 flex items-center justify-center shadow rounded-full bg-white text-blue-500 text-sm' />
|
||||
</div>
|
||||
className={`cursor-pointer card-single h-28 w-28 relative shadow rounded-md overflow-hidden flex justify-center items-center
|
||||
group hover:border-purple-400`}>
|
||||
<button
|
||||
className='absolute right-0.5 top-1 z-20'
|
||||
onClick={e => {
|
||||
e.stopPropagation() // 阻止事件冒泡,防止触发父级元素的点击事件
|
||||
removeRecent()
|
||||
}}
|
||||
onMouseEnter={toggleIcons}
|
||||
onMouseLeave={toggleIcons}>
|
||||
{isClockVisible ? (
|
||||
<i className='fas fa-clock-rotate-left w-6 h-6 flex items-center justify-center shadow rounded-full bg-white text-blue-500 text-sm'></i>
|
||||
) : (
|
||||
<i className='fas fa-trash-can w-6 h-6 flex items-center justify-center shadow rounded-full bg-white text-red-500 text-sm'></i>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className='absolute text-sm bottom-2 transition-all duration-200 text-white z-30'>
|
||||
{title}
|
||||
</div>
|
||||
@@ -93,6 +131,6 @@ const GameItem = ({ item }) => {
|
||||
src={img}
|
||||
alt={title}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ function GroupCategory({ currentCategory, categoryOptions }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex items-center'>
|
||||
<Link className='mx-2' href='/category'>
|
||||
<i className='fas fa-bars' />
|
||||
</Link>
|
||||
<div id='category-list' className='dark:border-gray-600 flex flex-wrap py-1'>
|
||||
<div
|
||||
id='category-list'
|
||||
className='dark:border-gray-600 flex flex-wrap py-1'>
|
||||
{categoryOptions.map(category => {
|
||||
const selected = currentCategory === category.name
|
||||
return (
|
||||
@@ -34,7 +36,7 @@ function GroupCategory({ currentCategory, categoryOptions }) {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import TagItemMini from './TagItemMini'
|
||||
function GroupTag({ tagOptions, currentTag }) {
|
||||
if (!tagOptions) return <></>
|
||||
return (
|
||||
<>
|
||||
<div className='flex items-center'>
|
||||
<Link href='/tag'>
|
||||
<i className='fas fa-tags p-2' />
|
||||
</Link>
|
||||
@@ -21,7 +21,7 @@ function GroupTag({ tagOptions, currentTag }) {
|
||||
return <TagItemMini key={tag.name} tag={tag} selected={selected} />
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ import Logo from './Logo'
|
||||
* 顶栏
|
||||
* @returns
|
||||
*/
|
||||
export default function Header() {
|
||||
export default function Header(props) {
|
||||
const { siteInfo } = props
|
||||
const { setSideBarVisible } = useGameGlobal()
|
||||
return (
|
||||
<header className='z-20'>
|
||||
<div className='w-full h-16 rounded-md bg-white shadow-md hover:shadow-xl transition-shadow duration-200 dark:bg-[#1F2030] flex justify-between items-center px-4'>
|
||||
<Logo />
|
||||
<Logo siteInfo={siteInfo} />
|
||||
|
||||
<button
|
||||
className='flex xl:hidden'
|
||||
|
||||
@@ -2,12 +2,19 @@ import { siteConfig } from '@/lib/config'
|
||||
import Link from 'next/link'
|
||||
|
||||
/* eslint-disable @next/next/no-html-link-for-pages */
|
||||
export default function Logo() {
|
||||
export default function Logo({ siteInfo }) {
|
||||
return (
|
||||
<Link passHref href='/' className='logo rounded cursor-pointer flex flex-col items-center'>
|
||||
<Link
|
||||
passHref
|
||||
href='/'
|
||||
className='logo rounded cursor-pointer flex flex-col items-center'>
|
||||
<div className='w-full'>
|
||||
<h1 className='text-2xl dark:text-white font-bold font-serif'>{siteConfig('TITLE')}</h1>
|
||||
<h2 className='text-xs text-gray-400 whitespace-nowrap'>{siteConfig('BIO')}</h2>
|
||||
<h1 className='text-2xl dark:text-white font-bold font-serif'>
|
||||
{siteInfo?.title}
|
||||
</h1>
|
||||
<h2 className='text-xs text-gray-400 whitespace-nowrap'>
|
||||
{siteConfig('BIO')}
|
||||
</h2>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function PostInfo(props) {
|
||||
const { post } = props
|
||||
|
||||
return (
|
||||
<section className='flex-wrap flex mt-2 text-gray--600 dark:text-gray-400 font-light leading-8'>
|
||||
<section className='flex-wrap flex m-2 text-gray--600 dark:text-gray-400 font-light leading-8'>
|
||||
<div>
|
||||
<div>
|
||||
{post?.type !== 'Page' && (
|
||||
|
||||
@@ -9,7 +9,7 @@ import Logo from './Logo'
|
||||
/**
|
||||
* 侧拉抽屉的内容
|
||||
*/
|
||||
export default function SideBarContent({ allNavPages }) {
|
||||
export default function SideBarContent({ allNavPages, siteInfo }) {
|
||||
const { sideBarVisible, setSideBarVisible, filterGames, setFilterGames } =
|
||||
useGameGlobal()
|
||||
const inputRef = useRef(null) // 创建对输入框的引用
|
||||
@@ -50,7 +50,7 @@ export default function SideBarContent({ allNavPages }) {
|
||||
return (
|
||||
<div className='px-3'>
|
||||
<div className='py-2 flex justify-between'>
|
||||
<Logo />
|
||||
<Logo siteInfo={siteInfo} />
|
||||
<button
|
||||
onClick={() => {
|
||||
setSideBarVisible(false)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import Comment from '@/components/Comment'
|
||||
import { Draggable } from '@/components/Draggable'
|
||||
import { AdSlot } from '@/components/GoogleAdsense'
|
||||
import replaceSearchResult from '@/components/Mark'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
@@ -17,9 +16,8 @@ import BlogArchiveItem from './components/BlogArchiveItem'
|
||||
import { BlogListPage } from './components/BlogListPage'
|
||||
import { BlogListScroll } from './components/BlogListScroll'
|
||||
import BlogPostBar from './components/BlogPostBar'
|
||||
import DownloadButton from './components/DownloadButton'
|
||||
import { Footer } from './components/Footer'
|
||||
import FullScreenButton from './components/FullScreenButton'
|
||||
import GameEmbed from './components/GameEmbed'
|
||||
import { GameListIndexCombine } from './components/GameListIndexCombine'
|
||||
import { GameListRelate } from './components/GameListRealate'
|
||||
import { GameListRecent } from './components/GameListRecent'
|
||||
@@ -46,7 +44,7 @@ export const useGameGlobal = () => useContext(ThemeGlobalGame)
|
||||
* @constructor
|
||||
*/
|
||||
const LayoutBase = props => {
|
||||
const { allNavPages, children } = props
|
||||
const { allNavPages, children, siteInfo } = props
|
||||
const searchModal = useRef(null)
|
||||
// 在列表中进行实时过滤
|
||||
const [filterKey, setFilterKey] = useState('')
|
||||
@@ -64,11 +62,6 @@ const LayoutBase = props => {
|
||||
const [sideBarVisible, setSideBarVisible] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setRecentGames(
|
||||
localStorage.getItem('recent_games')
|
||||
? JSON.parse(localStorage.getItem('recent_games'))
|
||||
: []
|
||||
)
|
||||
loadWowJS()
|
||||
}, [])
|
||||
|
||||
@@ -87,7 +80,7 @@ const LayoutBase = props => {
|
||||
}}>
|
||||
<div
|
||||
id='theme-game'
|
||||
className={`${siteConfig('FONT_STYLE')} w-full h-full min-h-screen justify-center bg-[#83FFE7] dark:bg-black dark:text-gray-300 scroll-smooth`}>
|
||||
className={`${siteConfig('FONT_STYLE')} w-full h-full min-h-screen justify-center dark:bg-black dark:bg-opacity-50 dark:text-gray-300 scroll-smooth`}>
|
||||
<Style />
|
||||
|
||||
{/* 左右布局 */}
|
||||
@@ -99,7 +92,7 @@ const LayoutBase = props => {
|
||||
<div className='py-4 px-2 sticky top-0 h-screen flex flex-col justify-between'>
|
||||
<div className='select-none'>
|
||||
{/* 抬头logo等 */}
|
||||
<Header />
|
||||
<Header siteInfo={siteInfo} />
|
||||
{/* 菜单栏 */}
|
||||
<MenuList {...props} />
|
||||
</div>
|
||||
@@ -112,9 +105,8 @@ const LayoutBase = props => {
|
||||
</div>
|
||||
|
||||
{/* 右侧 */}
|
||||
<main className='flex-grow w-full h-full flex flex-col min-h-screen overflow-x-auto'>
|
||||
<main className='flex-grow w-full h-full flex flex-col min-h-screen overflow-x-auto md:p-2'>
|
||||
<div className='flex-grow h-full'>{children}</div>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
</div>
|
||||
@@ -124,7 +116,7 @@ const LayoutBase = props => {
|
||||
onClose={() => {
|
||||
setSideBarVisible(false)
|
||||
}}>
|
||||
<SideBarContent {...props} />
|
||||
<SideBarContent siteInfo={siteInfo} {...props} />
|
||||
</SideBarDrawer>
|
||||
</div>
|
||||
</ThemeGlobalGame.Provider>
|
||||
@@ -138,19 +130,25 @@ const LayoutBase = props => {
|
||||
* @returns
|
||||
*/
|
||||
const LayoutIndex = props => {
|
||||
const { tagOptions, currentTag, categoryOptions, currentCategory } = props
|
||||
const { tagOptions, currentTag, categoryOptions, currentCategory, siteInfo } =
|
||||
props
|
||||
return (
|
||||
<>
|
||||
{/* 首页移动端顶部导航 */}
|
||||
<div className='p-2 xl:hidden'>
|
||||
<Header />
|
||||
<Header siteInfo={siteInfo} />
|
||||
</div>
|
||||
{/* 最近游戏 */}
|
||||
<GameListRecent />
|
||||
{/* 游戏列表 */}
|
||||
<LayoutPostList {...props} />
|
||||
|
||||
{/* 主区域下方 */}
|
||||
{/* 广告 */}
|
||||
<div className='w-full'>
|
||||
<AdSlot type='in-article' />
|
||||
</div>
|
||||
|
||||
{/* 主区域下方 导览 */}
|
||||
<div className='w-full bg-white dark:bg-hexo-black-gray rounded-lg p-2'>
|
||||
{/* 标签汇总 */}
|
||||
<GroupCategory
|
||||
@@ -159,16 +157,8 @@ const LayoutIndex = props => {
|
||||
/>
|
||||
<hr />
|
||||
<GroupTag tagOptions={tagOptions} currentTag={currentTag} />
|
||||
</div>
|
||||
|
||||
{/* 广告 */}
|
||||
<div className='w-full'>
|
||||
<AdSlot type='in-article' />
|
||||
</div>
|
||||
|
||||
{/* 站点公告信息 */}
|
||||
<div className='w-full bg-white dark:bg-hexo-black-gray rounded-lg p-2'>
|
||||
<Announcement {...props} />
|
||||
{/* 站点公告信息 */}
|
||||
<Announcement {...props} className='p-2' />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -278,27 +268,25 @@ const LayoutArchive = props => {
|
||||
* @returns
|
||||
*/
|
||||
const LayoutSlug = props => {
|
||||
const { setRecentGames } = useGameGlobal()
|
||||
const { post, siteInfo, allNavPages, recommendPosts, lock, validPassword } =
|
||||
props
|
||||
const game = deepClone(post)
|
||||
const [loading, setLoading] = useState(true)
|
||||
// const [url, setUrl] = useState(game?.ext?.href)
|
||||
|
||||
const relateGames = recommendPosts
|
||||
const randomGames = shuffleArray(deepClone(allNavPages))
|
||||
|
||||
// 初始化可安装应用
|
||||
initialPWA(game, siteInfo)
|
||||
initialPWA(post, siteInfo)
|
||||
|
||||
// 将当前游戏加入到最近游玩
|
||||
useEffect(() => {
|
||||
// 更新最新游戏
|
||||
const recentGames = localStorage.getItem('recent_games')
|
||||
? JSON.parse(localStorage.getItem('recent_games'))
|
||||
: []
|
||||
|
||||
const existedIndex = recentGames.findIndex(item => item?.id === game?.id)
|
||||
const existedIndex = recentGames.findIndex(item => item?.id === post?.id)
|
||||
if (existedIndex === -1) {
|
||||
recentGames.unshift(game) // 将游戏插入到数组头部
|
||||
recentGames.unshift(post) // 将游戏插入到数组头部
|
||||
} else {
|
||||
// 如果游戏已存在于数组中,将其移至数组头部
|
||||
const existingGame = recentGames.splice(existedIndex, 1)[0]
|
||||
@@ -306,126 +294,37 @@ const LayoutSlug = props => {
|
||||
}
|
||||
localStorage.setItem('recent_games', JSON.stringify(recentGames))
|
||||
|
||||
const iframe = document.getElementById('game-wrapper')
|
||||
|
||||
// 定义一个函数来处理iframe加载成功事件
|
||||
function iframeLoaded() {
|
||||
if (game) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定加载事件
|
||||
if (iframe?.attachEvent) {
|
||||
iframe?.attachEvent('onload', iframeLoaded)
|
||||
} else {
|
||||
if (iframe) iframe.onload = iframeLoaded
|
||||
}
|
||||
|
||||
// 更改iFrame的title
|
||||
if (
|
||||
document
|
||||
?.getElementById('game-wrapper')
|
||||
?.contentDocument.querySelector('title')?.textContent
|
||||
) {
|
||||
document
|
||||
.getElementById('game-wrapper')
|
||||
.contentDocument.querySelector('title').textContent = `${
|
||||
game?.title || ''
|
||||
} - Play ${game?.title || ''} on ${siteConfig('TITLE')}`
|
||||
}
|
||||
}, [game])
|
||||
setRecentGames(recentGames)
|
||||
}, [post])
|
||||
|
||||
return (
|
||||
<>
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && (
|
||||
<div id='article-wrapper' className='md:px-2'>
|
||||
{/* 游戏区域 */}
|
||||
<div className='game-detail-wrapper w-full grow flex md:px-2'>
|
||||
{/* 移动端返回主页按钮 */}
|
||||
<Draggable stick='left'>
|
||||
<div
|
||||
style={{ left: '0px', top: '1rem' }}
|
||||
className='fixed xl:hidden group space-x-1 flex items-center z-20 pr-3 pl-1 bg-[#202030] rounded-r-2xl shadow-lg '>
|
||||
<Link
|
||||
href='/'
|
||||
className='px-1 py-3 hover:scale-125 duration-200 transition-all'
|
||||
passHref>
|
||||
<i className='fas fa-arrow-left' />
|
||||
</Link>{' '}
|
||||
<span
|
||||
className='text-white font-serif'
|
||||
onClick={() => {
|
||||
document.querySelector('.game-info').scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'end',
|
||||
inline: 'nearest'
|
||||
})
|
||||
}}>
|
||||
G
|
||||
</span>
|
||||
</div>
|
||||
</Draggable>
|
||||
<div id='article-wrapper'>
|
||||
<div className='game-detail-wrapper w-full grow flex'>
|
||||
<div className={`w-full md:py-2`}>
|
||||
{/* 游戏窗口 */}
|
||||
<GameEmbed post={post} siteInfo={siteInfo} />
|
||||
|
||||
<div className='w-full py-1 md:py-4'>
|
||||
{/* 游戏区 */}
|
||||
<div className='bg-black w-full xl:h-[calc(100vh-8rem)] h-screen rounded-md relative'>
|
||||
{/* Loading遮罩 */}
|
||||
{loading && (
|
||||
<div className='absolute z-20 w-full xl:h-[calc(100vh-8rem)] h-screen rounded-md overflow-hidden '>
|
||||
<div className='z-20 absolute bg-black bg-opacity-75 w-full h-full flex flex-col gap-4 justify-center items-center'>
|
||||
<h2 className='text-3xl text-white flex gap-2 items-center'>
|
||||
<i className='fas fa-spinner animate-spin'></i>
|
||||
{siteInfo?.title || siteConfig('TITLE')}
|
||||
</h2>
|
||||
<h3 className='text-xl text-white'>
|
||||
{siteInfo?.description || siteConfig('DESCRIPTION')}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* 游戏封面图 */}
|
||||
{game?.pageCoverThumbnail && (
|
||||
<img
|
||||
src={game?.pageCoverThumbnail}
|
||||
className='w-full h-full object-cover blur-md absolute top-0 left-0 z-0'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<iframe
|
||||
id='game-wrapper'
|
||||
className={`w-full xl:h-[calc(100vh-8rem)] h-screen md:rounded-md overflow-hidden ${game?.ext?.href ? '' : 'hidden'}`}
|
||||
style={{
|
||||
position: 'relative'
|
||||
}}
|
||||
src={game?.ext?.href}></iframe>
|
||||
|
||||
{/* 游戏窗口装饰器 */}
|
||||
{game && !loading && (
|
||||
<div className='game-decorator bg-[#0B0D14] right-0 bottom-0 flex justify-center z-10 md:absolute'>
|
||||
<DownloadButton />
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 游戏资讯 */}
|
||||
<div className='game-info dark:text-white py-4 px-2 md:px-0 mt-14 md:mt-0'>
|
||||
{/* 资讯 */}
|
||||
<div className='game-info dark:text-white py-2 px-2 md:px-0 mt-14 md:mt-0'>
|
||||
{/* 关联游戏 */}
|
||||
<div className='w-full'>
|
||||
<GameListRelate posts={relateGames} />
|
||||
</div>
|
||||
|
||||
{game && (
|
||||
<div className='bg-white shadow-md my-2 p-2 rounded-md dark:bg-black'>
|
||||
{/* 详情描述 */}
|
||||
{post && (
|
||||
<div className='bg-white shadow-md my-2 p-4 rounded-md dark:bg-black'>
|
||||
<PostInfo post={post} />
|
||||
<NotionPage post={post} />
|
||||
{/* 广告嵌入 */}
|
||||
<AdSlot />
|
||||
{/* 分享栏目 */}
|
||||
<ShareBar post={post} />
|
||||
{/* 评论区 */}
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,14 +5,21 @@
|
||||
* @returns
|
||||
*/
|
||||
const Style = () => {
|
||||
return <style jsx global>{`
|
||||
|
||||
// 底色
|
||||
.dark body{
|
||||
return (
|
||||
<style jsx global>{`
|
||||
// 底色
|
||||
.dark body {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
`}</style>
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 800 800'%3E%3Cdefs%3E%3CradialGradient id='a' cx='400' cy='400' r='50%25' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffffff'/%3E%3Cstop offset='1' stop-color='%230EF'/%3E%3C/radialGradient%3E%3CradialGradient id='b' cx='400' cy='400' r='70%25' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffffff'/%3E%3Cstop offset='1' stop-color='%230FF'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect fill='url(%23a)' width='800' height='800'/%3E%3Cg fill-opacity='.8'%3E%3Cpath fill='url(%23b)' d='M998.7 439.2c1.7-26.5 1.7-52.7 0.1-78.5L401 399.9c0 0 0-0.1 0-0.1l587.6-116.9c-5.1-25.9-11.9-51.2-20.3-75.8L400.9 399.7c0 0 0-0.1 0-0.1l537.3-265c-11.6-23.5-24.8-46.2-39.3-67.9L400.8 399.5c0 0 0-0.1-0.1-0.1l450.4-395c-17.3-19.7-35.8-38.2-55.5-55.5l-395 450.4c0 0-0.1 0-0.1-0.1L733.4-99c-21.7-14.5-44.4-27.6-68-39.3l-265 537.4c0 0-0.1 0-0.1 0l192.6-567.4c-24.6-8.3-49.9-15.1-75.8-20.2L400.2 399c0 0-0.1 0-0.1 0l39.2-597.7c-26.5-1.7-52.7-1.7-78.5-0.1L399.9 399c0 0-0.1 0-0.1 0L282.9-188.6c-25.9 5.1-51.2 11.9-75.8 20.3l192.6 567.4c0 0-0.1 0-0.1 0l-265-537.3c-23.5 11.6-46.2 24.8-67.9 39.3l332.8 498.1c0 0-0.1 0-0.1 0.1L4.4-51.1C-15.3-33.9-33.8-15.3-51.1 4.4l450.4 395c0 0 0 0.1-0.1 0.1L-99 66.6c-14.5 21.7-27.6 44.4-39.3 68l537.4 265c0 0 0 0.1 0 0.1l-567.4-192.6c-8.3 24.6-15.1 49.9-20.2 75.8L399 399.8c0 0 0 0.1 0 0.1l-597.7-39.2c-1.7 26.5-1.7 52.7-0.1 78.5L399 400.1c0 0 0 0.1 0 0.1l-587.6 116.9c5.1 25.9 11.9 51.2 20.3 75.8l567.4-192.6c0 0 0 0.1 0 0.1l-537.3 265c11.6 23.5 24.8 46.2 39.3 67.9l498.1-332.8c0 0 0 0.1 0.1 0.1l-450.4 395c17.3 19.7 35.8 38.2 55.5 55.5l395-450.4c0 0 0.1 0 0.1 0.1L66.6 899c21.7 14.5 44.4 27.6 68 39.3l265-537.4c0 0 0.1 0 0.1 0L207.1 968.3c24.6 8.3 49.9 15.1 75.8 20.2L399.8 401c0 0 0.1 0 0.1 0l-39.2 597.7c26.5 1.7 52.7 1.7 78.5 0.1L400.1 401c0 0 0.1 0 0.1 0l116.9 587.6c25.9-5.1 51.2-11.9 75.8-20.3L400.3 400.9c0 0 0.1 0 0.1 0l265 537.3c23.5-11.6 46.2-24.8 67.9-39.3L400.5 400.8c0 0 0.1 0 0.1-0.1l395 450.4c19.7-17.3 38.2-35.8 55.5-55.5l-450.4-395c0 0 0-0.1 0.1-0.1L899 733.4c14.5-21.7 27.6-44.4 39.3-68l-537.4-265c0 0 0-0.1 0-0.1l567.4 192.6c8.3-24.6 15.1-49.9 20.2-75.8L401 400.2c0 0 0-0.1 0-0.1L998.7 439.2z'/%3E%3C/g%3E%3C/svg%3E");
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
}
|
||||
`}</style>
|
||||
)
|
||||
}
|
||||
|
||||
export { Style }
|
||||
|
||||
@@ -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,90 +1,99 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import CONFIG from '../config'
|
||||
import { SVGLocation } from './svg/SVGLocation'
|
||||
import { SVGEmail } from './svg/SVGEmail'
|
||||
import { SVGLocation } from './svg/SVGLocation'
|
||||
|
||||
/* eslint-disable react/no-unescaped-entities */
|
||||
export const Contact = () => {
|
||||
return <>
|
||||
{/* <!-- ====== Contact Start ====== --> */}
|
||||
<section id="contact" className="relative py-20 md:py-[120px]">
|
||||
<div
|
||||
className="absolute left-0 top-0 -z-[1] h-full w-full dark:bg-dark"
|
||||
></div>
|
||||
<div
|
||||
className="absolute left-0 top-0 -z-[1] h-1/2 w-full bg-[#E9F9FF] dark:bg-dark-700 lg:h-[45%] xl:h-1/2"
|
||||
></div>
|
||||
<div className="container px-4">
|
||||
<div className="-mx-4 flex flex-wrap items-center">
|
||||
|
||||
{/* 联系方式左侧文字 */}
|
||||
<div className="w-full px-4 lg:w-7/12 xl:w-8/12">
|
||||
<div className="ud-contact-content-wrapper">
|
||||
<div className="ud-contact-title mb-12 lg:mb-[150px]">
|
||||
<span
|
||||
className="mb-6 block text-base font-medium text-dark dark:text-white"
|
||||
>
|
||||
{siteConfig('STARTER_CONTACT_TITLE', null, CONFIG)}
|
||||
</span>
|
||||
<h2
|
||||
className="max-w-[260px] text-[35px] font-semibold leading-[1.14] text-dark dark:text-white"
|
||||
>
|
||||
{siteConfig('STARTER_CONTACT_TEXT', null, CONFIG)}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="mb-12 flex flex-wrap justify-between lg:mb-0">
|
||||
<div className="mb-8 flex w-[330px] max-w-full">
|
||||
<div className="mr-6 text-[32px] text-primary">
|
||||
<SVGLocation/>
|
||||
</div>
|
||||
<div>
|
||||
<h5
|
||||
className="mb-[18px] text-lg font-semibold text-dark dark:text-white"
|
||||
>
|
||||
{siteConfig('STARTER_CONTACT_LOCATION_TITLE', null, CONFIG)}
|
||||
</h5>
|
||||
<p className="text-base text-body-color dark:text-dark-6">
|
||||
{siteConfig('STARTER_CONTACT_LOCATION_TEXT', null, CONFIG)}
|
||||
</p>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
{/* <!-- ====== Contact Start ====== --> */}
|
||||
<section id='contact' className='relative py-20 md:py-[120px]'>
|
||||
<div className='absolute left-0 top-0 -z-[1] h-full w-full dark:bg-dark'></div>
|
||||
<div className='absolute left-0 top-0 -z-[1] h-1/2 w-full bg-[#E9F9FF] dark:bg-dark-700 lg:h-[45%] xl:h-1/2'></div>
|
||||
<div className='container px-4'>
|
||||
<div className='-mx-4 flex flex-wrap items-center'>
|
||||
{/* 联系方式左侧文字 */}
|
||||
<div className='w-full px-4 lg:w-7/12 xl:w-8/12'>
|
||||
<div className='ud-contact-content-wrapper'>
|
||||
<div className='ud-contact-title mb-12 lg:mb-[150px]'>
|
||||
<span className='mb-6 block text-base font-medium text-dark dark:text-white'>
|
||||
{siteConfig('STARTER_CONTACT_TITLE', null, CONFIG)}
|
||||
</span>
|
||||
<h2 className='max-w-[260px] text-[35px] font-semibold leading-[1.14] text-dark dark:text-white'>
|
||||
{siteConfig('STARTER_CONTACT_TEXT', null, CONFIG)}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="mb-8 flex w-[330px] max-w-full">
|
||||
<div className="mr-6 text-[32px] text-primary">
|
||||
<SVGEmail/>
|
||||
<div className='mb-12 flex flex-wrap justify-between lg:mb-0'>
|
||||
<div className='mb-8 flex w-[330px] max-w-full'>
|
||||
<div className='mr-6 text-[32px] text-primary'>
|
||||
<SVGLocation />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className='mb-[18px] text-lg font-semibold text-dark dark:text-white'>
|
||||
{siteConfig(
|
||||
'STARTER_CONTACT_LOCATION_TITLE',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
</h5>
|
||||
<p className='text-base text-body-color dark:text-dark-6'>
|
||||
{siteConfig(
|
||||
'STARTER_CONTACT_LOCATION_TEXT',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5
|
||||
className="mb-[18px] text-lg font-semibold text-dark dark:text-white"
|
||||
>
|
||||
{siteConfig('STARTER_CONTACT_EMAIL_TITLE', null, CONFIG)}
|
||||
</h5>
|
||||
<p className="text-base text-body-color dark:text-dark-6">
|
||||
{siteConfig('STARTER_CONTACT_EMAIL_TEXT', null, CONFIG)}
|
||||
</p>
|
||||
<div className='mb-8 flex w-[330px] max-w-full'>
|
||||
<div className='mr-6 text-[32px] text-primary'>
|
||||
<SVGEmail />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className='mb-[18px] text-lg font-semibold text-dark dark:text-white'>
|
||||
{siteConfig(
|
||||
'STARTER_CONTACT_EMAIL_TITLE',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
</h5>
|
||||
<p className='text-base text-body-color dark:text-dark-6'>
|
||||
{siteConfig('STARTER_CONTACT_EMAIL_TEXT', null, CONFIG)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{siteConfig('STARTER_CONTACT_MSG_EXTERNAL_URL', null, CONFIG) && (
|
||||
<>
|
||||
{/* 联系方式右侧留言 */}
|
||||
<div className='w-full px-4 lg:w-5/12 xl:w-4/12'>
|
||||
<div
|
||||
className='wow fadeInUp rounded-lg bg-white px-8 py-10 shadow-testimonial dark:bg-dark-2 dark:shadow-none sm:px-10 sm:py-12 md:p-[60px] lg:p-10 lg:px-10 lg:py-12 2xl:p-[60px]'
|
||||
data-wow-delay='.2s'>
|
||||
{/* 自定义的留言表单 、 需要对接接口 */}
|
||||
{/* <MessageForm/> */}
|
||||
{/* 嵌入外部表单 */}
|
||||
<iframe
|
||||
src={siteConfig(
|
||||
'STARTER_CONTACT_MSG_EXTERNAL_URL',
|
||||
null,
|
||||
CONFIG
|
||||
)}
|
||||
width='100%'
|
||||
height='500px'
|
||||
frameBorder='0'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 联系方式右侧留言 */}
|
||||
<div className="w-full px-4 lg:w-5/12 xl:w-4/12">
|
||||
<div
|
||||
className="wow fadeInUp rounded-lg bg-white px-8 py-10 shadow-testimonial dark:bg-dark-2 dark:shadow-none sm:px-10 sm:py-12 md:p-[60px] lg:p-10 lg:px-10 lg:py-12 2xl:p-[60px]"
|
||||
data-wow-delay=".2s"
|
||||
>
|
||||
{/* 自定义的留言表单 、 需要对接接口 */}
|
||||
{/* <MessageForm/> */}
|
||||
{/* 嵌入外部表单 */}
|
||||
<iframe src="https://noteforms.com/forms/yfctc7" width="100%" height="500px" frameBorder="0"></iframe>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* <!-- ====== Contact End ====== --> */}
|
||||
|
||||
</>
|
||||
</section>
|
||||
{/* <!-- ====== Contact End ====== --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user