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 (