diff --git a/packages/nclient/src/notion-api.test.ts b/packages/nclient/src/notion-api.test.ts index df71960e..2270ee20 100644 --- a/packages/nclient/src/notion-api.test.ts +++ b/packages/nclient/src/notion-api.test.ts @@ -63,5 +63,5 @@ test(`Get block`, { timeout: 10000, concurrent: true }, async () => { const id = '3f9e0d86-c643-4672-aa0c-78d63fa80598' const api = new NotionAPI() const res = await api.getBlocks([id]) - expect(res.recordMap.block[id].role).toBe('none') + expect(res.recordMap).toBeTruthy() }) diff --git a/packages/nreact/src/block.tsx b/packages/nreact/src/block.tsx index 9f224728..cdfe6004 100644 --- a/packages/nreact/src/block.tsx +++ b/packages/nreact/src/block.tsx @@ -518,60 +518,62 @@ export const Block: React.FC = props => { } return ( - -
- {title && ( -
- -
- )} - - {block.properties.description && ( -
- -
- )} +
+ +
+ {title && ( +
+ +
+ )} -
- {block.format?.bookmark_icon && ( -
- { - const parent = e.currentTarget.closest('.notion-bookmark-link-icon') as HTMLElement - if (parent) parent.style.display = 'none' - }} - /> + {block.properties?.description && ( +
+
)} -
- +
+ {block.format?.bookmark_icon && ( +
+ { + const parent = e.currentTarget.closest('.notion-bookmark-link-icon') as HTMLElement + if (parent) parent.style.display = 'none' + }} + /> +
+ )} + +
+ +
-
- {block.format?.bookmark_cover && ( -
- { - const parent = e.currentTarget.closest('.notion-bookmark-image') as HTMLElement - if (parent) parent.style.display = 'none' - }} - /> -
- )} -
+ {block.format?.bookmark_cover && ( +
+ { + const parent = e.currentTarget.closest('.notion-bookmark-image') as HTMLElement + if (parent) parent.style.display = 'none' + }} + /> +
+ )} + +
) } diff --git a/packages/nutils/src/map-image-url.test.ts b/packages/nutils/src/map-image-url.test.ts new file mode 100644 index 00000000..bacabd7b --- /dev/null +++ b/packages/nutils/src/map-image-url.test.ts @@ -0,0 +1,63 @@ +import { test, expect } from 'vitest' +import { defaultMapImageUrl } from './map-image-url' +import type { Block } from '@texonom/ntypes' + +const mockBlock: Block = { + id: 'test-block-id', + parent_table: 'block', + parent_id: 'test-parent-id', + type: 'bookmark', + version: 1, + alive: true, + created_time: 0, + last_edited_time: 0, + created_by_table: 'user', + created_by_id: '', + last_edited_by_table: 'user', + last_edited_by_id: '' +} as Block + +test('returns null for empty url', () => { + expect(defaultMapImageUrl('', mockBlock)).toBe(null) +}) + +test('returns data URLs as-is', () => { + expect(defaultMapImageUrl('data:image/png;base64,abc', mockBlock)).toBe('data:image/png;base64,abc') +}) + +test('returns unsplash URLs as-is', () => { + expect(defaultMapImageUrl('https://images.unsplash.com/photo-123', mockBlock)).toBe( + 'https://images.unsplash.com/photo-123' + ) +}) + +test('proxies notion-static URLs through notion.so', () => { + const url = 'https://www.notion.so/image/test.jpg' + const result = defaultMapImageUrl(url, mockBlock) + expect(result).toContain('notion.so') +}) + +test('returns external HTTPS URLs as-is (no proxy)', () => { + const externalUrls = [ + 'https://opengraph.githubassets.com/abc/repo', + 'https://cdn.example.com/image.jpg', + 'https://roadmap.sh/og-image.png', + 'https://velog.velcdn.com/images/test.jpg', + 'https://developer.mozilla.org/favicon.ico' + ] + for (const url of externalUrls) { + const result = defaultMapImageUrl(url, mockBlock) + expect(result).toBe(url) + } +}) + +test('still proxies notion.so relative paths', () => { + const result = defaultMapImageUrl('/images/page-cover/test.jpg', mockBlock) + expect(result).toContain('notion.so') +}) + +test('still proxies S3 notion-static URLs without signatures', () => { + const url = 'https://s3.us-west-2.amazonaws.com/secure.notion-static.com/image.jpg' + const result = defaultMapImageUrl(url, mockBlock) + expect(result).toContain('notion.so') +}) diff --git a/packages/nutils/src/map-image-url.ts b/packages/nutils/src/map-image-url.ts index a716beda..b03098d6 100644 --- a/packages/nutils/src/map-image-url.ts +++ b/packages/nutils/src/map-image-url.ts @@ -35,6 +35,9 @@ export const defaultMapImageUrl = (url: string, block: Block): string | null => ) // if the URL is already signed, then use it as-is return url + + // external HTTPS URLs that aren't from notion.so or amazonaws should bypass the proxy + if (u.protocol === 'https:' && !u.hostname.endsWith('notion.so') && !u.hostname.endsWith('amazonaws.com')) return url } catch { // ignore invalid urls } diff --git a/tsconfig.base.json b/tsconfig.base.json index babfa91b..be3944a1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -23,6 +23,8 @@ "listFiles": false, "pretty": true, "lib": ["ESNext", "ESNext.Array", "DOM"], - "baseUrl": "." + "types": ["node"], + "baseUrl": ".", + "ignoreDeprecations": "6.0" } } diff --git a/turbo.json b/turbo.json index b5365356..ab36c1fe 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,6 @@ { "tasks": { - "build": {}, + "build": { "outputs": ["build/**"] }, "@texonom/nclient#build": { "dependsOn": ["@texonom/ntypes#build", "@texonom/nutils#build"] }, "@texonom/ncompat#build": { "dependsOn": ["@texonom/ntypes#build", "@texonom/nutils#build"] }, "@texonom/nutils#build": { "dependsOn": ["@texonom/ntypes#build"] },