theme-game 部分样式

This commit is contained in:
tangly1024.com
2024-03-20 18:40:06 +08:00
parent 54a3b3746a
commit d2508c82e5
21 changed files with 539 additions and 574 deletions

View File

@@ -76,9 +76,13 @@ nav {
}
.shadow-card {
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px,
rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px,
rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
box-shadow:
rgba(0, 0, 0, 0.07) 0px 1px 2px,
rgba(0, 0, 0, 0.07) 0px 2px 4px,
rgba(0, 0, 0, 0.07) 0px 4px 8px,
rgba(0, 0, 0, 0.07) 0px 8px 16px,
rgba(0, 0, 0, 0.07) 0px 16px 32px,
rgba(0, 0, 0, 0.07) 0px 32px 64px;
}
.gt-meta {
@@ -106,7 +110,6 @@ nav {
backdrop-filter: blur(10px);
}
.medium-zoom-overlay {
background: none !important;
/* background: rgba(0, 0, 0, 0.01) none repeat scroll 0% 0% !important; */
@@ -157,7 +160,7 @@ nav {
/* twikoo 评论区超链接样式 */
.tk-main a {
@apply text-blue-700
@apply text-blue-700;
}
/* twikoo 内置的 element-ui 加载样式 */
@@ -167,7 +170,7 @@ nav {
/* Webmention style */
.webmention-block {
background: rgba(0, 116, 222, .2);
background: rgba(0, 116, 222, 0.2);
padding: 1rem 2rem;
border-radius: 5px;
}
@@ -176,11 +179,11 @@ nav {
font-style: italic;
font-weight: 700;
font-size: 16px;
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
.webmention-block-intro a {
color: #0000EE;
color: #0000ee;
text-decoration: underline;
}
@@ -197,14 +200,14 @@ nav {
.webmention-counts .count {
font-weight: bold;
margin-right: .2rem;
margin-right: 0.2rem;
}
/* .webmention-counts .counts > span {
margin-right: .8rem;
} */
.webmention-counts .counts>span:not(:last-child):after {
content: "";
.webmention-counts .counts > span:not(:last-child):after {
content: '';
}
a.avatar-wrapper {
@@ -221,7 +224,7 @@ a.avatar-wrapper {
.avatar {
border-radius: 50%;
margin: 0;
border: 3px solid rgba(0, 116, 222, .5);
border: 3px solid rgba(0, 116, 222, 0.5);
}
.replies {
@@ -235,7 +238,7 @@ a.avatar-wrapper {
position: relative;
padding: 0;
align-items: flex-start;
margin-top: .6rem;
margin-top: 0.6rem;
}
.reply p {
@@ -255,4 +258,9 @@ a.avatar-wrapper {
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
}
.writing-vertical {
writing-mode: vertical-rl; /* 竖向排列从右向左 */
text-orientation: upright; /* 文字方向正常 */
}

View File

@@ -1,33 +0,0 @@
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
/**
* 加密文章校验组件
* @param {password, validPassword} props
* @param password 正确的密码
* @param validPassword(bool) 回调函数校验正确回调入参为true
* @returns
*/
export const ArticleFooter = props => {
const router = useRouter()
const { locale } = useGlobal()
return <div className="flex justify-between font-medium text-gray-500 dark:text-gray-400">
<a>
<button
onClick={() => router.push('/')}
className="mt-2 cursor-pointer hover:text-black dark:hover:text-gray-100"
>
{locale.POST.BACK}
</button>
</a>
<a>
<button
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className="mt-2 cursor-pointer hover:text-black dark:hover:text-gray-100"
>
{locale.POST.TOP}
</button>
</a>
</div>
}

View File

@@ -1,56 +0,0 @@
import Image from 'next/image'
import TagItem from './TagItem'
import md5 from 'js-md5'
import { siteConfig } from '@/lib/config'
import NotionIcon from '@/components/NotionIcon'
export const ArticleInfo = (props) => {
const { post } = props
const emailHash = md5(siteConfig('CONTACT_EMAIL', '#'))
return <section className="flex-wrap flex mt-2 text-gray--600 dark:text-gray-400 font-light leading-8">
<div>
<h1 className="font-bold text-3xl text-black dark:text-white">
<NotionIcon icon={post?.pageIcon} />{post?.title}
</h1>
{post?.type !== 'Page' && <>
<nav className="flex mt-7 items-start text-gray-500 dark:text-gray-400">
<div className="flex mb-4">
<a href={siteConfig('CONTACT_GITHUB', '#')} className="flex">
<Image
alt={siteConfig('AUTHOR')}
width={24}
height={24}
src={`https://gravatar.com/avatar/${emailHash}`}
className="rounded-full"
/>
<p className="ml-2 md:block">{siteConfig('AUTHOR')}</p>
</a>
<span className="block">&nbsp;/&nbsp;</span>
</div>
<div className="mr-2 mb-4 md:ml-0">
{post?.publishDay}
</div>
{post?.tags && (
<div className="flex flex-nowrap max-w-full overflow-x-auto article-tags">
{post?.tags.map(tag => (
<TagItem key={tag} tag={tag} />
))}
</div>
)}
<span className="hidden busuanzi_container_page_pv mr-2">
<i className='mr-1 fas fa-eye' />
&nbsp;
<span className="mr-2 busuanzi_value_page_pv" />
</span>
</nav>
</>}
</div>
</section>
}

View File

@@ -1,51 +1,13 @@
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { GameListIndexCombine } from './GameListIndexCombine'
export const BlogListPage = props => {
const { page = 1, posts, postCount } = props
const { locale } = useGlobal()
const router = useRouter()
const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
const currentPage = +page
const showPrev = currentPage > 1
const showNext = currentPage < totalPage && posts?.length > 0
const pagePrefix = router.asPath
.split('?')[0]
.replace(/\/page\/[1-9]\d*/, '')
.replace(/\/$/, '')
return (
<div className='w-full md:pr-12 my-6'>
<div id='posts-wrapper'>
<>
<div id='posts-wrapper' className='my-4 select-none'>
<GameListIndexCombine {...props} />
</div>
<div className='flex justify-between text-xs'>
<Link
href={{
pathname: currentPage - 1 === 1 ? `${pagePrefix}/` : `${pagePrefix}/page/${currentPage - 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
className={`${showPrev ? ' ' : ' invisible block pointer-events-none '}no-underline py-2 px-3 rounded`}>
<button rel='prev' className='block cursor-pointer'>
{locale.PAGINATION.PREV}
</button>
</Link>
<Link
href={{
pathname: `${pagePrefix}/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
className={`${showNext ? ' ' : 'invisible pointer-events-none '} no-underline py-2 px-3 rounded`}>
<button rel='next' className='block cursor-pointer'>
{locale.PAGINATION.NEXT}
</button>
</Link>
</div>
</div>
{/* 这里不显示分页组件,首页只展示部分即可 */}
</>
)
}

View File

@@ -1,9 +1,9 @@
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import throttle from 'lodash.throttle'
import { deepClone } from '@/lib/utils'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { deepClone } from '@/lib/utils'
import throttle from 'lodash.throttle'
import { useCallback, useEffect, useRef, useState } from 'react'
import { GameListIndexCombine } from './GameListIndexCombine'
export const BlogListScroll = props => {
const { posts } = props
@@ -12,9 +12,8 @@ export const BlogListScroll = props => {
const [page, updatePage] = useState(1)
let hasMore = false
const postsToShow = posts && Array.isArray(posts)
? deepClone(posts).slice(0, parseInt(siteConfig('POSTS_PER_PAGE')) * page)
: []
const postsToShow =
posts && Array.isArray(posts) ? deepClone(posts).slice(0, parseInt(siteConfig('POSTS_PER_PAGE')) * page) : []
if (posts) {
const totalCount = posts.length
@@ -28,56 +27,33 @@ export const BlogListScroll = props => {
const targetRef = useRef(null)
// 监听滚动自动分页加载
const scrollTrigger = useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
const scrollTrigger = useCallback(
throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? targetRef.current.clientHeight : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500)
)
useEffect(() => {
window.addEventListener('scroll', scrollTrigger)
return () => {
window.removeEventListener('scroll', scrollTrigger)
}
})
return (
<div id="posts-wrapper" className="w-full md:pr-12 mb-12" ref={targetRef}>
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link
href={`/${p.slug}`}
className="text-black text-xl md:text-2xl no-underline hover:underline">
{p.title}
</Link>
</h2>
<>
<div id='posts-wrapper' className='my-4' ref={targetRef}>
<GameListIndexCombine posts={postsToShow} />
</div>
<div className="mb-4 text-sm text-gray-700">
by <a href="#" className="text-gray-700">{siteConfig('AUTHOR')}</a> on {p.date?.start_date || p.createdTime}
<span className="font-bold mx-1"> | </span>
<a href="#" className="text-gray-700">{p.category}</a>
<span className="font-bold mx-1"> | </span>
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div>
<p className="text-gray-700 leading-normal">
{p.summary}
</p>
</article>
))}
<div
onClick={handleGetMore}
className="w-full my-4 py-4 text-center cursor-pointer "
>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
<div onClick={handleGetMore} className='w-full my-4 py-4 text-center cursor-pointer '>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</>
)
}

View File

@@ -0,0 +1,31 @@
import { useGlobal } from '@/lib/global'
import { useImperativeHandle } from 'react'
/**
* 深色模式按钮
*/
const DarkModeButton = props => {
const { cRef, className } = props
const { isDarkMode, toggleDarkMode } = useGlobal()
/**
* 对外暴露方法
*/
useImperativeHandle(cRef, () => {
return {
handleChangeDarkMode: () => {
toggleDarkMode()
}
}
})
return (
<div
onClick={toggleDarkMode}
className={`${className || ''} flex items-center`}>
<i className={`fas mr-2 ${isDarkMode ? 'fa-sun' : 'fa-moon px-0.5'}`} />
{isDarkMode ? 'Dark Mode' : 'Light Mode'}{' '}
</div>
)
}
export default DarkModeButton

View File

@@ -1,34 +1,32 @@
import DarkModeButton from '@/components/DarkModeButton'
import { siteConfig } from '@/lib/config'
export const Footer = props => {
const d = new Date()
const currentYear = d.getFullYear()
const { post } = props
const fullWidth = post?.fullWidth ?? false
const since = siteConfig('SINCE')
const copyrightDate = parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
const copyrightDate =
parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
return (
<footer
className={`z-10 relative mt-6 flex-shrink-0 m-auto w-full text-gray-500 dark:text-gray-400 transition-all ${
!fullWidth ? 'max-w-2xl px-4' : 'px-4 md:px-24'
}`}>
<DarkModeButton className='text-center py-4' />
<hr className='border-gray-200 dark:border-gray-600' />
<div className='my-4 text-sm leading-6'>
<div className='flex align-baseline justify-between flex-wrap'>
<span className='dark:text-gray-200 no-underline ml-4'>
Powered by
<a href='https://github.com/tangly1024/NotionNext' className=' hover:underline'>
{' '}
NotionNext {siteConfig('VERSION')}{' '}
</a>
</span>
<p>
© {siteConfig('TITLE')} {copyrightDate}
</p>
</div>
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' />
{/* 页面底部 */}
<div className='w-full flex justify-between p-4 '>
<p>
© {siteConfig('TITLE')} {copyrightDate}
</p>
<p>{siteConfig('DESCRIPTION')}</p>
<span className='dark:text-gray-200 no-underline ml-4'>
Powered by
<a
href='https://github.com/tangly1024/NotionNext'
className=' hover:underline'>
{' '}
NotionNext {siteConfig('VERSION')}{' '}
</a>
</span>
</div>
</footer>
)

View File

@@ -24,6 +24,7 @@ export const GameListIndexCombine = ({ posts }) => {
if (recommend) {
// 4合一卡组
let groupItems = []
while (gamesClone?.length > 0) {
index++
@@ -38,16 +39,18 @@ export const GameListIndexCombine = ({ posts }) => {
const item = gamesClone.shift()
if (item.tags?.some(t => t === siteConfig('GAME_RECOMMEND_TAG', 'Recommend', CONFIG))) {
components.push(<GameItem key={index} item={item} isLargeCard={true} />)
break
continue
} else {
groupItems.push(item)
}
}
if (groupItems.length === 4 || (gamesClone.length === 0 && groupItems.length > 4)) {
if (groupItems.length === 4) {
components.push(<GameItemGroup key={index} items={groupItems} />)
// 清空4合一卡片
groupItems = []
} else {
// 剩余的4合一不满4个的给他放大卡
while (groupItems.length > 0) {
const item = groupItems.shift()
components.push(<GameItem key={index++} item={item} isLargeCard={true} />)

View File

@@ -40,7 +40,8 @@ export const GameListNormal = ({ games, maxCount = 18 }) => {
* @returns
*/
const GameItem = ({ item }) => {
const { title, img } = item
const { title } = item
const img = item.pageCoverThumbnail
const [showType, setShowType] = useState('img') // img or video
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
const video = item?.ext?.video

View File

@@ -1,15 +1,15 @@
/* eslint-disable @next/next/no-img-element */
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
import { useState } from 'react'
import { useGameGlobal } from '..'
/**
* 游戏列表- 最近游戏
* @returns
*/
export const GameListRecent = ({ maxCount = 14 }) => {
const { recentGames } = useGlobal()
const { recentGames } = useGameGlobal()
const gamesClone = deepClone(recentGames)
// 构造一个List<Component>
const components = []
@@ -31,8 +31,7 @@ export const GameListRecent = ({ maxCount = 14 }) => {
return (
<>
<div className='text-white text-lg pb-1 p-2'>Recent Played</div>
<div className='game-list-recent-wrapper w-full max-w-full overflow-x-auto p-2'>
<div className='game-list-recent-wrapper w-full max-w-full overflow-x-auto pt-4 px-2'>
<div className='game-grid md:flex grid grid-flow-col gap-2'>
{components?.map((ItemComponent, index) => {
return ItemComponent
@@ -67,6 +66,9 @@ const GameItem = ({ item }) => {
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>
<div className='absolute text-sm bottom-2 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-75 transition-all duration-200'>
<div className='h-full w-full absolute bg-gradient-to-b from-transparent to-black'></div>

View File

@@ -0,0 +1,41 @@
import Link from 'next/link'
function GroupCategory({ currentCategory, categoryOptions }) {
if (!categoryOptions) {
return <></>
}
return (
<>
<Link className='mx-2' href='/category'>
<i className='fas fa-bars' />
</Link>
<div id='category-list' className='dark:border-gray-600 flex py-1'>
{categoryOptions.map(category => {
const selected = currentCategory === category.name
return (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
className={` ${
selected
? 'bg-green-500 text-white '
: 'dark:text-gray-300 hover:bg-green-500 rounded-lg hover:text-white'
} whitespace-nowrap overflow-ellipsis w-full items-center px-2 cursor-pointer py-1 font-bold`}>
{/* <i
className={`${selected ? 'text-white fa-folder-open' : 'fa-folder text-gray-400'} fas mr-2`}
/> */}
{category.name}
{/* <span className='text-xs flex items-start pl-2 h-full'>
{category.count}
</span> */}
</Link>
)
})}
</div>
</>
)
}
export default GroupCategory

View File

@@ -0,0 +1,28 @@
import Link from 'next/link'
import TagItemMini from './TagItemMini'
/**
* 标签组
* @param tags
* @param currentTag
* @returns {JSX.Element}
* @constructor
*/
function GroupTag({ tagOptions, currentTag }) {
if (!tagOptions) return <></>
return (
<>
<Link href='/tag'>
<i className='fas fa-tags p-2' />
</Link>
<div id='tags-group' className='flex flex-wrap p-1 gap-2'>
{tagOptions?.slice(0, 20)?.map(tag => {
const selected = tag.name === currentTag
return <TagItemMini key={tag.name} tag={tag} selected={selected} />
})}
</div>
</>
)
}
export default GroupTag

View File

@@ -1,5 +1,4 @@
import { useGlobal } from '@/lib/global'
import Image from 'next/image'
import { useGameGlobal } from '..'
import Logo from './Logo'
/**
@@ -7,10 +6,10 @@ import Logo from './Logo'
* @returns
*/
export default function Header() {
const { setSideBarVisible } = useGlobal()
const { setSideBarVisible } = useGameGlobal()
return (
<header className='z-20'>
<div className='w-full h-16 rounded-md bg-white shadow-card dark:bg-[#1F2030] flex justify-between items-center px-4'>
<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 />
<button
@@ -18,7 +17,7 @@ export default function Header() {
onClick={() => {
setSideBarVisible(true)
}}>
<Image src='/svg/search.svg' className='mr-2' width={20} height={20} />
<i className='fas fa-search' />
</button>
</div>
</header>

View File

@@ -1,30 +1,72 @@
import Link from 'next/link'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { useGameGlobal } from '..'
import CONFIG from '../config'
import DarkModeButton from './DarkModeButton'
import { MenuItemDrop } from './MenuItemDrop'
export const MenuList = () => {
/**
* 导航菜单
*/
export const MenuList = props => {
const { setSideBarVisible } = useGameGlobal()
const { customNav, customMenu } = props
const { locale } = useGlobal()
const defaultLinks = [
{
id: 1,
icon: 'fas fa-home',
name: locale.NAV.INDEX,
to: '/' || '/',
show: true
},
{
id: 2,
icon: 'fas fa-th',
name: locale.COMMON.CATEGORY,
to: '/category',
show: siteConfig('GAME_MENU_CATEGORY', null, CONFIG)
},
{
id: 3,
icon: 'fas fa-tag',
name: locale.COMMON.TAGS,
to: '/tag',
show: siteConfig('GAME_MENU_TAG', null, CONFIG)
}
]
let links = [].concat(defaultLinks)
if (customNav) {
links = defaultLinks.concat(customNav)
}
// 如果 开启自定义菜单则覆盖Page生成的菜单
if (siteConfig('CUSTOM_MENU')) {
links = customMenu
}
return (
<div className='dark:text-white'>
<ul>
<li className='py-4 px-2 font-bold hover:underline'>
<Link href='/' passHref>
<span className='flex items-center gap-2'>
<i className='fas fa-home' />
Home
</span>
</Link>
</li>
<li className='py-4 px-2 font-bold hover:underline'>
<button
className='flex items-center gap-2'
onClick={() => {
setSideBarVisible(true)
}}>
<i className='fas fa-search' />
<span>Search</span>
</button>
</li>
</ul>
</div>
<ul>
<li className='py-4 px-2 font-bold'>
<button
className='flex items-center gap-2'
onClick={() => {
setSideBarVisible(true)
}}>
<i className='fas fa-search' />
<span>Search</span>
</button>
<button className='flex items-center gap-2'>
{/* 切换深色模式 */}
<DarkModeButton className='text-center py-4' />
</button>
</li>
{links?.map(
(link, index) =>
link && link.show && <MenuItemDrop key={index} link={link} />
)}
</ul>
)
}

View File

@@ -1,152 +0,0 @@
import Collapse from '@/components/Collapse'
import LazyImage from '@/components/LazyImage'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import CONFIG from '../config'
import { MenuItemCollapse } from './MenuItemCollapse'
import { MenuItemDrop } from './MenuItemDrop'
import RandomPostButton from './RandomPostButton'
import SearchButton from './SearchButton'
import { SvgIcon } from './SvgIcon'
const Nav = props => {
const { navBarTitle, fullWidth, siteInfo } = props
const useSticky = !JSON.parse(siteConfig('GAME_AUTO_COLLAPSE_NAV_BAR', true))
const navRef = useRef(null)
const sentinalRef = useRef([])
const handler = ([entry]) => {
if (navRef && navRef.current && useSticky) {
if (!entry.isIntersecting && entry !== undefined) {
navRef.current?.classList.add('sticky-nav-full')
} else {
navRef.current?.classList.remove('sticky-nav-full')
}
} else {
navRef.current?.classList.add('remove-sticky')
}
}
useEffect(() => {
const obvserver = new window.IntersectionObserver(handler)
obvserver.observe(sentinalRef.current)
return () => {
if (sentinalRef.current) obvserver.unobserve(sentinalRef.current)
}
}, [sentinalRef])
return (
<>
<div className='observer-element h-4 md:h-12' ref={sentinalRef}></div>
<div
className={`sticky-nav m-auto w-full h-6 flex flex-row justify-between items-center mb-2 md:mb-12 py-8 bg-opacity-60 ${
!fullWidth ? 'max-w-3xl px-4' : 'px-4 md:px-24'
}`}
id='sticky-nav'
ref={navRef}>
<div className='flex items-center'>
<Link href='/' aria-label={siteConfig('TITLE')}>
<div className='h-6 w-6'>
{/* <SvgIcon/> */}
{siteConfig('GAME_NAV_NOTION_ICON', null, CONFIG) ? (
<LazyImage src={siteInfo?.icon} width={24} height={24} alt={siteConfig('AUTHOR')} />
) : (
<SvgIcon />
)}
</div>
</Link>
{navBarTitle ? (
<p className='ml-2 font-medium text-gray-800 dark:text-gray-300 header-name'>{navBarTitle}</p>
) : (
<p className='ml-2 font-medium text-gray-800 dark:text-gray-300 header-name whitespace-nowrap'>
{siteConfig('TITLE')}
{/* ,{' '}<span className="font-normal">{siteConfig('DESCRIPTION')}</span> */}
</p>
)}
</div>
<NavBar {...props} />
</div>
</>
)
}
const NavBar = props => {
const { customMenu, customNav } = props
const [isOpen, changeOpen] = useState(false)
const toggleOpen = () => {
changeOpen(!isOpen)
}
const collapseRef = useRef(null)
const { locale } = useGlobal()
let links = [
{
id: 2,
name: locale.NAV.RSS,
to: '/feed',
show: siteConfig('ENABLE_RSS') && siteConfig('GAME_MENU_RSS', null, CONFIG),
target: '_blank'
},
{
icon: 'fas fa-search',
name: locale.NAV.SEARCH,
to: '/search',
show: siteConfig('GAME_MENU_SEARCH', null, CONFIG)
},
{
icon: 'fas fa-archive',
name: locale.NAV.ARCHIVE,
to: '/archive',
show: siteConfig('GAME_MENU_ARCHIVE', null, CONFIG)
},
{
icon: 'fas fa-folder',
name: locale.COMMON.CATEGORY,
to: '/category',
show: siteConfig('GAME_MENU_CATEGORY', null, CONFIG)
},
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: siteConfig('GAME_MENU_TAG', null, CONFIG) }
]
if (customNav) {
links = links.concat(customNav)
}
// 如果 开启自定义菜单则覆盖Page生成的菜单
if (siteConfig('CUSTOM_MENU')) {
links = customMenu
}
if (!links || links.length === 0) {
return null
}
return (
<div className='flex-shrink-0 flex'>
<ul className='hidden md:flex flex-row'>
{links?.map((link, index) => (
<MenuItemDrop key={index} link={link} />
))}
</ul>
<div className='md:hidden'>
<Collapse collapseRef={collapseRef} isOpen={isOpen} type='vertical' className='fixed top-16 right-6'>
<div className='dark:border-black bg-white dark:bg-black rounded border p-2 text-sm'>
{links?.map((link, index) => (
<MenuItemCollapse
key={index}
link={link}
onHeightChange={param => collapseRef.current?.updateCollapseHeight(param)}
/>
))}
</div>
</Collapse>
</div>
{JSON.parse(siteConfig('GAME_MENU_RANDOM_POST', null, CONFIG)) && <RandomPostButton {...props} />}
{JSON.parse(siteConfig('GAME_MENU_SEARCH_BUTTON', null, CONFIG)) && <SearchButton {...props} />}
<i
onClick={toggleOpen}
className='fas fa-bars cursor-pointer px-5 flex justify-center items-center md:hidden'></i>
</div>
)
}
export default Nav

View File

@@ -1,9 +0,0 @@
import { MenuList } from './MenuList'
export default function NavBar({ className }) {
return (
<nav className={className}>
<MenuList />
</nav>
)
}

View File

@@ -0,0 +1,53 @@
import NotionIcon from '@/components/NotionIcon'
import Link from 'next/link'
import TagItem from './TagItem'
/**
* 文章详情页说明信息
*/
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'>
<div>
<div>
{post?.type !== 'Page' && (
<>
<Link
href={`/category/${post?.category}`}
passHref
className='cursor-pointer text-xs font-bold hover:underline mr-2'>
{post?.category}
</Link>
</>
)}
</div>
<h1 className='font-bold text-3xl text-black dark:text-white'>
<NotionIcon icon={post?.pageIcon} />
{post?.title}
</h1>
{post?.type !== 'Page' && (
<>
<nav className='flex my-2 items-start text-gray-500 dark:text-gray-400'>
{post?.tags && (
<div className='flex flex-nowrap max-w-full overflow-x-auto article-tags'>
{post?.tags.map(tag => (
<TagItem key={tag} tag={tag} />
))}
</div>
)}
<span className='hidden busuanzi_container_page_pv mr-2'>
<i className='mr-1 fas fa-fire' />
&nbsp;
<span className='mr-2 busuanzi_value_page_pv' />
</span>
</nav>
</>
)}
</div>
</section>
)
}

View File

@@ -1,5 +1,8 @@
import { siteConfig } from '@/lib/config'
import { deepClone } from '@/lib/utils'
import { useEffect, useRef } from 'react'
import { useGameGlobal } from '..'
import CONFIG from '../config'
import { GameListNormal } from './GameListNormal'
import Logo from './Logo'
@@ -7,9 +10,9 @@ import Logo from './Logo'
* 侧拉抽屉的内容
*/
export default function SideBarContent() {
const { allGames, sideBarVisible, setSideBarVisible, filterGames, setFilterGames } = useGameGlobal()
const { allNavPages, sideBarVisible, setSideBarVisible, filterGames, setFilterGames } = useGameGlobal()
const inputRef = useRef(null) // 创建对输入框的引用
const allGames = deepClone(allNavPages)
useEffect(() => {
if (sideBarVisible) {
setTimeout(() => {
@@ -21,7 +24,9 @@ export default function SideBarContent() {
const handleSearch = e => {
const search = e.target.value
if (!search || search === '') {
setFilterGames(allGames?.filter(item => item.recommend))
setFilterGames(
allGames?.filter(item => item.tags?.some(t => t === siteConfig('GAME_RECOMMEND_TAG', 'Recommend', CONFIG)))
)
return
}
setFilterGames(

View File

@@ -1,11 +1,11 @@
import Link from 'next/link'
const TagItem = ({ tag }) => (
(<Link href={`/tag/${encodeURIComponent(tag)}`}>
<p className="mr-1 rounded-full px-2 py-1 border leading-none text-sm dark:border-gray-600">
<Link href={`/tag/${encodeURIComponent(tag)}`}>
<p className='cursor-pointer hover:bg-gray-50 dark:hover:bg-hexo-black-gray mr-1 rounded-full px-2 py-1 border leading-none text-sm dark:border-gray-600'>
{tag}
</p>
</Link>)
</Link>
)
export default TagItem

View File

@@ -0,0 +1,21 @@
import Link from 'next/link'
const TagItemMini = ({ tag, selected = false }) => {
return (
<Link
key={tag}
href={selected ? '/' : `/tag/${encodeURIComponent(tag.name)}`}
className={` rounded hover:text-white hover:bg-green-500 text-black dark:text-white dark:bg-gray-800 py-0.5 px-1 `}
passHref>
{/* # {tag.name} */}
<span className='flex flex-nowrap cursor-pointer'>
# <span>{tag.name}</span>{' '}
<span className='h-full flex items-start text-xs ml-1'>
{tag.count ? `${tag.count}` : ''}
</span>
</span>
</Link>
)
}
export default TagItemMini

View File

@@ -1,9 +1,11 @@
import CusdisComponent from '@/components/CusdisComponent'
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'
import ShareBar from '@/components/ShareBar'
import { siteConfig } from '@/lib/config'
import { deepClone, isBrowser } from '@/lib/utils'
import { deepClone, isBrowser, shuffleArray } from '@/lib/utils'
import Link from 'next/link'
import { createContext, useContext, useEffect, useRef, useState } from 'react'
import Announcement from './components/Announcement'
@@ -16,8 +18,11 @@ import FullScreen from './components/FullScreen'
import { GameListIndexCombine } from './components/GameListIndexCombine'
import { GameListRelate } from './components/GameListRealate'
import { GameListRecent } from './components/GameListRecent'
import GroupCategory from './components/GroupCategory'
import GroupTag from './components/GroupTag'
import Header from './components/Header'
import NavBar from './components/NavBar'
import { MenuList } from './components/MenuList'
import PostInfo from './components/PostInfo'
import SearchNavBar from './components/SearchNavBar'
import SideBarContent from './components/SideBarContent'
import SideBarDrawer from './components/SideBarDrawer'
@@ -47,14 +52,22 @@ const LayoutBase = props => {
const [filterGames, setFilterGames] = useState(
deepClone(
allNavPages?.filter(item => item.tags?.some(t => t === siteConfig('GAME_RECOMMEND_TAG', 'Recommend', CONFIG)))
allNavPages?.filter(item =>
item.tags?.some(
t => t === siteConfig('GAME_RECOMMEND_TAG', 'Recommend', CONFIG)
)
)
)
)
const [recentGames, setRecentGames] = useState([])
const [sideBarVisible, setSideBarVisible] = useState(false)
useEffect(() => {
setRecentGames(localStorage.getItem('recent_games') ? JSON.parse(localStorage.getItem('recent_games')) : [])
setRecentGames(
localStorage.getItem('recent_games')
? JSON.parse(localStorage.getItem('recent_games'))
: []
)
}, [])
return (
@@ -74,37 +87,34 @@ const LayoutBase = props => {
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`}>
<Style />
{/* 左右布局 */}
<div id='wrapper' className={'relative flex justify-between w-full h-full mx-auto'}>
{/* 左侧 */}
<div
id='wrapper'
className={'relative flex justify-between w-full h-full mx-auto'}>
{/* PC端左侧 */}
<div className='w-52 hidden xl:block relative z-10'>
<div className='py-4 px-2 sticky top-0 h-screen flex flex-col justify-between'>
{/* 顶部 */}
<div className=''>
<div className='select-none'>
<Header />
<NavBar />
<nav
className={` dark:text-white bg-white dark:bg-hexo-black-gray my-4 rounded-md px-2`}>
<MenuList {...props} />
</nav>
</div>
{/* 左侧广告栏目 */}
<div className='w-full'>
<AdSlot />
<AdSlot />
<AdSlot />
</div>
<div>
<Footer />
</div>
</div>
</div>
{/* 右侧 */}
<main className='flex-grow w-full overflow-x-auto'>
{children}
<main className='flex-grow w-full h-full flex flex-col min-h-screen overflow-x-auto'>
<div className='flex-grow h-full'>{children}</div>
<div className='ads w-full justify-center flex p-2'>
<AdSlot type='flow' />
</div>
<Footer />
</main>
</div>
@@ -127,11 +137,38 @@ const LayoutBase = props => {
* @returns
*/
const LayoutIndex = props => {
const { tagOptions, currentTag, categoryOptions, currentCategory } = props
return (
<>
{/* 首页移动端顶部导航 */}
<div className='p-2 xl:hidden'>
<Header />
</div>
{/* 最近游戏 */}
<GameListRecent />
{/* 游戏列表 */}
<LayoutPostList {...props} />
<Announcement {...props} />
{/* 主区域下方 */}
<div className='w-full bg-white dark:bg-hexo-black-gray rounded-lg p-2'>
{/* 标签汇总 */}
<GroupCategory
categoryOptions={categoryOptions}
currentCategory={currentCategory}
/>
<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} />
</div>
</>
)
}
@@ -159,9 +196,9 @@ const LayoutPostList = props => {
<>
{tag && <SearchNavBar {...props} />}
{siteConfig('POST_LIST_STYLE') === 'page' ? (
<BlogListPage {...props} posts={filteredBlogPosts} />
<BlogListPage posts={filteredBlogPosts} />
) : (
<BlogListScroll {...props} posts={filteredBlogPosts} />
<BlogListScroll posts={filteredBlogPosts} />
)}
</>
)
@@ -224,7 +261,11 @@ const LayoutArchive = props => {
<>
<div className='mb-10 pb-20 md:py-12 p-3 min-h-screen w-full'>
{Object.keys(archivePosts).map(archiveTitle => (
<BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />
<BlogArchiveItem
key={archiveTitle}
archiveTitle={archiveTitle}
archivePosts={archivePosts}
/>
))}
</div>
</>
@@ -237,23 +278,19 @@ const LayoutArchive = props => {
* @returns
*/
const LayoutSlug = props => {
const { post, allNavPages, lock, validPassword } = props
const game = post
const { post, allNavPages, recommendPosts, lock, validPassword } = props
const game = deepClone(post)
const [loading, setLoading] = useState(false)
// const [url, setUrl] = useState(game?.ext?.href)
// 替换成随机推荐和相关游戏
const relateGames = allNavPages
const randomGames = allNavPages
const relateGames = recommendPosts
const randomGames = shuffleArray(deepClone(allNavPages))
// 将当前游戏加入到最近游玩
useEffect(() => {
// if (!url || url !== game?.ext?.href) {
// // 游戏路径
// setUrl(game?.ext?.href)
// }
// 更新最新游戏
const recentGames = localStorage.getItem('recent_games') ? JSON.parse(localStorage.getItem('recent_games')) : []
const recentGames = localStorage.getItem('recent_games')
? JSON.parse(localStorage.getItem('recent_games'))
: []
const existedIndex = recentGames.findIndex(item => item?.id === game?.id)
if (existedIndex === -1) {
@@ -275,15 +312,21 @@ const LayoutSlug = props => {
}
// 绑定加载事件
if (iframe.attachEvent) {
iframe.attachEvent('onload', iframeLoaded)
if (iframe?.attachEvent) {
iframe?.attachEvent('onload', iframeLoaded)
} else {
iframe.onload = iframeLoaded
if (iframe) iframe.onload = iframeLoaded
}
// 更改iFrame的title
if (document?.getElementById('game-wrapper')?.contentDocument.querySelector('title')?.textContent) {
document.getElementById('game-wrapper').contentDocument.querySelector('title').textContent = `${
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')}`
}
@@ -294,103 +337,101 @@ const LayoutSlug = props => {
{lock && <ArticleLock validPassword={validPassword} />}
{!lock && (
<div id='article-wrapper' className='px-2'>
<>
{/* <ArticleInfo post={post} />
<NotionPage post={post} />
<ShareBar post={post} />
<Comment frontMatter={post} />
<ArticleFooter /> */}
{/* 游戏区域 */}
<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' 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 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'>
<i className='fas fa-spinner animate-spin'></i>
{siteConfig('TITLE')}
</h2>
<h3 className='text-xl text-white'>{siteConfig('DESCRIPTION')}</h3>
</div>
{/* 游戏封面图 */}
{/* eslint-disable-next-line @next/next/no-img-element */}
{game?.img && <img src={game?.img} className='w-full h-full blur-md absolute top-0 left-0 z-0' />}
<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'>
<i className='fas fa-spinner animate-spin'></i>
{siteConfig('TITLE')}
</h2>
<h3 className='text-xl text-white'>
{siteConfig('DESCRIPTION')}
</h3>
</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 h-12 md:w-12 z-10 md:absolute'>
{/* 加入全屏按钮 */}
<FullScreen />
</div>
)}
</div>
{/* 游戏资讯 */}
<div className='game-info dark:text-white py-4 mt-8 md:mt-0'>
<div className='w-full'>
<GameListRelate posts={relateGames} />
{/* 游戏封面图 */}
{/* eslint-disable-next-line @next/next/no-img-element */}
{game?.img && (
<img
src={game?.img}
className='w-full h-full blur-md absolute top-0 left-0 z-0'
/>
)}
</div>
<h1 className='text-2xl px-2 py-2 xl:px-0'>{game?.title}</h1>
<h2 className='px-2 py-2 xl:px-0'>
Play {game?.title || ''} on {siteConfig('TITLE', '')}
</h2>
<p className='px-2 py-2 xl:px-0'>{siteConfig('DESCRIPTION')}</p>
<AdSlot />
)}
{game && (
<div>
<div className='py-2 text-2xl dark:text-white'>Comment</div>
<CusdisComponent frontMatter={game} />
</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 h-12 md:w-12 z-10 md:absolute'>
{/* 加入全屏按钮 */}
<FullScreen />
</div>
)}
</div>
{/* 游戏资讯 */}
<div className='game-info dark:text-white py-4 px-2 mt-8 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'>
<PostInfo post={post} />
<NotionPage post={post} />
{/* 广告嵌入 */}
<AdSlot />
<ShareBar post={post} />
<Comment frontMatter={post} />
</div>
)}
</div>
</div>
</div>
<div className='xl:hidden py-2'>
<Header />
</div>
{/* 其它游戏列表 */}
<div>
<GameListIndexCombine posts={randomGames} />
</div>
</>
{/* 其它游戏列表 */}
<GameListIndexCombine posts={randomGames} />
</div>
)}
</>
@@ -416,15 +457,21 @@ const LayoutCategoryIndex = props => {
return (
<>
<div id='category-list' className='duration-200 flex flex-wrap'>
<div
id='category-list'
className='duration-200 flex flex-wrap my-4 gap-2'>
{categoryOptions?.map(category => {
return (
<Link key={category.name} href={`/category/${category.name}`} passHref legacyBehavior>
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<div
className={
'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'
'bg-white rounded-lg hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'
}>
<i className='mr-4 fas fa-folder' />
{/* <i className='mr-4 fas fa-folder' /> */}
{category.name}({category.count})
</div>
</Link>
@@ -445,20 +492,17 @@ const LayoutTagIndex = props => {
return (
<>
<div>
<div id='tags-list' className='duration-200 flex flex-wrap'>
<div id='tags-list' className='duration-200 flex flex-wrap my-4 gap-2'>
{tagOptions.map(tag => {
return (
<div key={tag.name} className='p-2'>
<Link
key={tag}
href={`/tag/${encodeURIComponent(tag.name)}`}
passHref
className={`cursor-pointer inline-block rounded hover:bg-gray-500 hover:text-white duration-200 mr-2 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-gray-800`}>
<div className='font-light dark:text-gray-400'>
<i className='mr-1 fas fa-tag' /> {tag.name + (tag.count ? `(${tag.count})` : '')}{' '}
</div>
</Link>
</div>
<Link
key={tag.name}
href={`/tag/${encodeURIComponent(tag.name)}`}
passHref
className={` select-none cursor-pointer flex bg-white rounded-lg hover:bg-gray-500 hover:text-white duration-200 mr-2 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white hover:shadow-xl dark:bg-gray-800`}>
<i className='mr-1 fas fa-tag' />{' '}
{tag.name + (tag.count ? `(${tag.count})` : '')}{' '}
</Link>
)
})}
</div>
@@ -468,14 +512,15 @@ const LayoutTagIndex = props => {
}
export {
Layout404,
LayoutArchive,
LayoutBase,
LayoutCategoryIndex,
LayoutIndex,
LayoutPostList,
LayoutSearch,
LayoutSlug,
LayoutTagIndex,
CONFIG as THEME_CONFIG
Layout404,
LayoutArchive,
LayoutBase,
LayoutCategoryIndex,
LayoutIndex,
LayoutPostList,
LayoutSearch,
LayoutSlug,
LayoutTagIndex,
CONFIG as THEME_CONFIG
}