Skip to content

Commit 67fc000

Browse files
authored
Merge branch 'main' into codex/refactor-tsup-to-vite-and-reduce-build-size
2 parents 1769143 + 03a29c8 commit 67fc000

File tree

12 files changed

+317
-199
lines changed

12 files changed

+317
-199
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"engines": {
99
"node": ">=22.16.0"
1010
},
11-
"packageManager": "[email protected].0",
11+
"packageManager": "[email protected].1",
1212
"scripts": {
1313
"prepare": "husky install",
1414
"link": "turbo link",
@@ -24,10 +24,10 @@
2424
"pu": "turbo pu -- --access=public --no-git-checks"
2525
},
2626
"devDependencies": {
27-
"@types/node": "^22.15.29",
28-
"@typescript-eslint/eslint-plugin": "^8.33.0",
29-
"@typescript-eslint/parser": "^8.33.0",
30-
"@vitest/coverage-v8": "^3.1.4",
27+
"@types/node": "^22.15.30",
28+
"@typescript-eslint/eslint-plugin": "^8.33.1",
29+
"@typescript-eslint/parser": "^8.33.1",
30+
"@vitest/coverage-v8": "^3.2.2",
3131
"dotenv": "^16.5.0",
3232
"eslint": "8.57.1",
3333
"eslint-config-next": "^15.3.3",
@@ -45,7 +45,7 @@
4545
"typescript": "^5.8.3",
4646
"vite": "^6.3.5",
4747
"vite-plugin-dts": "^3.8.1",
48-
"vitest": "^3.1.4"
48+
"vitest": "^3.2.2"
4949
},
5050
"standard-version": {
5151
"scripts": {

packages/cli/src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ cli.runExit(process.argv.slice(2), Cli.defaultContext)
1616

1717
export * from './notion/export'
1818
export * from './notion/index'
19+
export * from './treemap'
20+
export * from './stats'

packages/cli/src/notion/export.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Option, Command } from 'clipanion'
22
import { NotionExporter } from './index'
3+
import { generateTreemaps, PageNode } from '../treemap'
4+
import { computeStats, writeStats } from '../stats'
35

46
export class NotionExportCommand extends Command {
57
static paths = [['export']]
@@ -38,6 +40,12 @@ export class NotionExportCommand extends Command {
3840
dataset = Option.Boolean('-d, --dataset', {
3941
description: 'Export as dataset for LLM learning'
4042
})
43+
treemap = Option.Boolean('--treemap', {
44+
description: 'Generate HTML treemap after export'
45+
})
46+
stats = Option.Boolean('--stats', {
47+
description: 'Generate statistics JSON after export'
48+
})
4149
token = Option.String('-t,--token', {
4250
description: 'Notion Access Token'
4351
})
@@ -59,5 +67,15 @@ export class NotionExportCommand extends Command {
5967
token: this.token
6068
})
6169
await exporter.execute()
70+
71+
if (this.treemap || this.stats) if (!exporter.pageTree) await exporter.loadRaw()
72+
73+
const tree = exporter.pageTree as unknown as PageNode
74+
if (this.treemap && tree) await generateTreemaps(this.folder, tree)
75+
76+
if (this.stats && tree) {
77+
const stats = computeStats(tree)
78+
await writeStats(`${this.folder}/stats.json`, stats)
79+
}
6280
}
6381
}

packages/cli/src/stats.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { PageNode } from './treemap'
2+
3+
export interface ExportStats {
4+
totalPages: number
5+
totalBlocks: number
6+
maxDepth: number
7+
}
8+
9+
export function computeStats(pageTree: PageNode): ExportStats {
10+
let totalPages = 0
11+
let totalBlocks = 0
12+
let maxDepth = 0
13+
14+
function traverse(node: PageNode, depth: number) {
15+
totalPages += node.pages || 0
16+
totalBlocks += node.blocks || 0
17+
if (depth > maxDepth) maxDepth = depth
18+
if (node.children) for (const child of node.children) traverse(child, depth + 1)
19+
}
20+
21+
traverse(pageTree, 1)
22+
return { totalPages, totalBlocks, maxDepth }
23+
}
24+
25+
export async function writeStats(file: string, stats: ExportStats) {
26+
const fs = await import('fs/promises')
27+
await fs.writeFile(file, JSON.stringify(stats, null, 2), 'utf8')
28+
}

packages/cli/src/treemap.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
11
import fs from 'fs'
22
import { promisify } from 'util'
3-
import { loadRaw } from './notion'
43

