Skip to content

Commit b6cf8ac

Browse files
authored
Merge branch 'main' into main
2 parents 543694a + 2e808eb commit b6cf8ac

7 files changed

Lines changed: 1030 additions & 609 deletions

File tree

lib/db/SiteDataApi.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,28 @@ export async function getSiteDataByPageId({ pageId, from }) {
9999
* 获取公告
100100
*/
101101
async function getNotice(post) {
102-
if (!post) {
103-
return null
102+
if (!post) return null
103+
104+
try {
105+
const rawBlockMap = await fetchNotionPageBlocks(post.id, 'data-notice')
106+
107+
// ✅ 必须经过 adapter 拍平结构,否则新格式的双层嵌套会导致 type undefined
108+
const adapted = adapterNotionBlockMap(rawBlockMap)
109+
110+
// ✅ 再清理 crdt_data 等 react-notion-x 不认识的字段
111+
post.blockMap = {
112+
...adapted,
113+
block: formatNotionBlock(adapted.block)
114+
}
115+
} catch (e) {
116+
console.warn('[getNotice] fetchNotionPageBlocks failed:', post.id, e)
117+
post.blockMap = null
104118
}
105119

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

123+
110124
/**
111125
* 空的默认数据
112126
* @param {*} pageId
@@ -226,7 +240,7 @@ export async function resolvePostProps({
226240
/**
227241
* 6️⃣ 如果拿到了 post,但没有 blockMap,则拉 block
228242
*/
229-
if (post?.id && !post?.blockMap) {
243+
if (post?.id && !post?.blockMap) {
230244
try {
231245
const rawBlockMap = await fetchNotionPageBlocks(post.id, source)
232246

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

317+
// ✅ 新增:先对原始 block 做格式统一,避免 normalizePageBlock 识别失败
318+
block = adapterNotionBlockMap({ block }).block
319+
320+
303321
const pageIds = getAllPageIds(
304322
collectionQuery,
305323
collectionId,
306324
collectionView,
307-
viewIds
325+
viewIds,
326+
block
308327
)
309328

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

334353
// 2️⃣ fetch 缺失的 blocks
335354
const fetchedBlocks = await fetchInBatches(blockIdsNeedFetch)
336-
block = Object.assign({}, block, fetchedBlocks)
355+
// ✅ fetch 回来的也要 adapter
356+
const adaptedFetchedBlocks = adapterNotionBlockMap({ block: fetchedBlocks }).block
357+
block = Object.assign({}, block, adaptedFetchedBlocks)
337358

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

345366
if (!pageBlock) {
346-
console.warn('⚠️ 无法解析 page block:', id, rawBlock)
367+
// console.warn('⚠️ 无法解析 page block:', id, rawBlock)
347368
continue
348369
}
349370

lib/db/notion/getAllPageIds.js

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,65 @@
11
import BLOG from "@/blog.config"
22

3-
export default function getAllPageIds(collectionQuery, collectionId, collectionView, viewIds) {
4-
if (!collectionQuery && !collectionView) {
5-
return []
6-
}
7-
let pageIds = []
8-
try {
9-
// Notion数据库中的第几个视图用于站点展示和排序:
3+
export default function getAllPageIds(collectionQuery, collectionId, collectionView, viewIds, block = {}) {
4+
const pageSet = new Set()
5+
6+
// ── 策略1:从 collectionView[viewId].value.value.page_sort 取(新格式)──
7+
if (collectionView && viewIds?.length > 0) {
108
const groupIndex = BLOG.NOTION_INDEX || 0
11-
if (viewIds && viewIds.length > 0) {
12-
const ids = collectionQuery[collectionId][viewIds[groupIndex]]?.collection_group_results?.blockIds || []
13-
if (ids) {
14-
for (const id of ids) {
15-
pageIds.push(id)
16-
}
17-
}
9+
const targetViewId = viewIds[groupIndex]
10+
const pageSort = collectionView?.[targetViewId]?.value?.value?.page_sort
11+
12+
if (Array.isArray(pageSort) && pageSort.length > 0) {
13+
pageSort.forEach(id => pageSet.add(id))
14+
// console.log('[getAllPageIds] 策略1命中 page_sort,数量:', pageSet.size)
1815
}
19-
} catch (error) {
20-
console.error('Error fetching page IDs:', ids, error);
21-
return [];
2216
}
2317

24-
// 否则按照数据库原始排序
25-
if (pageIds.length === 0 && collectionQuery && Object.values(collectionQuery).length > 0) {
26-
const pageSet = new Set()
27-
Object.values(collectionQuery[collectionId]).forEach(view => {
28-
view?.blockIds?.forEach(id => pageSet.add(id)) // group视图
29-
view?.collection_group_results?.blockIds?.forEach(id => pageSet.add(id)) // table视图
18+
// ── 策略2:遍历所有 viewId 的 page_sort 兜底 ──
19+
if (pageSet.size === 0 && collectionView) {
20+
Object.values(collectionView).forEach(viewEntry => {
21+
const pageSort = viewEntry?.value?.value?.page_sort
22+
if (Array.isArray(pageSort)) {
23+
pageSort.forEach(id => pageSet.add(id))
24+
}
3025
})
31-
pageIds = [...pageSet]
32-
// console.log('PageIds: 从collectionQuery获取', collectionQuery, pageIds.length)
26+
if (pageSet.size > 0) {
27+
// console.log('[getAllPageIds] 策略2命中 page_sort(遍历),数量:', pageSet.size)
28+
}
29+
}
30+
31+
// ── 策略3:旧格式兼容,从 collectionQuery 取 ──
32+
if (pageSet.size === 0 && collectionQuery && collectionId) {
33+
const viewQuery = collectionQuery?.[collectionId]
34+
if (viewQuery) {
35+
Object.values(viewQuery).forEach(viewData => {
36+
[
37+
viewData?.collection_group_results?.blockIds,
38+
viewData?.results?.blockIds,
39+
viewData?.blockIds,
40+
].forEach(ids => {
41+
if (Array.isArray(ids)) ids.forEach(id => pageSet.add(id))
42+
})
43+
})
44+
if (pageSet.size > 0) {
45+
// console.log('[getAllPageIds] 策略3命中 collectionQuery(旧格式),数量:', pageSet.size)
46+
}
47+
}
48+
}
49+
50+
if (pageSet.size === 0) {
51+
// console.warn('[getAllPageIds] 所有策略均未命中,返回空数组')
52+
return []
3353
}
34-
return pageIds
54+
55+
// ── 统一过滤:只保留有权限的 pageId ──
56+
// const accessibleIds = [...pageSet].filter(id => {
57+
// const entry = block[id]
58+
// if (!entry) return true // block 里没有记录,保留交给后续 fetch 处理
59+
// return entry?.value?.role !== 'none'
60+
// })
61+
62+
// console.log(`[getAllPageIds] 过滤后可访问数量: ${accessibleIds.length}/${pageSet.size}`)
63+
// return accessibleIds
64+
return [...pageSet]
3565
}

lib/db/notion/getPostBlocks.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,30 +85,46 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) {
8585
export function formatNotionBlock(block) {
8686
const clonedBlock = deepClone(block)
8787
const blocksToProcess = Object.keys(clonedBlock || {})
88-
// 循环遍历文档的每个block
88+
8989
for (let i = 0; i < blocksToProcess.length; i++) {
9090
const blockId = blocksToProcess[i]
91-
const b = clonedBlock[blockId]
91+
let b = clonedBlock[blockId]
92+
93+
// ✅ 【新增】统一结构:兼容新版双层嵌套格式
94+
// 新格式: { spaceId, value: { value: { id, type }, role } }
95+
// 次格式: { value: { id, type }, role }
96+
// 旧格式: { value: { id, type } }
97+
if (b?.value?.value?.id) {
98+
// 新格式,剥掉外层,只保留真实 block value
99+
clonedBlock[blockId] = { value: b.value.value }
100+
b = clonedBlock[blockId]
101+
} else if (!b?.value?.id && b?.value?.role !== undefined) {
102+
// role:none 等无权限 block,直接跳过
103+
continue
104+
}
105+
106+
// ✅ 【新增】清理 crdt 字段,react-notion-x 不认识会报 Unsupported block type
107+
if (b?.value) {
108+
delete b.value.crdt_data
109+
delete b.value.crdt_format_version
110+
}
111+
112+
// 原有逻辑不变 ↓↓↓
92113

93-
// === 【新增】强制修复非法 URL ===
94114
sanitizeBlockUrls(b?.value)
95115

96116
if (b?.value?.type === 'sync_block' && b?.value?.children) {
97117
const childBlocks = b.value.children
98-
// 移除同步块
99118
delete clonedBlock[blockId]
100-
// 用子块替代同步块
101119
childBlocks.forEach((childBlock, index) => {
102120
const newBlockId = `${blockId}_child_${index}`
103121
clonedBlock[newBlockId] = childBlock
104122
blocksToProcess.splice(i + index + 1, 0, newBlockId)
105123
})
106-
// 重新处理新加入的子块
107124
i--
108125
continue
109126
}
110127

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

124-
// 如果是文件,或嵌入式PDF,需要重新加密签名
125140
if (
126141
['file', 'pdf', 'video', 'audio'].includes(b?.value?.type) &&
127142
b?.value?.properties?.source?.[0][0] &&

lib/utils/notion.util.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,25 @@ export function adapterNotionBlockMap(blockMap) {
2929
}
3030

3131

32+
function unwrapValue(obj) {
33+
if (!obj) return obj
3234

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

34-
function unwrapValue(obj) {
35-
let cur = obj;
36-
let guard = 0;
41+
// 次新格式:{ value: { id, type, ... }, role }
42+
if (obj?.value?.id && obj?.role !== undefined) {
43+
return obj.value
44+
}
3745

38-
while (cur?.value && typeof cur.value === 'object' && guard < 5) {
39-
cur = cur.value;
40-
guard++;
46+
// 旧格式:{ value: { id, type, ... } } 直接取 value
47+
if (obj?.value?.id) {
48+
return obj.value
4149
}
4250

43-
return cur;
51+
// 兜底:原样返回
52+
return obj?.value ?? obj
4453
}

package.json

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "notion-next",
3-
"version": "4.9.3.1",
3+
"version": "4.9.4",
44
"homepage": "https://github.com/tangly1024/NotionNext.git",
55
"license": "MIT",
66
"engines": {
@@ -45,7 +45,8 @@
4545
"test:ci": "jest --ci --coverage --watchAll=false",
4646
"health-check": "node scripts/health-check.js",
4747
"validate": "npm run health-check",
48-
"final-validation": "node scripts/final-validation.js"
48+
"final-validation": "node scripts/final-validation.js",
49+
"postinstall": "patch-package"
4950
},
5051
"dependencies": {
5152
"@babel/runtime": "^7.23.0",
@@ -65,18 +66,21 @@
6566
"lodash.throttle": "^4.1.1",
6667
"memory-cache": "^0.2.0",
6768
"next": "^14.2.30",
68-
"notion-client": "7.7.1",
69-
"notion-utils": "7.7.1",
69+
"notion-client": "7.10.0",
70+
"notion-utils": "7.10.0",
7071
"react": "^18.3.1",
7172
"react-dom": "^18.3.1",
7273
"react-facebook": "^8.1.4",
7374
"react-hotkeys-hook": "^4.6.2",
74-
"react-notion-x": "7.7.1",
75+
"react-notion-x": "7.10.0",
7576
"react-share": "^5.2.2",
7677
"react-tweet-embed": "~2.0.0",
7778
"zstd-codec": "^0.1.5"
7879
},
7980
"devDependencies": {
81+
"@testing-library/jest-dom": "^6.1.4",
82+
"@testing-library/react": "^14.1.2",
83+
"@testing-library/user-event": "^14.5.1",
8084
"@types/node": "22.15.3",
8185
"@types/react": "18.3.10",
8286
"@typescript-eslint/eslint-plugin": "^7.18.0",
@@ -92,18 +96,16 @@
9296
"eslint-plugin-prettier": "^5.5.1",
9397
"eslint-plugin-react": "^7.37.5",
9498
"eslint-plugin-react-hooks": "^4.6.2",
99+
"jest": "^29.7.0",
100+
"jest-environment-jsdom": "^29.7.0",
101+
"jest-junit": "^16.0.0",
95102
"next-sitemap": "^1.9.12",
103+
"patch-package": "^8.0.1",
96104
"postcss": "^8.5.6",
97105
"prettier": "^3.6.2",
98106
"tailwindcss": "^3.4.17",
99107
"typescript": "5.6.2",
100-
"webpack-bundle-analyzer": "^4.5.0",
101-
"@testing-library/jest-dom": "^6.1.4",
102-
"@testing-library/react": "^14.1.2",
103-
"@testing-library/user-event": "^14.5.1",
104-
"jest": "^29.7.0",
105-
"jest-environment-jsdom": "^29.7.0",
106-
"jest-junit": "^16.0.0"
108+
"webpack-bundle-analyzer": "^4.5.0"
107109
},
108110
"resolutions": {
109111
"axios": ">=0.21.1",

patches/notion-utils+7.10.0.patch

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
diff --git a/node_modules/notion-utils/build/index.js b/node_modules/notion-utils/build/index.js
2+
index a6e480b..adab074 100644
3+
--- a/node_modules/notion-utils/build/index.js
4+
+++ b/node_modules/notion-utils/build/index.js
5+
@@ -556,7 +556,7 @@ var normalizeTitle = (title) => {
6+
};
7+
8+
// src/uuid-to-id.ts
9+
-var uuidToId = (uuid) => uuid.replaceAll("-", "");
10+
+var uuidToId = (uuid) => uuid?.replaceAll("-", "") ?? "";
11+
12+
// src/get-canonical-page-id.ts
13+
var getCanonicalPageId = (pageId, recordMap, { uuid = true } = {}) => {

0 commit comments

Comments
 (0)