Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions lib/db/SiteDataApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,28 @@ export async function getSiteDataByPageId({ pageId, from }) {
* 获取公告
*/
async function getNotice(post) {
if (!post) {
return null
if (!post) return null

try {
const rawBlockMap = await fetchNotionPageBlocks(post.id, 'data-notice')

// ✅ 必须经过 adapter 拍平结构,否则新格式的双层嵌套会导致 type undefined
const adapted = adapterNotionBlockMap(rawBlockMap)

// ✅ 再清理 crdt_data 等 react-notion-x 不认识的字段
post.blockMap = {
...adapted,
block: formatNotionBlock(adapted.block)
}
} catch (e) {
console.warn('[getNotice] fetchNotionPageBlocks failed:', post.id, e)
post.blockMap = null
}

post.blockMap = await fetchNotionPageBlocks(post.id, 'data-notice')
return post
}


/**
* 空的默认数据
* @param {*} pageId
Expand Down Expand Up @@ -226,7 +240,7 @@ export async function resolvePostProps({
/**
* 6️⃣ 如果拿到了 post,但没有 blockMap,则拉 block
*/
if (post?.id && !post?.blockMap) {
if (post?.id && !post?.blockMap) {
try {
const rawBlockMap = await fetchNotionPageBlocks(post.id, source)

Expand Down Expand Up @@ -300,11 +314,16 @@ async function convertNotionToSiteData(SITE_DATABASE_PAGE_ID, from, pageRecordMa
const viewIds = rawMetadata?.view_ids
const collectionData = []

// ✅ 新增:先对原始 block 做格式统一,避免 normalizePageBlock 识别失败
block = adapterNotionBlockMap({ block }).block


const pageIds = getAllPageIds(
collectionQuery,
collectionId,
collectionView,
viewIds
viewIds,
block
)

if (pageIds?.length === 0) {
Expand Down Expand Up @@ -333,7 +352,9 @@ async function convertNotionToSiteData(SITE_DATABASE_PAGE_ID, from, pageRecordMa

// 2️⃣ fetch 缺失的 blocks
const fetchedBlocks = await fetchInBatches(blockIdsNeedFetch)
block = Object.assign({}, block, fetchedBlocks)
// ✅ fetch 回来的也要 adapter
const adaptedFetchedBlocks = adapterNotionBlockMap({ block: fetchedBlocks }).block
block = Object.assign({}, block, adaptedFetchedBlocks)

// 3️⃣ 只执行一次:生成 collectionData
for (let i = 0; i < pageIds.length; i++) {
Expand All @@ -343,7 +364,7 @@ async function convertNotionToSiteData(SITE_DATABASE_PAGE_ID, from, pageRecordMa
const pageBlock = normalizePageBlock(rawBlock)

if (!pageBlock) {
console.warn('⚠️ 无法解析 page block:', id, rawBlock)
// console.warn('⚠️ 无法解析 page block:', id, rawBlock)
continue
}

Expand Down
84 changes: 57 additions & 27 deletions lib/db/notion/getAllPageIds.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
import BLOG from "@/blog.config"

export default function getAllPageIds(collectionQuery, collectionId, collectionView, viewIds) {
if (!collectionQuery && !collectionView) {
return []
}
let pageIds = []
try {
// Notion数据库中的第几个视图用于站点展示和排序:
export default function getAllPageIds(collectionQuery, collectionId, collectionView, viewIds, block = {}) {
const pageSet = new Set()

// ── 策略1:从 collectionView[viewId].value.value.page_sort 取(新格式)──
if (collectionView && viewIds?.length > 0) {
const groupIndex = BLOG.NOTION_INDEX || 0
if (viewIds && viewIds.length > 0) {
const ids = collectionQuery[collectionId][viewIds[groupIndex]]?.collection_group_results?.blockIds || []
if (ids) {
for (const id of ids) {
pageIds.push(id)
}
}
const targetViewId = viewIds[groupIndex]
const pageSort = collectionView?.[targetViewId]?.value?.value?.page_sort

if (Array.isArray(pageSort) && pageSort.length > 0) {
pageSort.forEach(id => pageSet.add(id))
// console.log('[getAllPageIds] 策略1命中 page_sort,数量:', pageSet.size)
}
} catch (error) {
console.error('Error fetching page IDs:', ids, error);
return [];
}

// 否则按照数据库原始排序
if (pageIds.length === 0 && collectionQuery && Object.values(collectionQuery).length > 0) {
const pageSet = new Set()
Object.values(collectionQuery[collectionId]).forEach(view => {
view?.blockIds?.forEach(id => pageSet.add(id)) // group视图
view?.collection_group_results?.blockIds?.forEach(id => pageSet.add(id)) // table视图
// ── 策略2:遍历所有 viewId 的 page_sort 兜底 ──
if (pageSet.size === 0 && collectionView) {
Object.values(collectionView).forEach(viewEntry => {
const pageSort = viewEntry?.value?.value?.page_sort
if (Array.isArray(pageSort)) {
pageSort.forEach(id => pageSet.add(id))
}
})
pageIds = [...pageSet]
// console.log('PageIds: 从collectionQuery获取', collectionQuery, pageIds.length)
if (pageSet.size > 0) {
// console.log('[getAllPageIds] 策略2命中 page_sort(遍历),数量:', pageSet.size)
}
}

// ── 策略3:旧格式兼容,从 collectionQuery 取 ──
if (pageSet.size === 0 && collectionQuery && collectionId) {
const viewQuery = collectionQuery?.[collectionId]
if (viewQuery) {
Object.values(viewQuery).forEach(viewData => {
[
viewData?.collection_group_results?.blockIds,
viewData?.results?.blockIds,
viewData?.blockIds,
].forEach(ids => {
if (Array.isArray(ids)) ids.forEach(id => pageSet.add(id))
})
})
if (pageSet.size > 0) {
// console.log('[getAllPageIds] 策略3命中 collectionQuery(旧格式),数量:', pageSet.size)
}
}
}

if (pageSet.size === 0) {
// console.warn('[getAllPageIds] 所有策略均未命中,返回空数组')
return []
}
return pageIds
}

// ── 统一过滤:只保留有权限的 pageId ──
// const accessibleIds = [...pageSet].filter(id => {
// const entry = block[id]
// if (!entry) return true // block 里没有记录,保留交给后续 fetch 处理
// return entry?.value?.role !== 'none'
// })

// console.log(`[getAllPageIds] 过滤后可访问数量: ${accessibleIds.length}/${pageSet.size}`)
// return accessibleIds
return [...pageSet]
}
33 changes: 21 additions & 12 deletions lib/db/notion/getPostBlocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,36 +85,46 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) {
export function formatNotionBlock(block) {
const clonedBlock = deepClone(block)
const blocksToProcess = Object.keys(clonedBlock || {})
// 循环遍历文档的每个block

for (let i = 0; i < blocksToProcess.length; i++) {
const blockId = blocksToProcess[i]
const b = clonedBlock[blockId]

// 跳过无效的block(缺少value或id)
if (!b?.value?.id) {
delete clonedBlock[blockId]
let b = clonedBlock[blockId]

// ✅ 【新增】统一结构:兼容新版双层嵌套格式
// 新格式: { spaceId, value: { value: { id, type }, role } }
// 次格式: { value: { id, type }, role }
// 旧格式: { value: { id, type } }
if (b?.value?.value?.id) {
// 新格式,剥掉外层,只保留真实 block value
clonedBlock[blockId] = { value: b.value.value }
b = clonedBlock[blockId]
} else if (!b?.value?.id && b?.value?.role !== undefined) {
// role:none 等无权限 block,直接跳过
continue
}

// === 【新增】强制修复非法 URL ===
// ✅ 【新增】清理 crdt 字段,react-notion-x 不认识会报 Unsupported block type
if (b?.value) {
delete b.value.crdt_data
delete b.value.crdt_format_version
}

// 原有逻辑不变 ↓↓↓

sanitizeBlockUrls(b?.value)

if (b?.value?.type === 'sync_block' && b?.value?.children) {
const childBlocks = b.value.children
// 移除同步块
delete clonedBlock[blockId]
// 用子块替代同步块
childBlocks.forEach((childBlock, index) => {
const newBlockId = `${blockId}_child_${index}`
clonedBlock[newBlockId] = childBlock
blocksToProcess.splice(i + index + 1, 0, newBlockId)
})
// 重新处理新加入的子块
i--
continue
}

// 处理 c++、c#、汇编等语言名字映射
if (b?.value?.type === 'code') {
if (b?.value?.properties?.language?.[0][0] === 'C++') {
b.value.properties.language[0][0] = 'cpp'
Expand All @@ -127,7 +137,6 @@ export function formatNotionBlock(block) {
}
}

// 如果是文件,或嵌入式PDF,需要重新加密签名
if (
['file', 'pdf', 'video', 'audio'].includes(b?.value?.type) &&
b?.value?.properties?.source?.[0][0] &&
Expand Down
23 changes: 16 additions & 7 deletions lib/utils/notion.util.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,25 @@ export function adapterNotionBlockMap(blockMap) {
}


function unwrapValue(obj) {
if (!obj) return obj

// 新格式特征:外层有 role 或 spaceId,value 里才是真实 block(有 id 和 type)
// { spaceId, value: { value: { id, type, ... }, role } }
if (obj?.value?.value?.id && obj?.value?.role) {
return obj.value.value
}

function unwrapValue(obj) {
let cur = obj;
let guard = 0;
// 次新格式:{ value: { id, type, ... }, role }
if (obj?.value?.id && obj?.role !== undefined) {
return obj.value
}

while (cur?.value && typeof cur.value === 'object' && guard < 5) {
cur = cur.value;
guard++;
// 旧格式:{ value: { id, type, ... } } 直接取 value
if (obj?.value?.id) {
return obj.value
}

return cur;
// 兜底:原样返回
return obj?.value ?? obj
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "notion-next",
"version": "4.9.3.1",
"version": "4.9.3.2",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"engines": {
Expand Down Expand Up @@ -45,7 +45,8 @@
"test:ci": "jest --ci --coverage --watchAll=false",
"health-check": "node scripts/health-check.js",
"validate": "npm run health-check",
"final-validation": "node scripts/final-validation.js"
"final-validation": "node scripts/final-validation.js",
"postinstall": "patch-package"
},
"dependencies": {
"@clerk/localizations": "^3.17.1",
Expand Down Expand Up @@ -95,6 +96,7 @@
"jest-environment-jsdom": "^29.7.0",
"jest-junit": "^16.0.0",
"next-sitemap": "^1.9.12",
"patch-package": "^8.0.1",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"tailwindcss": "^3.4.17",
Expand Down
15 changes: 15 additions & 0 deletions patches/notion-utils+7.7.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
diff --git a/node_modules/notion-utils/build/index.js b/node_modules/notion-utils/build/index.js
index 5e45758..299c23f 100644
--- a/node_modules/notion-utils/build/index.js
+++ b/node_modules/notion-utils/build/index.js
@@ -530,7 +530,9 @@ var normalizeTitle = (title) => {
};

// src/uuid-to-id.ts
-var uuidToId = (uuid) => uuid.replaceAll("-", "");
+// var uuidToId = (uuid) => uuid.replaceAll("-", "");
+var uuidToId = (uuid) => uuid?.replaceAll("-", "") ?? "";
+

// src/get-canonical-page-id.ts
var getCanonicalPageId = (pageId, recordMap, { uuid = true } = {}) => {
Loading
Loading