Skip to content

Commit 4d3b30b

Browse files
committedMar 19, 2024
theme-game
1 parent ff77d30 commit 4d3b30b

21 files changed

+489
-343
lines changed
 

‎blog.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,8 @@ const BLOG = {
432432
category: process.env.NEXT_PUBLIC_NOTION_PROPERTY_CATEGORY || 'category',
433433
date: process.env.NEXT_PUBLIC_NOTION_PROPERTY_DATE || 'date',
434434
tags: process.env.NEXT_PUBLIC_NOTION_PROPERTY_TAGS || 'tags',
435-
icon: process.env.NEXT_PUBLIC_NOTION_PROPERTY_ICON || 'icon'
435+
icon: process.env.NEXT_PUBLIC_NOTION_PROPERTY_ICON || 'icon',
436+
ext: process.env.NEXT_PUBLIC_NOTION_PROPERTY_EXT || 'ext' // 扩展字段,存放json-string,用于复杂业务
436437
},
437438

438439
// RSS订阅

‎components/Draggable.js

+27-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { useRef, useEffect, useState } from 'react'
1+
import { useEffect, useRef, useState } from 'react'
2+
23
/**
34
* 可拖拽组件
45
*/
5-
6-
export const Draggable = (props) => {
7-
const { children } = props
6+
export const Draggable = props => {
7+
const { children, stick } = props
88
const draggableRef = useRef(null)
99
const rafRef = useRef(null)
1010
const [moving, setMoving] = useState(false)
@@ -14,8 +14,10 @@ export const Draggable = (props) => {
1414
const draggableElements = document.getElementsByClassName('draggable')
1515

1616
// 标准化鼠标事件对象
17-
function e(event) { // 定义事件对象标准化函数
18-
if (!event) { // 兼容IE浏览器
17+
function e(event) {
18+
// 定义事件对象标准化函数
19+
if (!event) {
20+
// 兼容IE浏览器
1921
event = window.event
2022
event.target = event.srcElement
2123
event.layerX = event.offsetX
@@ -40,9 +42,10 @@ export const Draggable = (props) => {
4042
document.onmousedown = start
4143
document.ontouchstart = start
4244

43-
function start (event) { // 按下鼠标时,初始化处理
45+
function start(event) {
46+
// 按下鼠标时,初始化处理
4447
if (!draggableElements) return
45-
event = e(event)// 获取标准事件对象
48+
event = e(event) // 获取标准事件对象
4649

4750
for (const drag of draggableElements) {
4851
// 判断鼠标点击的区域是否是拖拽框内
@@ -60,27 +63,28 @@ export const Draggable = (props) => {
6063
offsetX = event.mx - currentObj.offsetLeft
6164
offsetY = event.my - currentObj.offsetTop
6265

63-
document.onmousemove = move// 注册鼠标移动事件处理函数
66+
document.onmousemove = move // 注册鼠标移动事件处理函数
6467
document.ontouchmove = move
65-
document.onmouseup = stop// 注册松开鼠标事件处理函数
68+
document.onmouseup = stop // 注册松开鼠标事件处理函数
6669
document.ontouchend = stop
6770
}
6871
}
6972

70-
function move(event) { // 鼠标移动处理函数
73+
function move(event) {
74+
// 鼠标移动处理函数
7175
event = e(event)
7276
rafRef.current = requestAnimationFrame(() => updatePosition(event))
7377
}
7478

75-
const stop = (event) => {
79+
const stop = event => {
7680
event = e(event)
7781
document.documentElement.style.overflow = 'auto' // 恢复默认的滚动行为
7882
cancelAnimationFrame(rafRef.current)
7983
setMoving(false)
8084
currentObj = document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null
8185
}
8286

83-
const updatePosition = (event) => {
87+
const updatePosition = event => {
8488
if (currentObj) {
8589
const left = event.mx - offsetX
8690
const top = event.my - offsetY
@@ -120,15 +124,18 @@ export const Draggable = (props) => {
120124
if (offsetTop < 0) {
121125
drag.firstElementChild.style.top = 0
122126
}
123-
if (offsetTop > (clientHeight - offsetHeight)) {
127+
if (offsetTop > clientHeight - offsetHeight) {
124128
drag.firstElementChild.style.top = clientHeight - offsetHeight + 'px'
125129
}
126130
if (offsetLeft < 0) {
127131
drag.firstElementChild.style.left = 0
128132
}
129-
if (offsetLeft > (clientWidth - offsetWidth)) {
133+
if (offsetLeft > clientWidth - offsetWidth) {
130134
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
131135
}
136+
if (stick === 'left') {
137+
drag.firstElementChild.style.left = 0 + 'px'
138+
}
132139
}
133140
}
134141

@@ -142,9 +149,11 @@ export const Draggable = (props) => {
142149
}
143150
}, [])
144151

145-
return <div className={`draggable ${moving ? 'cursor-grabbing' : 'cursor-grab'} select-none`} ref={draggableRef}>
146-
{children}
147-
</div>
152+
return (
153+
<div className={`draggable ${moving ? 'cursor-grabbing' : 'cursor-grab'} select-none`} ref={draggableRef}>
154+
{children}
155+
</div>
156+
)
148157
}
149158

150159
Draggable.defaultProps = { left: 0, top: 0 }

‎lib/notion/getPageProperties.js

+29-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { getTextContent, getDateValue } from 'notion-utils'
2-
import { NotionAPI } from 'notion-client'
31
import BLOG from '@/blog.config'
2+
import { NotionAPI } from 'notion-client'
3+
import { getDateValue, getTextContent } from 'notion-utils'
44
import formatDate from '../utils/formatDate'
55
// import { createHash } from 'crypto'
66
import md5 from 'js-md5'
@@ -49,8 +49,7 @@ export default async function getPageProperties(id, value, schema, authToken, ta
4949
if (rawUsers[i][0][1]) {
5050
const userId = rawUsers[i][0]
5151
const res = await api.getUsers(userId)
52-
const resValue =
53-
res?.recordMapWithRoles?.notion_user?.[userId[1]]?.value
52+
const resValue = res?.recordMapWithRoles?.notion_user?.[userId[1]]?.value
5453
const user = {
5554
id: resValue?.id,
5655
first_name: resValue?.given_name,
@@ -93,16 +92,17 @@ export default async function getPageProperties(id, value, schema, authToken, ta
9392
properties.pageIcon = mapImgUrl(value?.format?.page_icon, value) ?? ''
9493
properties.pageCover = mapImgUrl(value?.format?.page_cover, value) ?? ''
9594
properties.pageCoverThumbnail = mapImgUrl(value?.format?.page_cover, value, 'block', 'pageCoverThumbnail') ?? ''
96-
95+
properties.ext = converToJSON(properties?.ext)
9796
properties.content = value.content ?? []
98-
properties.tagItems = properties?.tags?.map(tag => {
99-
return { name: tag, color: tagOptions?.find(t => t.value === tag)?.color || 'gray' }
100-
}) || []
97+
properties.tagItems =
98+
properties?.tags?.map(tag => {
99+
return { name: tag, color: tagOptions?.find(t => t.value === tag)?.color || 'gray' }
100+
}) || []
101101
delete properties.content
102102

103103
// 处理URL
104104
if (properties.type === 'Post') {
105-
properties.slug = (BLOG.POST_URL_PREFIX) ? generateCustomizeUrl(properties) : (properties.slug ?? properties.id)
105+
properties.slug = BLOG.POST_URL_PREFIX ? generateCustomizeUrl(properties) : properties.slug ?? properties.id
106106
} else if (properties.type === 'Page') {
107107
properties.slug = properties.slug ?? properties.id
108108
} else if (properties.type === 'Menu' || properties.type === 'SubMenu') {
@@ -122,6 +122,24 @@ export default async function getPageProperties(id, value, schema, authToken, ta
122122
return properties
123123
}
124124

125+
/**
126+
* 字符串转json
127+
* @param {*} str
128+
* @returns
129+
*/
130+
function converToJSON(str) {
131+
if (!str) {
132+
return {}
133+
}
134+
// 使用正则表达式去除空格和换行符
135+
try {
136+
return JSON.parse(str.replace(/\s/g, ''))
137+
} catch (error) {
138+
console.warn('无效JSON', str)
139+
return {}
140+
}
141+
}
142+
125143
/**
126144
* 映射用户自定义表头
127145
*/
@@ -164,7 +182,7 @@ function generateCustomizeUrl(postProperties) {
164182
const formatPostCreatedDate = new Date(postProperties?.publishDay)
165183
fullPrefix += String(formatPostCreatedDate.getUTCDate()).padStart(2, 0)
166184
} else if (pattern === '%slug%') {
167-
fullPrefix += (postProperties.slug ?? postProperties.id)
185+
fullPrefix += postProperties.slug ?? postProperties.id
168186
} else if (!pattern.includes('%')) {
169187
fullPrefix += pattern
170188
} else {
@@ -180,5 +198,5 @@ function generateCustomizeUrl(postProperties) {
180198
if (fullPrefix.endsWith('/')) {
181199
fullPrefix = fullPrefix.substring(0, fullPrefix.length - 1) // 去掉尾部部的"/"
182200
}
183-
return `${fullPrefix}/${(postProperties.slug ?? postProperties.id)}`
201+
return `${fullPrefix}/${postProperties.slug ?? postProperties.id}`
184202
}
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="robots" content="noindex, nofollow">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<title>Full Screen iFrame</title>
9+
<style>
10+
html,
11+
body {
12+
height: 100%;
13+
margin: 0;
14+
padding: 0;
15+
overflow: hidden;
16+
}
17+
18+
#myIframe {
19+
width: 100%;
20+
height: 100%;
21+
border: none;
22+
/* 可选:移除边框 */
23+
}
24+
</style>
25+
</head>
26+
27+
<body>
28+
<!-- <div style="position: absolute;
29+
right: 0px;
30+
bottom: 0px;
31+
background: white;">
32+
<button onclick="toggleFullScreen()">Toggle Full Screen</button>
33+
</div> -->
34+
35+
<iframe id="myIframe" allowfullscreen="allowfullscreen" allow="autoplay" scrolling="no"></iframe>
36+
37+
38+
<!-- https://letsplay247.github.io/cz.html?n=space-wars-battleground -->
39+
<script>
40+
var myParam = location.search.split('n=')[1]
41+
document.getElementById("myIframe").src = myParam;
42+
</script>
43+
<script src="/js/fullscreen.js" type="text/javascript"></script>
44+
45+
</body>
46+
47+
48+
</html>

‎public/games-external/crazy/index.htm

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Full Screen iFrame</title>
8+
<style>
9+
html,
10+
body {
11+
height: 100%;
12+
margin: 0;
13+
padding: 0;
14+
overflow: hidden;
15+
}
16+
17+
#myIframe {
18+
width: 100%;
19+
height: 100%;
20+
border: none;
21+
/* 可选:移除边框 */
22+
}
23+
</style>
24+
</head>
25+
26+
<body>
27+
<!-- <div style="display: block;background-color: red;">
28+
<button onclick="toggleFullScreen()">Toggle Full Screen</button>
29+
</div> -->
30+
<iframe id="myIframe" allowfullscreen="allowfullscreen" allow="autoplay" scrolling="no"></iframe>
31+
32+
33+
<!-- https://letsplay247.github.io/cz.html?n=space-wars-battleground -->
34+
<script>
35+
var myParam = location.search.split('n=')[1]
36+
document.getElementById("myIframe").src = "https://games.crazygames.com/en_US/" + myParam + "/index.html";
37+
</script>
38+
<script src="/js/fullscreen.js" type="text/javascript"></script>
39+
40+
</body>
41+
42+
43+
</html>

‎public/js/fullscreen.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
window.toggleFullScreen = toggleFullScreen
2+
function toggleFullScreen() {
3+
var iframe = document.getElementById('myIframe')
4+
5+
if (!document.fullscreenElement) {
6+
if (iframe.requestFullscreen) {
7+
iframe.requestFullscreen()
8+
} else if (iframe.mozRequestFullScreen) {
9+
/* Firefox */
10+
iframe.mozRequestFullScreen()
11+
} else if (iframe.webkitRequestFullscreen) {
12+
/* Chrome, Safari and Opera */
13+
iframe.webkitRequestFullscreen()
14+
} else if (iframe.msRequestFullscreen) {
15+
/* IE/Edge */
16+
iframe.msRequestFullscreen()
17+
}
18+
} else {
19+
if (document.exitFullscreen) {
20+
document.exitFullscreen()
21+
} else if (document.mozCancelFullScreen) {
22+
/* Firefox */
23+
document.mozCancelFullScreen()
24+
} else if (document.webkitExitFullscreen) {
25+
/* Chrome, Safari and Opera */
26+
document.webkitExitFullscreen()
27+
} else if (document.msExitFullscreen) {
28+
/* IE/Edge */
29+
document.msExitFullscreen()
30+
}
31+
}
32+
}

‎themes/game/components/Announcement.js

+16-7
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,24 @@ import dynamic from 'next/dynamic'
22

33
const NotionPage = dynamic(() => import('@/components/NotionPage'))
44

5+
/**
6+
* 公告
7+
* @param {*} param0
8+
* @returns
9+
*/
510
const Announcement = ({ notice, className }) => {
611
if (notice?.blockMap) {
7-
return <div className={className}>
8-
<section id='announcement-wrapper' className='mb-10'>
9-
{notice && (<div id="announcement-content">
10-
<NotionPage post={notice} className='text-center ' />
11-
</div>)}
12-
</section>
13-
</div>
12+
return (
13+
<div className={className}>
14+
<section id='announcement-wrapper' className='mb-10'>
15+
{notice && (
16+
<div id='announcement-content'>
17+
<NotionPage post={notice} />
18+
</div>
19+
)}
20+
</section>
21+
</div>
22+
)
1423
} else {
1524
return null
1625
}
+34-33
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
21
import { siteConfig } from '@/lib/config'
32
import { useGlobal } from '@/lib/global'
4-
import { useRouter } from 'next/router'
53
import Link from 'next/link'
6-
import BlogPost from './BlogPost'
4+
import { useRouter } from 'next/router'
5+
import { GameListIndexCombine } from './GameListIndexCombine'
76

87
export const BlogListPage = props => {
98
const { page = 1, posts, postCount } = props
@@ -14,37 +13,39 @@ export const BlogListPage = props => {
1413

1514
const showPrev = currentPage > 1
1615
const showNext = currentPage < totalPage && posts?.length > 0
17-
const pagePrefix = router.asPath.split('?')[0].replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
16+
const pagePrefix = router.asPath
17+
.split('?')[0]
18+
.replace(/\/page\/[1-9]\d*/, '')
19+
.replace(/\/$/, '')
1820

1921
return (
20-
<div className="w-full md:pr-12 my-6">
21-
22-
<div id="posts-wrapper">
23-
{posts?.map(post => (
24-
<BlogPost key={post.id} post={post}/>
25-
))}
26-
</div>
27-
28-
<div className="flex justify-between text-xs">
29-
<Link
30-
href={{ pathname: currentPage - 1 === 1 ? `${pagePrefix}/` : `${pagePrefix}/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }}
31-
className={`${showPrev ? ' ' : ' invisible block pointer-events-none '}no-underline py-2 px-3 rounded`}>
32-
33-
<button rel="prev" className="block cursor-pointer">
34-
{locale.PAGINATION.PREV}
35-
</button>
36-
37-
</Link>
38-
<Link
39-
href={{ pathname: `${pagePrefix}/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} }}
40-
className={`${showNext ? ' ' : 'invisible pointer-events-none '} no-underline py-2 px-3 rounded`}>
41-
42-
<button rel="next" className="block cursor-pointer">
43-
{locale.PAGINATION.NEXT}
44-
</button>
45-
46-
</Link>
47-
</div>
48-
</div>
22+
<div className='w-full md:pr-12 my-6'>
23+
<div id='posts-wrapper'>
24+
<GameListIndexCombine {...props} />
25+
</div>
26+
27+
<div className='flex justify-between text-xs'>
28+
<Link
29+
href={{
30+
pathname: currentPage - 1 === 1 ? `${pagePrefix}/` : `${pagePrefix}/page/${currentPage - 1}`,
31+
query: router.query.s ? { s: router.query.s } : {}
32+
}}
33+
className={`${showPrev ? ' ' : ' invisible block pointer-events-none '}no-underline py-2 px-3 rounded`}>
34+
<button rel='prev' className='block cursor-pointer'>
35+
{locale.PAGINATION.PREV}
36+
</button>
37+
</Link>
38+
<Link
39+
href={{
40+
pathname: `${pagePrefix}/page/${currentPage + 1}`,
41+
query: router.query.s ? { s: router.query.s } : {}
42+
}}
43+
className={`${showNext ? ' ' : 'invisible pointer-events-none '} no-underline py-2 px-3 rounded`}>
44+
<button rel='next' className='block cursor-pointer'>
45+
{locale.PAGINATION.NEXT}
46+
</button>
47+
</Link>
48+
</div>
49+
</div>
4950
)
5051
}

‎themes/game/components/Draggable.js

-153
This file was deleted.

‎themes/game/components/Footer.js

+24-18
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
import DarkModeButton from '@/components/DarkModeButton'
2-
import Vercel from '@/components/Vercel'
32
import { siteConfig } from '@/lib/config'
43

5-
export const Footer = (props) => {
4+
export const Footer = props => {
65
const d = new Date()
76
const currentYear = d.getFullYear()
87
const { post } = props
98
const fullWidth = post?.fullWidth ?? false
109
const since = siteConfig('SINCE')
1110
const copyrightDate = parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
1211

13-
return <footer
14-
className={`z-10 relative mt-6 flex-shrink-0 m-auto w-full text-gray-500 dark:text-gray-400 transition-all ${
15-
!fullWidth ? 'max-w-2xl px-4' : 'px-4 md:px-24'
16-
}`}
17-
>
18-
<DarkModeButton className='text-center py-4'/>
19-
<hr className="border-gray-200 dark:border-gray-600" />
20-
<div className="my-4 text-sm leading-6">
21-
<div className="flex align-baseline justify-between flex-wrap">
22-
<p>
23-
© {siteConfig('AUTHOR')} {copyrightDate}
24-
</p>
25-
<Vercel />
26-
</div>
27-
</div>
28-
</footer>
12+
return (
13+
<footer
14+
className={`z-10 relative mt-6 flex-shrink-0 m-auto w-full text-gray-500 dark:text-gray-400 transition-all ${
15+
!fullWidth ? 'max-w-2xl px-4' : 'px-4 md:px-24'
16+
}`}>
17+
<DarkModeButton className='text-center py-4' />
18+
<hr className='border-gray-200 dark:border-gray-600' />
19+
<div className='my-4 text-sm leading-6'>
20+
<div className='flex align-baseline justify-between flex-wrap'>
21+
<span className='dark:text-gray-200 no-underline ml-4'>
22+
Powered by
23+
<a href='https://github.com/tangly1024/NotionNext' className=' hover:underline'>
24+
{' '}
25+
NotionNext {siteConfig('VERSION')}{' '}
26+
</a>
27+
</span>
28+
<p>
29+
© {siteConfig('TITLE')} {copyrightDate}
30+
</p>
31+
</div>
32+
</div>
33+
</footer>
34+
)
2935
}

‎themes/game/components/FullScreen.js

+9-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
/* eslint-disable @next/next/no-img-element */
22

3-
import Image from 'next/image'
4-
53
/**
64
* 全屏按钮
75
* @returns
@@ -14,26 +12,20 @@ export default function FullScreen() {
1412
block: 'end',
1513
inline: 'nearest'
1614
})
17-
// console.log(document?.getElementById('game-wrapper')?.contentWindow)
18-
document?.getElementById('game-wrapper')?.contentWindow?.toggleFullScreen()
15+
document?.getElementById('game-wrapper')?.contentWindow?.toggleFullScreen &&
16+
document?.getElementById('game-wrapper')?.contentWindow?.toggleFullScreen()
1917
}
2018

2119
return (
2220
<div
23-
className="group text-white w-full justify-center items-center flex rounded-lg m-2 md:m-0 p-2 hover:bg-gray-700 bg-[#1F2030] md:rounded-none md:bg-none"
24-
onClick={toggleFullScreen}
25-
>
26-
<Image
27-
width={18}
28-
height={18}
29-
src="/svg/fullscreen-alt.svg"
30-
alt="full screen"
31-
title="full screen"
32-
className="cursor-pointer group-hover:scale-125 transition-all duration-150 "
21+
className='group text-white w-full justify-center items-center flex rounded-lg m-2 md:m-0 p-2 hover:bg-gray-700 bg-[#1F2030] md:rounded-none md:bg-none'
22+
onClick={toggleFullScreen}>
23+
<i
24+
alt='full screen'
25+
title='full screen'
26+
className='cursor-pointer fas fa-expand group-hover:scale-125 transition-all duration-150 '
3327
/>
34-
<span className="h-full flex mx-2 md:hidden items-center select-none">
35-
FullScreen
36-
</span>
28+
<span className='h-full flex mx-2 md:hidden items-center select-none'>FullScreen</span>
3729
</div>
3830
)
3931
}

‎themes/game/components/GameListIndexCombine.js

+15-16
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
/* eslint-disable @next/next/no-img-element */
22
import { AdSlot } from '@/components/GoogleAdsense'
3-
import { deepClone } from '@/lib/utils'
3+
import { siteConfig } from '@/lib/config'
4+
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
5+
import Link from 'next/link'
46
import { useState } from 'react'
7+
import CONFIG from '../config'
58

69
/**
710
* 游戏列表
811
* @returns
912
*/
10-
export const GameListIndexCombine = ({ games }) => {
11-
const gamesClone = deepClone(games)
12-
13-
gamesClone?.sort((a, b) => {
14-
const orderA = a.order || 999
15-
const orderB = b.order || 999
16-
17-
return orderA - orderB
18-
})
13+
export const GameListIndexCombine = ({ posts }) => {
14+
const gamesClone = deepClone(posts)
1915

2016
// 构造一个List<Component>
2117
const components = []
2218

2319
// 根据序号随机大小;或根据game.recommend 决定
24-
const recommend = true
20+
const recommend = siteConfig('GAME_INDEX_EXPAND_RECOMMEND', true, CONFIG)
2521

2622
let index = 0
2723
// 无限循环
@@ -40,7 +36,7 @@ export const GameListIndexCombine = ({ games }) => {
4036
// 试图将4合一卡组塞满
4137
while (gamesClone?.length > 0 && groupItems.length < 4) {
4238
const item = gamesClone.shift()
43-
if (item.recommend) {
39+
if (item.tags?.some(t => t === siteConfig('GAME_RECOMMEND_TAG', 'Recommend', CONFIG))) {
4440
components.push(<GameItem key={index} item={item} isLargeCard={true} />)
4541
break
4642
} else {
@@ -122,11 +118,14 @@ const GameItemGroup = ({ items }) => {
122118
* @returns
123119
*/
124120
const GameItem = ({ item, isLargeCard }) => {
125-
const { id, title, img, video } = item
121+
const { title } = item
122+
const img = item.pageCoverThumbnail
126123
const [showType, setShowType] = useState('img') // img or video
124+
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
125+
const video = item?.ext?.video
127126
return (
128-
<a
129-
href={`/game/${id}`}
127+
<Link
128+
href={`${url}`}
130129
onMouseOver={() => {
131130
setShowType('video')
132131
}}
@@ -159,6 +158,6 @@ const GameItem = ({ item, isLargeCard }) => {
159158
src={img}
160159
alt={title}
161160
/>
162-
</a>
161+
</Link>
163162
)
164163
}

‎themes/game/components/GameListNormal.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* eslint-disable @next/next/no-img-element */
2-
import { deepClone } from '@/lib/utils'
2+
import { siteConfig } from '@/lib/config'
3+
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
4+
import Link from 'next/link'
35
import { useState } from 'react'
46

57
/**
@@ -38,12 +40,14 @@ export const GameListNormal = ({ games, maxCount = 18 }) => {
3840
* @returns
3941
*/
4042
const GameItem = ({ item }) => {
41-
const { id, title, img, video } = item
43+
const { title, img } = item
4244
const [showType, setShowType] = useState('img') // img or video
45+
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
46+
const video = item?.ext?.video
4347

4448
return (
45-
<a
46-
href={`/game/${id}`}
49+
<Link
50+
href={`${url}`}
4751
onMouseOver={() => {
4852
setShowType('video')
4953
}}
@@ -64,6 +68,6 @@ const GameItem = ({ item }) => {
6468
</video>
6569
)}
6670
<img className='w-full h-full absolute object-cover' src={img} alt={title} />
67-
</a>
71+
</Link>
6872
)
6973
}

‎themes/game/components/GameListRealate.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/* eslint-disable @next/next/no-img-element */
2-
import { deepClone } from '@/lib/utils'
2+
import { siteConfig } from '@/lib/config'
3+
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
4+
import Link from 'next/link'
35
import { useState } from 'react'
46

57
/**
68
* 游戏列表- 关联游戏,在详情页展示
79
* @returns
810
*/
9-
export const GameListRelate = ({ games }) => {
10-
const gamesClone = deepClone(games)
11+
export const GameListRelate = ({ posts }) => {
12+
const gamesClone = deepClone(posts)
1113

1214
// 构造一个List<Component>
1315
const components = []
@@ -24,7 +26,7 @@ export const GameListRelate = ({ games }) => {
2426

2527
return (
2628
<div className='game-list-wrapper w-full max-w-full overflow-x-auto'>
27-
<div className='game-grid grid grid-flow-col gap-2'>
29+
<div className='game-grid grid grid-flow-col justify-start gap-2'>
2830
{components?.map((ItemComponent, index) => {
2931
return ItemComponent
3032
})}
@@ -38,13 +40,17 @@ export const GameListRelate = ({ games }) => {
3840
* @param {*} param0
3941
* @returns
4042
*/
41-
const GameItem = ({ item, isLargeCard }) => {
42-
const { id, title, img, video } = item
43+
const GameItem = ({ item }) => {
44+
const { title } = item
4345
const [showType, setShowType] = useState('img') // img or video
46+
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
47+
48+
const img = item?.pageCoverThumbnail
49+
const video = item?.ext?.video
4450

4551
return (
46-
<a
47-
href={`/game/${id}`}
52+
<Link
53+
href={`${url}`}
4854
onMouseOver={() => {
4955
setShowType('video')
5056
}}
@@ -71,6 +77,6 @@ const GameItem = ({ item, isLargeCard }) => {
7177
src={img}
7278
alt={title}
7379
/>
74-
</a>
80+
</Link>
7581
)
7682
}

‎themes/game/components/GameListRecent.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @next/next/no-img-element */
2+
import { siteConfig } from '@/lib/config'
23
import { useGlobal } from '@/lib/global'
3-
import { deepClone } from '@/lib/utils'
4+
import { checkContainHttp, deepClone, sliceUrlFromHttp } from '@/lib/utils'
45
import { useState } from 'react'
56

67
/**
@@ -48,12 +49,15 @@ export const GameListRecent = ({ maxCount = 14 }) => {
4849
* @returns
4950
*/
5051
const GameItem = ({ item }) => {
51-
const { id, title, img, video } = item || {}
52+
const { title } = item || {}
5253
const [showType, setShowType] = useState('img') // img or video
54+
const url = checkContainHttp(item.slug) ? sliceUrlFromHttp(item.slug) : `${siteConfig('SUB_PATH', '')}/${item.slug}`
5355

56+
const img = item?.pageCoverThumbnail
57+
const video = item?.ext?.video
5458
return (
5559
<a
56-
href={`/game/${id}`}
60+
href={`${url}`}
5761
onMouseOver={() => {
5862
setShowType('video')
5963
}}

‎themes/game/components/Header.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { useGlobal } from '@/lib/global'
12
import Image from 'next/image'
23
import Logo from './Logo'
3-
import { useGlobal } from '@/lib/global'
44

55
/**
66
* 顶栏
@@ -9,22 +9,16 @@ import { useGlobal } from '@/lib/global'
99
export default function Header() {
1010
const { setSideBarVisible } = useGlobal()
1111
return (
12-
<header className="z-20">
13-
<div className="w-full h-16 rounded-md bg-[#1F2030] flex justify-between items-center px-4">
12+
<header className='z-20'>
13+
<div className='w-full h-16 rounded-md bg-white shadow-card dark:bg-[#1F2030] flex justify-between items-center px-4'>
1414
<Logo />
1515

1616
<button
17-
className="flex xl:hidden"
17+
className='flex xl:hidden'
1818
onClick={() => {
1919
setSideBarVisible(true)
20-
}}
21-
>
22-
<Image
23-
src="/svg/search.svg"
24-
className="mr-2"
25-
width={20}
26-
height={20}
27-
/>
20+
}}>
21+
<Image src='/svg/search.svg' className='mr-2' width={20} height={20} />
2822
</button>
2923
</div>
3024
</header>

‎themes/game/components/Logo.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default function Logo() {
66
return (
77
<Link passHref href='/' className='logo rounded cursor-pointer flex flex-col items-center'>
88
<div className='w-full'>
9-
<h1 className='text-2xl text-white font-bold font-serif'>{siteConfig('TITLE')}</h1>
9+
<h1 className='text-2xl dark:text-white font-bold font-serif'>{siteConfig('TITLE')}</h1>
1010
<h2 className='text-xs text-gray-400 whitespace-nowrap'>{siteConfig('BIO')}</h2>
1111
</div>
1212
</Link>

‎themes/game/components/MenuList.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import { useGameGlobal } from '..'
44
export const MenuList = () => {
55
const { setSideBarVisible } = useGameGlobal()
66
return (
7-
<div>
7+
<div className='dark:text-white'>
88
<ul>
9-
<li className='text-white py-4 px-2 font-bold hover:underline'>
9+
<li className='py-4 px-2 font-bold hover:underline'>
1010
<Link href='/' passHref>
1111
<span className='flex items-center gap-2'>
1212
<i className='fas fa-home' />
1313
Home
1414
</span>
1515
</Link>
1616
</li>
17-
<li className='text-white py-4 px-2 font-bold hover:underline'>
17+
<li className='py-4 px-2 font-bold hover:underline'>
1818
<button
1919
className='flex items-center gap-2'
2020
onClick={() => {

‎themes/game/config.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const CONFIG = {
22
GAME_NAV_NOTION_ICON: true, // 是否读取Notion图标作为站点头像 ; 否则默认显示黑色SVG方块
33

4+
GAME_RECOMMEND_TAG: 'Recommend', // 打了此Tag,被视为推荐
5+
GAME_INDEX_EXPAND_RECOMMEND: true, // 首页列表是否将推荐游戏放大,否则随机放大。
6+
47
// 特殊菜单
58
GAME_MENU_RANDOM_POST: true, // 是否显示随机跳转文章按钮
69
GAME_MENU_SEARCH_BUTTON: true, // 是否显示搜索按钮,该按钮支持Algolia搜索

‎themes/game/index.js

+163-32
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
import Comment from '@/components/Comment'
1+
import CusdisComponent from '@/components/CusdisComponent'
2+
import { Draggable } from '@/components/Draggable'
23
import { AdSlot } from '@/components/GoogleAdsense'
34
import replaceSearchResult from '@/components/Mark'
4-
import NotionPage from '@/components/NotionPage'
5-
import ShareBar from '@/components/ShareBar'
65
import { siteConfig } from '@/lib/config'
76
import { deepClone, isBrowser } from '@/lib/utils'
87
import Link from 'next/link'
9-
import { useRouter } from 'next/router'
108
import { createContext, useContext, useEffect, useRef, useState } from 'react'
119
import Announcement from './components/Announcement'
12-
import { ArticleFooter } from './components/ArticleFooter'
13-
import { ArticleInfo } from './components/ArticleInfo'
1410
import { ArticleLock } from './components/ArticleLock'
1511
import BlogArchiveItem from './components/BlogArchiveItem'
1612
import { BlogListPage } from './components/BlogListPage'
1713
import { BlogListScroll } from './components/BlogListScroll'
1814
import { Footer } from './components/Footer'
15+
import FullScreen from './components/FullScreen'
16+
import { GameListIndexCombine } from './components/GameListIndexCombine'
17+
import { GameListRelate } from './components/GameListRealate'
18+
import { GameListRecent } from './components/GameListRecent'
1919
import Header from './components/Header'
2020
import NavBar from './components/NavBar'
2121
import SearchNavBar from './components/SearchNavBar'
@@ -45,7 +45,11 @@ const LayoutBase = props => {
4545
// 在列表中进行实时过滤
4646
const [filterKey, setFilterKey] = useState('')
4747

48-
const [filterGames, setFilterGames] = useState(deepClone(allNavPages?.filter(item => item.recommend)))
48+
const [filterGames, setFilterGames] = useState(
49+
deepClone(
50+
allNavPages?.filter(item => item.tags?.some(t => t === siteConfig('GAME_RECOMMEND_TAG', 'Recommend', CONFIG)))
51+
)
52+
)
4953
const [recentGames, setRecentGames] = useState([])
5054
const [sideBarVisible, setSideBarVisible] = useState(false)
5155

@@ -68,7 +72,7 @@ const LayoutBase = props => {
6872
}}>
6973
<div
7074
id='theme-game'
71-
className={`${siteConfig('FONT_STYLE')} w-full h-full min-h-screen justify-center dark:text-gray-300 scroll-smooth`}>
75+
className={`${siteConfig('FONT_STYLE')} w-full h-full min-h-screen justify-center bg-[#83FFE7] dark:bg-black dark:text-gray-300 scroll-smooth`}>
7276
<Style />
7377
{/* 左右布局 */}
7478
<div id='wrapper' className={'relative flex justify-between w-full h-full mx-auto'}>
@@ -95,8 +99,9 @@ const LayoutBase = props => {
9599
</div>
96100

97101
{/* 右侧 */}
98-
<main className='flex-grow w-full overflow-x-scroll'>
102+
<main className='flex-grow w-full overflow-x-auto'>
99103
{children}
104+
100105
<div className='ads w-full justify-center flex p-2'>
101106
<AdSlot type='flow' />
102107
</div>
@@ -122,7 +127,13 @@ const LayoutBase = props => {
122127
* @returns
123128
*/
124129
const LayoutIndex = props => {
125-
return <LayoutPostList {...props} topSlot={<Announcement {...props} />} />
130+
return (
131+
<>
132+
<GameListRecent />
133+
<LayoutPostList {...props} />
134+
<Announcement {...props} />
135+
</>
136+
)
126137
}
127138

128139
/**
@@ -131,7 +142,7 @@ const LayoutIndex = props => {
131142
* @returns
132143
*/
133144
const LayoutPostList = props => {
134-
const { posts, topSlot, tag } = props
145+
const { posts, tag } = props
135146
const { filterKey } = useGameGlobal()
136147
let filteredBlogPosts = []
137148
if (filterKey && posts) {
@@ -146,7 +157,6 @@ const LayoutPostList = props => {
146157

147158
return (
148159
<>
149-
{topSlot}
150160
{tag && <SearchNavBar {...props} />}
151161
{siteConfig('POST_LIST_STYLE') === 'page' ? (
152162
<BlogListPage {...props} posts={filteredBlogPosts} />
@@ -227,38 +237,159 @@ const LayoutArchive = props => {
227237
* @returns
228238
*/
229239
const LayoutSlug = props => {
230-
const { post, lock, validPassword } = props
231-
const router = useRouter()
240+
const { post, allNavPages, lock, validPassword } = props
241+
const game = post
242+
const [loading, setLoading] = useState(false)
243+
// const [url, setUrl] = useState(game?.ext?.href)
244+
// 替换成随机推荐和相关游戏
245+
const relateGames = allNavPages
246+
const randomGames = allNavPages
247+
248+
// 将当前游戏加入到最近游玩
232249
useEffect(() => {
233-
// 404
234-
if (!post) {
235-
setTimeout(
236-
() => {
237-
if (isBrowser) {
238-
const article = document.getElementById('notion-article')
239-
if (!article) {
240-
router.push('/404').then(() => {
241-
console.warn('找不到页面', router.asPath)
242-
})
243-
}
244-
}
245-
},
246-
siteConfig('POST_WAITING_TIME_FOR_404') * 1000
247-
)
250+
// if (!url || url !== game?.ext?.href) {
251+
// // 游戏路径
252+
// setUrl(game?.ext?.href)
253+
// }
254+
255+
// 更新最新游戏
256+
const recentGames = localStorage.getItem('recent_games') ? JSON.parse(localStorage.getItem('recent_games')) : []
257+
258+
const existedIndex = recentGames.findIndex(item => item?.id === game?.id)
259+
if (existedIndex === -1) {
260+
recentGames.unshift(game) // 将游戏插入到数组头部
261+
} else {
262+
// 如果游戏已存在于数组中,将其移至数组头部
263+
const existingGame = recentGames.splice(existedIndex, 1)[0]
264+
recentGames.unshift(existingGame)
265+
}
266+
localStorage.setItem('recent_games', JSON.stringify(recentGames))
267+
268+
const iframe = document.getElementById('game-wrapper')
269+
270+
// 定义一个函数来处理iframe加载成功事件
271+
function iframeLoaded() {
272+
if (game) {
273+
setLoading(false)
274+
}
248275
}
249-
}, [post])
276+
277+
// 绑定加载事件
278+
if (iframe.attachEvent) {
279+
iframe.attachEvent('onload', iframeLoaded)
280+
} else {
281+
iframe.onload = iframeLoaded
282+
}
283+
284+
// 更改iFrame的title
285+
if (document?.getElementById('game-wrapper')?.contentDocument.querySelector('title')?.textContent) {
286+
document.getElementById('game-wrapper').contentDocument.querySelector('title').textContent = `${
287+
game?.title || ''
288+
} - Play ${game?.title || ''} on ${siteConfig('TITLE')}`
289+
}
290+
}, [game])
291+
250292
return (
251293
<>
252294
{lock && <ArticleLock validPassword={validPassword} />}
253295

254296
{!lock && (
255297
<div id='article-wrapper' className='px-2'>
256298
<>
257-
<ArticleInfo post={post} />
299+
{/* <ArticleInfo post={post} />
258300
<NotionPage post={post} />
259301
<ShareBar post={post} />
260302
<Comment frontMatter={post} />
261-
<ArticleFooter />
303+
<ArticleFooter /> */}
304+
{/* 游戏区域 */}
305+
<div className='game-detail-wrapper w-full grow flex md:px-2'>
306+
{/* 移动端返回主页按钮 */}
307+
<Draggable stick='left'>
308+
<div
309+
style={{ left: '0px', top: '1rem' }}
310+
className='fixed xl:hidden group space-x-1 flex items-center z-20 pr-3 pl-1 bg-[#202030] rounded-r-2xl shadow-lg '>
311+
<Link href='/' className='px-1 py-3 hover:scale-125 duration-200 transition-all' passHref>
312+
<i className='fas fa-arrow-left' />
313+
</Link>{' '}
314+
<span
315+
className='text-white font-serif'
316+
onClick={() => {
317+
document.querySelector('.game-info').scrollIntoView({
318+
behavior: 'smooth',
319+
block: 'end',
320+
inline: 'nearest'
321+
})
322+
}}>
323+
G
324+
</span>
325+
</div>
326+
</Draggable>
327+
328+
<div className='w-full py-1 md:py-4'>
329+
<div className='bg-black w-full xl:h-[calc(100vh-8rem)] h-screen rounded-md relative'>
330+
{/* Loading遮罩 */}
331+
{loading && (
332+
<div className='absolute z-20 w-full xl:h-[calc(100vh-8rem)] h-screen rounded-md overflow-hidden '>
333+
<div className='z-20 absolute bg-black bg-opacity-75 w-full h-full flex flex-col gap-4 justify-center items-center'>
334+
<h2 className='text-3xl text-white flex gap-2'>
335+
<i className='fas fa-spinner animate-spin'></i>
336+
{siteConfig('TITLE')}
337+
</h2>
338+
<h3 className='text-xl text-white'>{siteConfig('DESCRIPTION')}</h3>
339+
</div>
340+
341+
{/* 游戏封面图 */}
342+
{/* eslint-disable-next-line @next/next/no-img-element */}
343+
{game?.img && <img src={game?.img} className='w-full h-full blur-md absolute top-0 left-0 z-0' />}
344+
</div>
345+
)}
346+
347+
<iframe
348+
id='game-wrapper'
349+
className={`w-full xl:h-[calc(100vh-8rem)] h-screen md:rounded-md overflow-hidden ${game?.ext?.href ? '' : 'hidden'}`}
350+
style={{
351+
position: 'relative'
352+
}}
353+
src={game?.ext?.href}></iframe>
354+
355+
{/* 游戏窗口装饰器 */}
356+
{game && !loading && (
357+
<div className='game-decorator bg-[#0B0D14] right-0 bottom-0 flex justify-center h-12 md:w-12 z-10 md:absolute'>
358+
{/* 加入全屏按钮 */}
359+
<FullScreen />
360+
</div>
361+
)}
362+
</div>
363+
364+
{/* 游戏资讯 */}
365+
<div className='game-info dark:text-white py-4 mt-8 md:mt-0'>
366+
<div className='w-full'>
367+
<GameListRelate posts={relateGames} />
368+
</div>
369+
<h1 className='text-2xl px-2 py-2 xl:px-0'>{game?.title}</h1>
370+
<h2 className='px-2 py-2 xl:px-0'>
371+
Play {game?.title || ''} on {siteConfig('TITLE', '')}
372+
</h2>
373+
<p className='px-2 py-2 xl:px-0'>{siteConfig('DESCRIPTION')}</p>
374+
<AdSlot />
375+
376+
{game && (
377+
<div>
378+
<div className='py-2 text-2xl dark:text-white'>Comment</div>
379+
<CusdisComponent frontMatter={game} />
380+
</div>
381+
)}
382+
</div>
383+
</div>
384+
</div>
385+
386+
<div className='xl:hidden py-2'>
387+
<Header />
388+
</div>
389+
{/* 其它游戏列表 */}
390+
<div>
391+
<GameListIndexCombine posts={randomGames} />
392+
</div>
262393
</>
263394
</div>
264395
)}

‎themes/theme.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import BLOG, { LAYOUT_MAPPINGS } from '@/blog.config'
2-
import { getQueryParam, getQueryVariable, isBrowser } from '../lib/utils'
3-
import dynamic from 'next/dynamic'
4-
import getConfig from 'next/config'
52
import * as ThemeComponents from '@theme-components'
3+
import getConfig from 'next/config'
4+
import dynamic from 'next/dynamic'
5+
import { getQueryParam, getQueryVariable, isBrowser } from '../lib/utils'
66

77
// 在next.config.js中扫描所有主题
88
export const { THEMES = [] } = getConfig().publicRuntimeConfig
@@ -100,7 +100,6 @@ export const initDarkMode = (updateDarkMode, defaultDarkMode) => {
100100

101101
// 查看localStorage中用户记录的是否深色模式
102102
const userDarkMode = loadDarkModeFromLocalStorage()
103-
console.log('深色模式',userDarkMode)
104103
if (userDarkMode) {
105104
newDarkMode = userDarkMode === 'dark' || userDarkMode === 'true'
106105
}

0 commit comments

Comments
 (0)
Please sign in to comment.