Skip to content

Commit fe69af8

Browse files
authored
Merge branch 'notionnext-org:main' into main
2 parents 63d1953 + 97d59ef commit fe69af8

6 files changed

Lines changed: 401 additions & 272 deletions

File tree

.github/workflows/bump-version-on-main.yml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,27 @@ jobs:
3232
fetch-depth: 0
3333

3434
- name: Bump package.json patch (last segment)
35-
run: node scripts/bump-package-patch-version.js
35+
id: bump
36+
run: |
37+
node scripts/bump-package-patch-version.js
38+
VERSION=$(node -p "require('./package.json').version")
39+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
3640
37-
- name: Commit and push if changed
41+
- name: Push version bump branch
3842
run: |
3943
git config user.name "github-actions[bot]"
4044
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
4145
if git diff --quiet package.json; then
4246
echo "No version change (unexpected)."
4347
exit 0
4448
fi
45-
NEW_VER=$(node -p "require('./package.json').version")
49+
BRANCH="chore/bump-package-version"
50+
git checkout -B "${BRANCH}"
4651
git add package.json
47-
git commit -m "chore(release): bump package.json to ${NEW_VER} [skip-version]"
48-
git push
52+
git commit -m "chore(release): bump package.json to ${{ steps.bump.outputs.version }} [skip-version]"
53+
git push --force-with-lease origin "${BRANCH}"
54+
{
55+
echo "Version bump branch pushed: \`${BRANCH}\`"
56+
echo ""
57+
echo "Create a pull request from \`${BRANCH}\` to \`main\` to publish version ${{ steps.bump.outputs.version }}."
58+
} >> "$GITHUB_STEP_SUMMARY"

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,15 @@
131131
},
132132
"resolutions": {
133133
"axios": ">=0.21.1",
134-
"form-data": "^4.0.4"
134+
"esbuild": "^0.25.0",
135+
"flatted": "^3.4.2",
136+
"form-data": "^4.0.4",
137+
"js-cookie": "^3.0.7",
138+
"lodash": "^4.18.1",
139+
"picomatch": "^2.3.2",
140+
"qs": "^6.15.2",
141+
"tmp": "^0.2.5",
142+
"uuid": "^11.1.1"
135143
},
136144
"bugs": {
137145
"url": "https://github.com/tangly/NotionNext/issues",

themes/endspace/components/MobileNav.js

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useRouter } from 'next/router'
2-
import { useState, useEffect, useRef } from 'react'
2+
import { useState, useEffect, useMemo, useRef } from 'react'
33
import { siteConfig } from '@/lib/config'
44
import { handleEmailClick } from '@/lib/plugins/mailEncrypt'
55
import { useGlobal } from '@/lib/global'
6-
import CONFIG from '../config'
76
import SmartLink from '@/components/SmartLink'
87
import { EndspacePlayer } from './EndspacePlayer'
8+
import { buildMenuItems, isMenuItemActive } from './menu'
99
import {
1010
IconMenu2,
1111
IconX,
@@ -70,23 +70,19 @@ const SocialIconComponents = {
7070
export const MobileNav = (props) => {
7171
const router = useRouter()
7272
const { siteInfo } = useGlobal()
73+
const { customNav, customMenu } = props
7374
const [activeTab, setActiveTab] = useState('Home')
7475
const [isMenuOpen, setIsMenuOpen] = useState(false)
76+
const [openSubMenu, setOpenSubMenu] = useState(null)
7577
const emailIcon = useRef(null)
7678

7779
// Get avatar from props or global context
7880
const avatarUrl = props?.siteInfo?.icon || siteInfo?.icon || siteConfig('AVATAR')
7981

80-
// All navigation items
81-
const menuItems = [
82-
{ name: 'Home', path: '/' },
83-
{ name: 'Category', path: '/category', show: siteConfig('ENDSPACE_MENU_CATEGORY', null, CONFIG) },
84-
{ name: 'Tag', path: '/tag', show: siteConfig('ENDSPACE_MENU_TAG', null, CONFIG) },
85-
{ name: 'Archive', path: '/archive', show: siteConfig('ENDSPACE_MENU_ARCHIVE', null, CONFIG) },
86-
{ name: 'Portfolio', path: '/portfolio' },
87-
{ name: 'Friends', path: '/friends' },
88-
{ name: 'Search', path: '/search', show: siteConfig('ENDSPACE_MENU_SEARCH', null, CONFIG) }
89-
].filter(item => item.show !== false)
82+
const menuItems = useMemo(
83+
() => buildMenuItems({ customNav, customMenu }),
84+
[customNav, customMenu]
85+
)
9086

9187
// Social icon config - using contact.config.js settings
9288
const socialLinks = [
@@ -107,18 +103,14 @@ export const MobileNav = (props) => {
107103

108104
useEffect(() => {
109105
const path = router.asPath
110-
if (path === '/') setActiveTab('Home')
111-
else if (path.includes('/category')) setActiveTab('Category')
112-
else if (path.includes('/tag')) setActiveTab('Tag')
113-
else if (path.includes('/archive')) setActiveTab('Archive')
114-
else if (path.includes('/search')) setActiveTab('Search')
115-
else if (path.includes('/friends')) setActiveTab('Friends')
116-
else if (path.includes('/portfolio')) setActiveTab('Portfolio')
117-
}, [router.asPath])
106+
const activeItem = menuItems.find(item => isMenuItemActive(item, path))
107+
setActiveTab(activeItem?.name || 'Home')
108+
}, [router.asPath, menuItems])
118109

119110
// Close menu when route changes
120111
useEffect(() => {
121112
setIsMenuOpen(false)
113+
setOpenSubMenu(null)
122114
}, [router.asPath])
123115

124116
// Prevent body scroll when menu is open
@@ -135,8 +127,7 @@ export const MobileNav = (props) => {
135127

136128
// Render icon component
137129
const renderIcon = (name) => {
138-
const IconComponent = IconComponents[name]
139-
if (!IconComponent) return null
130+
const IconComponent = IconComponents[name] || BookMarkFillIcon
140131
return <IconComponent size={20} className="w-6 text-center" />
141132
}
142133

@@ -199,22 +190,69 @@ export const MobileNav = (props) => {
199190
>
200191
{/* Navigation Items */}
201192
<div className="flex flex-col items-start p-6 space-y-2">
202-
{menuItems.map(item => (
203-
<SmartLink
204-
key={item.name}
205-
href={item.path}
206-
className={`flex items-center gap-4 py-3 w-full transition-all group ${
207-
activeTab === item.name
208-
? 'text-black font-bold'
209-
: 'text-[var(--endspace-text-secondary)] hover:text-black'
210-
}`}
211-
>
212-
<div className={`transition-colors ${activeTab === item.name ? 'text-black' : 'text-gray-400 group-hover:text-black'}`}>
213-
{renderIcon(item.name)}
193+
{menuItems.map(item => {
194+
const hasSubMenu = item.subMenus?.length > 0
195+
const itemKey = `${item.name}-${item.path}`
196+
const isOpen = openSubMenu === itemKey
197+
const itemClassName = `flex items-center gap-4 py-3 w-full transition-all group ${
198+
activeTab === item.name
199+
? 'text-black font-bold'
200+
: 'text-[var(--endspace-text-secondary)] hover:text-black'
201+
}`
202+
const itemContent = (
203+
<>
204+
<div className={`transition-colors ${activeTab === item.name ? 'text-black' : 'text-gray-400 group-hover:text-black'}`}>
205+
{item.icon ? <i className={item.icon} /> : renderIcon(item.name)}
206+
</div>
207+
<span className="text-xl font-medium">{item.name}</span>
208+
{hasSubMenu && (
209+
<span className={`ml-auto text-xl transition-transform duration-200 ${isOpen ? 'rotate-90' : ''}`}>
210+
&rsaquo;
211+
</span>
212+
)}
213+
</>
214+
)
215+
216+
return (
217+
<div key={itemKey} className='w-full'>
218+
{hasSubMenu ? (
219+
<button
220+
type='button'
221+
onClick={() => setOpenSubMenu(isOpen ? null : itemKey)}
222+
className={itemClassName}
223+
>
224+
{itemContent}
225+
</button>
226+
) : (
227+
<SmartLink
228+
href={item.path}
229+
target={item.target}
230+
className={itemClassName}
231+
>
232+
{itemContent}
233+
</SmartLink>
234+
)}
235+
236+
{hasSubMenu && isOpen && (
237+
<div className='mb-2 ml-10 flex flex-col border-l border-[var(--endspace-border-base)] pl-4'>
238+
{item.subMenus.map(subMenu => (
239+
<SmartLink
240+
key={`${subMenu.name}-${subMenu.path}`}
241+
href={subMenu.path}
242+
target={subMenu.target || item.target}
243+
className='flex items-center gap-3 py-2 text-base text-[var(--endspace-text-secondary)] transition-colors hover:text-black'
244+
>
245+
<span className='w-4 text-center text-gray-400'>
246+
{subMenu.icon ? <i className={subMenu.icon} /> : renderIcon(subMenu.name)}
247+
</span>
248+
<span>{subMenu.name}</span>
249+
</SmartLink>
250+
))}
251+
</div>
252+
)}
214253
</div>
215-
<span className="text-xl font-medium">{item.name}</span>
216-
</SmartLink>
217-
))}
254+
)
255+
})}
218256
</div>
219257

220258
{/* Music Player (No Label, No Divider) */}

themes/endspace/components/SideNav.js

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useRouter } from 'next/router'
2-
import { useState, useEffect, useRef } from 'react'
2+
import { useState, useEffect, useMemo, useRef } from 'react'
33
import { siteConfig } from '@/lib/config'
44
import { handleEmailClick } from '@/lib/plugins/mailEncrypt'
55
import { useGlobal } from '@/lib/global'
6-
import CONFIG from '../config'
76
import SmartLink from '@/components/SmartLink'
87
import { EndspacePlayer } from './EndspacePlayer'
8+
import { buildMenuItems, isMenuItemActive } from './menu'
99
import {
1010
IconBrandGithub,
1111
IconBrandTwitter,
@@ -71,6 +71,7 @@ const SocialIconComponents = {
7171
export const SideNav = (props) => {
7272
const router = useRouter()
7373
const { siteInfo } = useGlobal()
74+
const { customNav, customMenu } = props
7475
const [isHovered, setIsHovered] = useState(false)
7576
const [activeTab, setActiveTab] = useState('Home')
7677
const [indicatorStyle, setIndicatorStyle] = useState({ top: 0, opacity: 0 })
@@ -81,16 +82,10 @@ export const SideNav = (props) => {
8182
// Get avatar from props or global context (Hexo way uses props)
8283
const avatarUrl = props?.siteInfo?.icon || siteInfo?.icon || siteConfig('AVATAR')
8384

84-
// All navigation items
85-
const menuItems = [
86-
{ name: 'Home', path: '/' },
87-
{ name: 'Category', path: '/category', show: siteConfig('ENDSPACE_MENU_CATEGORY', null, CONFIG) },
88-
{ name: 'Tag', path: '/tag', show: siteConfig('ENDSPACE_MENU_TAG', null, CONFIG) },
89-
{ name: 'Archive', path: '/archive', show: siteConfig('ENDSPACE_MENU_ARCHIVE', null, CONFIG) },
90-
{ name: 'Portfolio', path: '/portfolio' },
91-
{ name: 'Friends', path: '/friends' },
92-
{ name: 'Search', path: '/search', show: siteConfig('ENDSPACE_MENU_SEARCH', null, CONFIG) }
93-
].filter(item => item.show !== false)
85+
const menuItems = useMemo(
86+
() => buildMenuItems({ customNav, customMenu }),
87+
[customNav, customMenu]
88+
)
9489

9590
// Social icon config - using contact.config.js settings
9691
const socialLinks = [
@@ -130,17 +125,11 @@ export const SideNav = (props) => {
130125
useEffect(() => {
131126
// Set active tab based on path
132127
const path = router.asPath
133-
let newTab = 'Home'
134-
if (path === '/') newTab = 'Home'
135-
else if (path.includes('/category')) newTab = 'Category'
136-
else if (path.includes('/tag')) newTab = 'Tag'
137-
else if (path.includes('/archive')) newTab = 'Archive'
138-
else if (path.includes('/search')) newTab = 'Search'
139-
else if (path.includes('/friends')) newTab = 'Friends'
140-
else if (path.includes('/portfolio')) newTab = 'Portfolio'
128+
const activeItem = menuItems.find(item => isMenuItemActive(item, path))
129+
const newTab = activeItem?.name || 'Home'
141130

142131
setActiveTab(newTab)
143-
}, [router.asPath])
132+
}, [router.asPath, menuItems])
144133

145134
// Update indicator position when activeTab changes
146135
useEffect(() => {
@@ -162,8 +151,7 @@ export const SideNav = (props) => {
162151

163152
// Render icon component
164153
const renderIcon = (name, isActive) => {
165-
const IconComponent = IconComponents[name]
166-
if (!IconComponent) return null
154+
const IconComponent = IconComponents[name] || BookMarkFillIcon
167155
return (
168156
<IconComponent
169157
size={20}
@@ -226,24 +214,58 @@ export const SideNav = (props) => {
226214
/>
227215

228216
{menuItems.map((item) => {
217+
const hasSubMenu = item.subMenus?.length > 0
229218
const isActive = activeTab === item.name
230219
return (
231-
<SmartLink key={item.name} href={item.path}>
220+
<div
221+
key={`${item.name}-${item.path}`}
222+
className='group/menu relative'
223+
>
224+
<SmartLink href={item.path} target={item.target}>
232225
<div
233226
ref={el => itemRefs.current[item.name] = el}
234227
className={`nier-nav-item relative h-[3rem] flex items-center cursor-pointer group transition-colors duration-300 hover:bg-[#d4d4d8] ${isActive ? 'active bg-[#d4d4d8]' : ''}`}
235228
>
236229
{/* Icon Container */}
237230
<div className="w-[5rem] flex-shrink-0 flex items-center justify-center z-10">
238-
{renderIcon(item.name, isActive)}
231+
{item.icon ? <i className={item.icon} /> : renderIcon(item.name, isActive)}
239232
</div>
240233

241234
{/* Text Label (Reveal on Hover) */}
242235
<span className={`text-sm font-medium tracking-wide uppercase whitespace-nowrap transition-opacity duration-300 z-10 ${isHovered ? 'opacity-100 delay-75' : 'opacity-0 w-0'}`}>
243236
{item.name.toUpperCase()}
244237
</span>
238+
{hasSubMenu && (
239+
<span className={`ml-auto pr-4 text-xs transition-opacity duration-300 ${isHovered ? 'opacity-100' : 'opacity-0'}`}>
240+
&rsaquo;
241+
</span>
242+
)}
245243
</div>
246-
</SmartLink>
244+
</SmartLink>
245+
{hasSubMenu && (
246+
<div
247+
className={`grid overflow-hidden transition-all duration-300 ${isHovered ? 'grid-rows-[0fr] opacity-0 group-hover/menu:grid-rows-[1fr] group-hover/menu:opacity-100' : 'grid-rows-[0fr] opacity-0'}`}
248+
>
249+
<div className='min-h-0 py-1'>
250+
{item.subMenus.map(subMenu => (
251+
<SmartLink
252+
key={`${subMenu.name}-${subMenu.path}`}
253+
href={subMenu.path}
254+
target={subMenu.target || item.target}
255+
className='flex h-10 items-center text-sm font-medium text-[var(--endspace-text-secondary)] transition-colors hover:bg-[#d4d4d8] hover:text-black'
256+
>
257+
<span className='flex w-[5rem] flex-shrink-0 items-center justify-center text-xs'>
258+
{subMenu.icon ? <i className={subMenu.icon} /> : renderIcon(subMenu.name, false)}
259+
</span>
260+
<span className='min-w-0 flex-1 truncate pr-4 text-xs uppercase tracking-wide'>
261+
{subMenu.name}
262+
</span>
263+
</SmartLink>
264+
))}
265+
</div>
266+
</div>
267+
)}
268+
</div>
247269
)
248270
})}
249271
</div>

0 commit comments

Comments
 (0)