diff --git a/.env.local b/.env.local
index 073b8bbb2b2..153de1d1e95 100644
--- a/.env.local
+++ b/.env.local
@@ -1,5 +1,5 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
-NEXT_PUBLIC_VERSION=4.4.6
+NEXT_PUBLIC_VERSION=4.5.0
# 可在此添加环境变量,去掉最左边的(# )注释即可
diff --git a/components/NotionPage.js b/components/NotionPage.js
index 9e0e9c24456..9e57e9a138f 100644
--- a/components/NotionPage.js
+++ b/components/NotionPage.js
@@ -26,9 +26,12 @@ const Equation = dynamic(
{ ssr: false }
)
-const Pdf = dynamic(() => import('react-notion-x/build/third-party/pdf').then(m => m.Pdf), {
- ssr: false
-})
+const Pdf = dynamic(
+ () => import('react-notion-x/build/third-party/pdf').then(m => m.Pdf),
+ {
+ ssr: false
+ }
+)
// https://github.com/txs
// import PrismMac from '@/components/PrismMac'
@@ -46,13 +49,25 @@ const TweetEmbed = dynamic(() => import('react-tweet-embed'), {
/**
* 文内google广告
*/
-const AdEmbed = dynamic(() => import('@/components/GoogleAdsense').then(m => m.AdEmbed), { ssr: true })
+const AdEmbed = dynamic(
+ () => import('@/components/GoogleAdsense').then(m => m.AdEmbed),
+ { ssr: true }
+)
-const Collection = dynamic(() => import('react-notion-x/build/third-party/collection').then(m => m.Collection), {
- ssr: true
-})
+const Collection = dynamic(
+ () =>
+ import('react-notion-x/build/third-party/collection').then(
+ m => m.Collection
+ ),
+ {
+ ssr: true
+ }
+)
-const Modal = dynamic(() => import('react-notion-x/build/third-party/modal').then(m => m.Modal), { ssr: false })
+const Modal = dynamic(
+ () => import('react-notion-x/build/third-party/modal').then(m => m.Modal),
+ { ssr: false }
+)
const Tweet = ({ id }) => {
return
@@ -83,19 +98,22 @@ const NotionPage = ({ post, className }) => {
// 将相册gallery下的图片加入放大功能
if (siteConfig('POST_DISABLE_GALLERY_CLICK')) {
setTimeout(() => {
- if (isBrowser) {
- const imgList = document?.querySelectorAll('.notion-collection-card-cover img')
- if (imgList && zoomRef.current) {
- for (let i = 0; i < imgList.length; i++) {
- zoomRef.current.attach(imgList[i])
- }
- }
+ const imgList = document?.querySelectorAll(
+ '.notion-asset-wrapper-image img'
+ )
+
+ console.log('放大', imgList)
- const cards = document.getElementsByClassName('notion-collection-card')
- for (const e of cards) {
- e.removeAttribute('href')
+ if (imgList && zoomRef.current) {
+ for (let i = 0; i < imgList.length; i++) {
+ zoomRef.current.attach(imgList[i])
}
}
+
+ const cards = document.getElementsByClassName('notion-collection-card')
+ for (const e of cards) {
+ e.removeAttribute('href')
+ }
}, 800)
}
@@ -108,10 +126,16 @@ const NotionPage = ({ post, className }) => {
const allAnchorTags = document.getElementsByTagName('a') // 或者使用 document.querySelectorAll('a') 获取 NodeList
for (const anchorTag of allAnchorTags) {
if (anchorTag?.target === '_blank') {
- const hrefWithoutQueryHash = anchorTag.href.split('?')[0].split('#')[0]
- const hrefWithRelativeHash = currentURL.split('#')[0] + anchorTag.href.split('#')[1]
-
- if (currentURL === hrefWithoutQueryHash || currentURL === hrefWithRelativeHash) {
+ const hrefWithoutQueryHash = anchorTag.href
+ .split('?')[0]
+ .split('#')[0]
+ const hrefWithRelativeHash =
+ currentURL.split('#')[0] + anchorTag.href.split('#')[1]
+
+ if (
+ currentURL === hrefWithoutQueryHash ||
+ currentURL === hrefWithRelativeHash
+ ) {
anchorTag.target = '_self'
}
}
@@ -121,14 +145,20 @@ const NotionPage = ({ post, className }) => {
// 放大图片:调整图片质量
const observer = new MutationObserver((mutationsList, observer) => {
mutationsList.forEach(mutation => {
- if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
+ if (
+ mutation.type === 'attributes' &&
+ mutation.attributeName === 'class'
+ ) {
if (mutation.target.classList.contains('medium-zoom-image--opened')) {
// 等待动画完成后替换为更高清的图像
setTimeout(() => {
// 获取该元素的 src 属性
const src = mutation?.target?.getAttribute('src')
// 替换为更高清的图像
- mutation?.target?.setAttribute('src', compressImage(src, siteConfig('IMAGE_ZOOM_IN_WIDTH', 1200)))
+ mutation?.target?.setAttribute(
+ 'src',
+ compressImage(src, siteConfig('IMAGE_ZOOM_IN_WIDTH', 1200))
+ )
}, 800)
}
}
@@ -136,21 +166,23 @@ const NotionPage = ({ post, className }) => {
})
// 监视整个文档中的元素和属性的变化
- observer.observe(document.body, { attributes: true, subtree: true, attributeFilter: ['class'] })
+ observer.observe(document.body, {
+ attributes: true,
+ subtree: true,
+ attributeFilter: ['class']
+ })
return () => {
observer.disconnect()
}
- }, [])
-
- if (!post || !post.blockMap) {
- return <>{post?.summary || ''}>
- }
+ }, [post])
return (
-
+
0) {
allPages.forEach(p => {
p.to = p.slug
- if (p?.slug?.indexOf('http') === 0) {
- p.target = '_blank'
- } else {
- p.target = '_self'
- if (p?.slug?.indexOf('/') !== 0) {
- p.to = '/' + p.slug
- }
- }
customNav.push({
icon: p.icon || null,
name: p.title,
- to: p.slug,
- target: '_blank',
+ href: p.href,
+ target: p.target,
show: true
})
})
@@ -192,15 +184,6 @@ function getCustomMenu({ collectionData, NOTION_CONFIG }) {
if (menuPages && menuPages.length > 0) {
menuPages.forEach(e => {
e.show = true
- if (e?.slug?.indexOf('http') === 0) {
- e.target = '_blank'
- e.to = e.slug
- } else {
- e.target = '_self'
- if (e?.slug?.indexOf('http') !== 0 && e?.slug?.indexOf('/') !== 0) {
- e.to = '/' + e.slug
- }
- }
if (e.type === BLOG.NOTION_PROPERTY_NAME.type_menu) {
menus.push(e)
} else if (e.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) {
@@ -312,6 +295,7 @@ export function getNavPages({ allPages }) {
tags: item.tags || null,
summary: item.summary || null,
slug: item.slug,
+ href: item.href,
pageIcon: item.pageIcon || '',
lastEditedDate: item.lastEditedDate,
publishDate: item.publishDate,
diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js
index fefdad4d196..5326c7eaa8e 100644
--- a/lib/notion/getPageProperties.js
+++ b/lib/notion/getPageProperties.js
@@ -4,6 +4,7 @@ import { getDateValue, getTextContent } from 'notion-utils'
import formatDate from '../utils/formatDate'
// import { createHash } from 'crypto'
import md5 from 'js-md5'
+import { checkContainHttp, sliceUrlFromHttp } from '../utils'
import { mapImgUrl } from './mapImage'
/**
@@ -106,7 +107,7 @@ export default async function getPageProperties(
properties.pageCover = mapImgUrl(value?.format?.page_cover, value) ?? ''
properties.pageCoverThumbnail =
mapImgUrl(value?.format?.page_cover, value, 'block') ?? ''
- properties.ext = converToJSON(properties?.ext)
+ properties.ext = convertToJSON(properties?.ext)
properties.content = value.content ?? []
properties.tagItems =
properties?.tags?.map(tag => {
@@ -118,27 +119,43 @@ export default async function getPageProperties(
delete properties.content
// 处理URL
+ // 1.按照用户配置的URL_PREFIX 转换一下slug
+ // 2.为文章添加一个href字段,存储最终调整的路径
if (properties.type === 'Post') {
- properties.slug = BLOG.POST_URL_PREFIX
- ? generateCustomizeUrl(properties)
- : properties.slug ?? properties.id
+ if (BLOG.POST_URL_PREFIX) {
+ properties.slug = generateCustomizeSlug(properties)
+ }
+ properties.href = properties.slug ?? properties.id
} else if (properties.type === 'Page') {
- properties.slug = properties.slug ?? properties.id
+ properties.href = properties.slug ?? properties.id
} else if (properties.type === 'Menu' || properties.type === 'SubMenu') {
// 菜单路径为空、作为可展开菜单使用
- properties.to = properties.slug ?? '#'
+ properties.href = properties.slug ?? '#'
properties.name = properties.title ?? ''
}
// 开启伪静态路径
if (JSON.parse(BLOG.PSEUDO_STATIC)) {
if (
- !properties?.slug?.endsWith('.html') &&
- !properties?.slug?.startsWith('http')
+ !properties?.href?.endsWith('.html') &&
+ !properties?.href?.startsWith('http')
) {
- properties.slug += '.html'
+ properties.href += '.html'
}
}
+
+ // 最终检查超链接
+ properties.href = checkContainHttp(properties?.href)
+ ? sliceUrlFromHttp(properties?.href)
+ : `/${properties.href}`
+
+ // 设置链接在页内或新页面打开
+ if (properties.href?.indexOf('http') === 0) {
+ properties.target = '_blank'
+ } else {
+ properties.target = '_self'
+ }
+
// 密码字段md5
properties.password = properties.password
? md5(properties.slug + properties.password)
@@ -151,7 +168,7 @@ export default async function getPageProperties(
* @param {*} str
* @returns
*/
-function converToJSON(str) {
+function convertToJSON(str) {
if (!str) {
return {}
}
@@ -188,11 +205,11 @@ function mapProperties(properties) {
/**
* 获取自定义URL
* 可以根据变量生成URL
- * 支持:%year%/%month%/%day%/%slug%
+ * 支持:%category%/%year%/%month%/%day%/%slug%
* @param {*} postProperties
* @returns
*/
-function generateCustomizeUrl(postProperties) {
+function generateCustomizeSlug(postProperties) {
let fullPrefix = ''
const allSlugPatterns = BLOG.POST_URL_PREFIX.split('/')
allSlugPatterns.forEach((pattern, idx) => {
@@ -210,6 +227,8 @@ function generateCustomizeUrl(postProperties) {
fullPrefix += String(formatPostCreatedDate.getUTCDate()).padStart(2, 0)
} else if (pattern === '%slug%') {
fullPrefix += postProperties.slug ?? postProperties.id
+ } else if (pattern === '%category%' && postProperties?.category) {
+ fullPrefix += postProperties.category
} else if (!pattern.includes('%')) {
fullPrefix += pattern
} else {
diff --git a/lib/utils/post.js b/lib/utils/post.js
new file mode 100644
index 00000000000..87852eda172
--- /dev/null
+++ b/lib/utils/post.js
@@ -0,0 +1,90 @@
+/**
+ * 文章相关工具
+ */
+import { checkContainHttp } from '.'
+
+/**
+ * 获取文章的关联推荐文章列表,目前根据标签关联性筛选
+ * @param post
+ * @param {*} allPosts
+ * @param {*} count
+ * @returns
+ */
+export function getRecommendPost(post, allPosts, count = 6) {
+ let recommendPosts = []
+ const postIds = []
+ const currentTags = post?.tags || []
+ for (let i = 0; i < allPosts.length; i++) {
+ const p = allPosts[i]
+ if (p.id === post.id || p.type.indexOf('Post') < 0) {
+ continue
+ }
+
+ for (let j = 0; j < currentTags.length; j++) {
+ const t = currentTags[j]
+ if (postIds.indexOf(p.id) > -1) {
+ continue
+ }
+ if (p.tags && p.tags.indexOf(t) > -1) {
+ recommendPosts.push(p)
+ postIds.push(p.id)
+ }
+ }
+ }
+
+ if (recommendPosts.length > count) {
+ recommendPosts = recommendPosts.slice(0, count)
+ }
+ return recommendPosts
+}
+
+/**
+ * 确认slug中不包含 / 符号
+ * @param {*} row
+ * @returns
+ */
+export function checkSlugHasNoSlash(row) {
+ let slug = row.slug
+ if (slug.startsWith('/')) {
+ slug = slug.substring(1)
+ }
+ return (
+ (slug.match(/\//g) || []).length === 0 &&
+ !checkContainHttp(slug) &&
+ row.type.indexOf('Menu') < 0
+ )
+}
+
+/**
+ * 检查url中包含一个 /
+ * @param {*} row
+ * @returns
+ */
+export function checkSlugHasOneSlash(row) {
+ let slug = row.slug
+ if (slug.startsWith('/')) {
+ slug = slug.substring(1)
+ }
+ return (
+ (slug.match(/\//g) || []).length === 1 &&
+ !checkContainHttp(slug) &&
+ row.type.indexOf('Menu') < 0
+ )
+}
+
+/**
+ * 检查url中包含两个及以上的 /
+ * @param {*} row
+ * @returns
+ */
+export function checkSlugHasMorThanTwoSlash(row) {
+ let slug = row.slug
+ if (slug.startsWith('/')) {
+ slug = slug.substring(1)
+ }
+ return (
+ (slug.match(/\//g) || []).length >= 2 &&
+ row.type.indexOf('Menu') < 0 &&
+ !checkContainHttp(slug)
+ )
+}
diff --git a/package.json b/package.json
index 4be32288ea2..26964d11d85 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "notion-next",
- "version": "4.4.6",
+ "version": "4.5.0",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
diff --git a/pages/[prefix]/[slug]/[...suffix].js b/pages/[prefix]/[slug]/[...suffix].js
index 763584e8693..2050e81d8d0 100644
--- a/pages/[prefix]/[slug]/[...suffix].js
+++ b/pages/[prefix]/[slug]/[...suffix].js
@@ -2,9 +2,9 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
-import { checkContainHttp } from '@/lib/utils'
+import { checkSlugHasMorThanTwoSlash, getRecommendPost } from '@/lib/utils/post'
import { idToUuid } from 'notion-utils'
-import Slug, { getRecommendPost } from '..'
+import Slug from '..'
/**
* 根据notion的slug访问页面
@@ -33,7 +33,7 @@ export async function getStaticPaths() {
return {
paths: allPages
- ?.filter(row => checkSlug(row))
+ ?.filter(row => checkSlugHasMorThanTwoSlash(row))
.map(row => ({
params: {
prefix: row.slug.split('/')[0],
@@ -54,20 +54,18 @@ export async function getStaticProps({
params: { prefix, slug, suffix },
locale
}) {
- let fullSlug = prefix + '/' + slug + '/' + suffix.join('/')
+ const fullSlug = prefix + '/' + slug + '/' + suffix.join('/')
const from = `slug-props-${fullSlug}`
const props = await getGlobalData({ from, locale })
- if (siteConfig('PSEUDO_STATIC', BLOG.PSEUDO_STATIC, props.NOTION_CONFIG)) {
- if (!fullSlug.endsWith('.html')) {
- fullSlug += '.html'
- }
- }
// 在列表内查找文章
props.post = props?.allPages?.find(p => {
return (
p.type.indexOf('Menu') < 0 &&
- (p.slug === fullSlug || p.id === idToUuid(fullSlug))
+ (p.slug === suffix ||
+ p.slug === fullSlug.substring(fullSlug.lastIndexOf('/') + 1) ||
+ p.slug === fullSlug ||
+ p.id === idToUuid(fullSlug))
)
})
@@ -132,16 +130,4 @@ export async function getStaticProps({
}
}
-function checkSlug(row) {
- let slug = row.slug
- if (slug.startsWith('/')) {
- slug = slug.substring(1)
- }
- return (
- (slug.match(/\//g) || []).length >= 2 &&
- row.type.indexOf('Menu') < 0 &&
- !checkContainHttp(slug)
- )
-}
-
export default PrefixSlug
diff --git a/pages/[prefix]/[slug]/index.js b/pages/[prefix]/[slug]/index.js
index 072a932c71b..1d27113d1ae 100644
--- a/pages/[prefix]/[slug]/index.js
+++ b/pages/[prefix]/[slug]/index.js
@@ -2,9 +2,13 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
-import { checkContainHttp } from '@/lib/utils'
+import {
+ checkSlugHasNoSlash,
+ checkSlugHasOneSlash,
+ getRecommendPost
+} from '@/lib/utils/post'
import { idToUuid } from 'notion-utils'
-import Slug, { getRecommendPost } from '..'
+import Slug from '..'
/**
* 根据notion的slug访问页面
@@ -26,12 +30,26 @@ export async function getStaticPaths() {
const from = 'slug-paths'
const { allPages } = await getGlobalData({ from })
+
// 根据slug中的 / 分割成prefix和slug两个字段 ; 例如 article/test
+ // 最终用户可以通过 [domain]/[prefix]/[slug] 路径访问,即这里的 [domain]/article/test
const paths = allPages
- ?.filter(row => checkSlug(row))
+ ?.filter(row => checkSlugHasOneSlash(row))
.map(row => ({
params: { prefix: row.slug.split('/')[0], slug: row.slug.split('/')[1] }
}))
+
+ // 增加一种访问路径 允许通过 [category]/[slug] 访问文章
+ // 例如文章slug 是 test ,然后文章的分类category是 production
+ // 则除了 [domain]/[slug] 以外,还支持分类名访问: [domain]/[category]/[slug]
+ console.log(
+ allPages
+ ?.filter(row => checkSlugHasNoSlash(row) && row.category)
+ .map(row => ({
+ params: { prefix: row.category, slug: row.slug }
+ }))
+ )
+
return {
paths: paths,
fallback: true
@@ -39,20 +57,15 @@ export async function getStaticPaths() {
}
export async function getStaticProps({ params: { prefix, slug }, locale }) {
- let fullSlug = prefix + '/' + slug
+ const fullSlug = prefix + '/' + slug
const from = `slug-props-${fullSlug}`
const props = await getGlobalData({ from, locale })
- if (siteConfig('PSEUDO_STATIC', BLOG.PSEUDO_STATIC, props.NOTION_CONFIG)) {
- if (!fullSlug.endsWith('.html')) {
- fullSlug += '.html'
- }
- }
// 在列表内查找文章
props.post = props?.allPages?.find(p => {
return (
p.type.indexOf('Menu') < 0 &&
- (p.slug === fullSlug || p.id === idToUuid(fullSlug))
+ (p.slug === slug || p.slug === fullSlug || p.id === idToUuid(fullSlug))
)
})
@@ -116,15 +129,5 @@ export async function getStaticProps({ params: { prefix, slug }, locale }) {
)
}
}
-function checkSlug(row) {
- let slug = row.slug
- if (slug.startsWith('/')) {
- slug = slug.substring(1)
- }
- return (
- (slug.match(/\//g) || []).length === 1 &&
- !checkContainHttp(slug) &&
- row.type.indexOf('Menu') < 0
- )
-}
+
export default PrefixSlug
diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js
index 7327e668e11..0f038881379 100644
--- a/pages/[prefix]/index.js
+++ b/pages/[prefix]/index.js
@@ -3,7 +3,7 @@ import { siteConfig } from '@/lib/config'
import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
-import { checkContainHttp } from '@/lib/utils'
+import { checkSlugHasNoSlash, getRecommendPost } from '@/lib/utils/post'
import { getLayoutByTheme } from '@/themes/theme'
import md5 from 'js-md5'
import { useRouter } from 'next/router'
@@ -71,7 +71,7 @@ export async function getStaticPaths() {
const from = 'slug-paths'
const { allPages } = await getGlobalData({ from })
const paths = allPages
- ?.filter(row => checkSlug(row))
+ ?.filter(row => checkSlugHasNoSlash(row))
.map(row => ({ params: { prefix: row.slug } }))
return {
paths: paths,
@@ -158,51 +158,4 @@ export async function getStaticProps({ params: { prefix }, locale }) {
}
}
-/**
- * 获取文章的关联推荐文章列表,目前根据标签关联性筛选
- * @param post
- * @param {*} allPosts
- * @param {*} count
- * @returns
- */
-export function getRecommendPost(post, allPosts, count = 6) {
- let recommendPosts = []
- const postIds = []
- const currentTags = post?.tags || []
- for (let i = 0; i < allPosts.length; i++) {
- const p = allPosts[i]
- if (p.id === post.id || p.type.indexOf('Post') < 0) {
- continue
- }
-
- for (let j = 0; j < currentTags.length; j++) {
- const t = currentTags[j]
- if (postIds.indexOf(p.id) > -1) {
- continue
- }
- if (p.tags && p.tags.indexOf(t) > -1) {
- recommendPosts.push(p)
- postIds.push(p.id)
- }
- }
- }
-
- if (recommendPosts.length > count) {
- recommendPosts = recommendPosts.slice(0, count)
- }
- return recommendPosts
-}
-
-function checkSlug(row) {
- let slug = row.slug
- if (slug.startsWith('/')) {
- slug = slug.substring(1)
- }
- return (
- (slug.match(/\//g) || []).length === 0 &&
- !checkContainHttp(slug) &&
- row.type.indexOf('Menu') < 0
- )
-}
-
export default Slug
diff --git a/themes/commerce/components/Footer.js b/themes/commerce/components/Footer.js
index 1f3c6b6be5f..27a558df630 100644
--- a/themes/commerce/components/Footer.js
+++ b/themes/commerce/components/Footer.js
@@ -18,7 +18,7 @@ const Footer = props => {
return (