5-
const writeFile = promisify(fs.writeFile)
6-
7-
interface PageNode {
4+
export interface PageNode {
85
id: string
96
blocks: number
107
pages: number
118
title: string
129
children?: PageNode[]
1310
}
1411

15-
async function main() {
16-
const { pageTree } = await loadRaw('texonom-raw')
12+
const writeFile = promisify(fs.writeFile)
1713

18-
// Generate treemap for page counts
14+
export async function generateTreemaps(folder: string, pageTree: PageNode) {
1915
const treemapDataPages = computeMetrics(pageTree, 'pages')
2016
const titlePages = 'Texonom PageTree'
21-
const outputPathPages = 'texonom-raw/pagetree.html'
17+
const outputPathPages = `${folder}/pagetree.html`
2218
await generateTreemapHTML(treemapDataPages, titlePages, outputPathPages)
2319

24-
// Generate treemap for block counts
2520
const treemapDataBlocks = computeMetrics(pageTree, 'blocks')
2621
const titleBlocks = 'Texonom BlockMap'
27-
const outputPathBlocks = 'texonom-raw/blocktree.html'
22+
const outputPathBlocks = `${folder}/blocktree.html`
2823
await generateTreemapHTML(treemapDataBlocks, titleBlocks, outputPathBlocks)
29-
30-
console.info('Treemap HTML files generated successfully.')
3124
}
3225

3326
interface TreemapNode {
@@ -353,5 +346,3 @@ async function generateTreemapHTML(data: TreemapNode, title: string, outputPath:
353346

354347
await writeFile(outputPath, htmlContent, 'utf8')
355348
}
356-
357-
main().catch(error => console.error(error))

packages/nclient/readme.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ const collectionViewId = 'ab639a5a-853e-45e1-9ef7-133b486c0acf'
2929
const collectionData = await api.getCollectionData(collectionId, collectionViewId)
3030
```
3131

32+
### Fetch backlinks
33+
34+
Backlinks require an auth token.
35+
36+
```ts
37+
const backlinks = await api.getBacklinks({
38+
block: { id: 'page-id', spaceId: 'space-id' }
39+
})
40+
41+
// or simply pass the page id
42+
const pageBacklinks = await api.getPageBacklinks('page-id')
43+
```
44+
3245
### Fetch a database's content
3346

3447
You can pass a database ID to the `getPage` method. The response is an object which contains several important properties:

packages/nclient/src/notion-api.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ test(`Backlink`, { timeout: 10000, concurrent: true }, async () => {
5959
expect(backlinks.backlinks.length > 0)
6060
})
6161

62+
test(`Page Backlink`, { timeout: 10000, concurrent: true }, async () => {
63+
const api = new NotionAPI({ authToken: process.env.NOTION_TOKEN })
64+
const backlinks = await api.getPageBacklinks('441d5ce2-b781-46d0-9354-54042b4f06d9')
65+
expect(backlinks.backlinks.length > 0)
66+
})
67+
6268
test(`Get block`, { timeout: 10000, concurrent: true }, async () => {
6369
const id = '3f9e0d86-c643-4672-aa0c-78d63fa80598'
6470
const api = new NotionAPI()

packages/nclient/src/notion-api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,23 @@ export class NotionAPI {
574574
})
575575
}
576576

577+
/**
578+
* Fetch backlinks for a page by automatically resolving its space id.
579+
* Requires an authToken since backlinks are a private API.
580+
*
581+
* @param pageId page id or url
582+
* @param fetchOption additional fetch options
583+
*/
584+
public async getPageBacklinks(pageId: string, fetchOption?: FetchOption) {
585+
const id = parsePageId(pageId)
586+
const res = await this.getBlocks([id], fetchOption)
587+
const block = res.recordMap.block[id]?.value
588+
if (!block) throw new Error(`Block not found "${uuidToId(id)}"`)
589+
const spaceId = block.space_id
590+
591+
return this.getBacklinks({ block: { id, spaceId } }, fetchOption)
592+
}
593+
577594
public async fetch<T>({
578595
endpoint,
579596
body,

packages/nreact/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
},
4141
"devDependencies": {
4242
"@types/lodash.throttle": "^4.1.9",
43-
"@types/node": "^22.15.29",
43+
"@types/node": "^22.15.30",
4444
"@types/react": "^19.1.6",
4545
"clipboard-copy": "^4.0.1",
4646
"date-fns": "^2.30.0",

packages/nreact/src/components/search-dialog.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import React from 'react'
2-
3-
import throttle from 'lodash.throttle'
42
import { getBlockParentPage, getBlockTitle } from '@texonom/nutils'
53

64
import { NotionContextConsumer, NotionContextProvider } from '../context'
@@ -12,6 +10,15 @@ import { PageTitle } from './page-title'
1210
import type { SearchParams, SearchResults, APIError } from '@texonom/ntypes'
1311
// TODO: modal.default.setAppElement('.notion-viewport')
1412

13+
// simple debounce utility so we only search after the user stops typing
14+
const debounce = (func: (...args: any[]) => void, wait: number) => {
15+
let timeout: ReturnType<typeof setTimeout> | undefined
16+
return (...args: any[]) => {
17+
if (timeout) clearTimeout(timeout)
18+
timeout = setTimeout(() => func(...args), wait)
19+
}
20+
}
21+
1522
export class SearchDialog extends React.Component<{
1623
isOpen: boolean
1724
rootBlockId: string
@@ -34,7 +41,8 @@ export class SearchDialog extends React.Component<{
3441
_search: any
3542

3643
componentDidMount() {
37-
this._search = throttle(this._searchImpl.bind(this), 1000)
44+
// debounce search calls so the expensive query only runs after typing stops
45+
this._search = debounce(this._searchImpl.bind(this), 500)
3846
this._warmupSearch()
3947
}
4048

@@ -66,7 +74,7 @@ export class SearchDialog extends React.Component<{
6674
placeholder='Search'
6775
value={query}
6876
ref={this._inputRef}
69-
onChange={this._onChangeQuery}
77+
onInput={this._onChangeQuery}
7078
/>
7179

7280
{query && (

0 commit comments

Comments
 (0)