Skip to content

Commit fcc2b1a

Browse files
committed
chore: cleanup notion parsing for client display
1 parent cc8b7f7 commit fcc2b1a

File tree

11 files changed

+182
-101
lines changed

11 files changed

+182
-101
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"rehype-slug": "^5.1.0",
3535
"svelte": "^3.44.0",
3636
"svelte-check": "^2.7.1",
37-
"svelte-highlight": "^7.1.2",
3837
"svelte-icons": "^2.1.0",
3938
"svelte-preprocess": "^4.10.7",
4039
"tailwindcss": "^3.1.5",

src/components/home/Client.svelte

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,48 @@
11
<script lang="ts">
2-
import type { ClientItem } from 'src/routes/+page.server';
2+
import type {
3+
DateProperty,
4+
NotionPage,
5+
RichTextProperty,
6+
TitleProperty,
7+
UrlProperty
8+
} from '$lib/notion';
9+
import NotionRichText from '$lib/notion/blocks/NotionRichText.svelte';
310
4-
export let client: ClientItem;
11+
export let client: NotionPage;
512
6-
const { name, description, website, logo, cover } = client;
13+
// Extract the properties from the client object
14+
// TODO: Type these properties
15+
const title = client.properties.Name as TitleProperty;
16+
const description = client.properties.Description as RichTextProperty;
17+
const website = client.properties.Website as UrlProperty;
18+
const date = client.properties['Working Since'] as DateProperty;
719
20+
// Date formatting
821
const formatDate = (date: Date) =>
922
date.toLocaleDateString('en-US', {
1023
month: 'long',
1124
year: 'numeric'
1225
});
1326
14-
const start = formatDate(client.dates.start);
15-
const end = client.dates.end ? formatDate(client.dates.end) : 'Present';
27+
const start = formatDate(new Date(date.date.start));
28+
const end = date.date.end ? formatDate(new Date(date.date.end)) : 'Present';
1629
</script>
1730

18-
<a href={website} rel="noopener noreferrer" target="_blank">
31+
<a href={website.url} rel="noopener noreferrer" target="_blank">
1932
<div
2033
class="flex h-full flex-col gap-2 overflow-clip rounded-md border-[1px] border-neutral-300 bg-neutral-100 bg-opacity-70 transition-all hover:scale-[101%] dark:border-neutral-700 dark:bg-neutral-800"
2134
>
22-
{#if cover}
35+
{#if client.cover}
2336
<div class="relative">
24-
<img src={cover} class="h-40 w-full object-cover" alt="Company cover" />
37+
<img
38+
src={client.cover[client.cover.type].url}
39+
class="h-40 w-full object-cover"
40+
alt="Company cover"
41+
/>
2542

26-
{#if logo}
43+
{#if client.icon}
2744
<img
28-
src={logo}
45+
src={client.icon[client.icon.type].url}
2946
class="absolute bottom-2 left-2 h-10 w-10 rounded-md border-[1px] border-neutral-300 bg-neutral-400 object-cover dark:border-neutral-700"
3047
alt="Company logo"
3148
/>
@@ -35,8 +52,12 @@
3552

3653
<div class="flex h-full flex-col justify-between px-4 pb-4">
3754
<div>
38-
<h2 class="font-medium text-blue-400">{name}</h2>
39-
<p class="text-body text-ellipsis line-clamp-2">{description}</p>
55+
<h2 class="font-medium text-blue-400">
56+
<NotionRichText text={title.title} />
57+
</h2>
58+
<p class="text-body text-ellipsis line-clamp-2">
59+
<NotionRichText text={description.rich_text} />
60+
</p>
4061
</div>
4162

4263
<div class="pt-2">

src/lib/notion/blocks/NotionCode.svelte

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script lang="ts">
2-
import { HighlightAuto } from 'svelte-highlight';
32
import type { RichTextDataText } from '$lib/notion';
43
54
export let data: {
@@ -14,6 +13,6 @@
1413

1514
<div class="py-2">
1615
<div class="overflow-clip rounded-lg">
17-
<HighlightAuto {code} />
16+
<pre>{code}</pre>
1817
</div>
1918
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script lang="ts">
2+
import type { NotionEmoji } from '../notion.types';
3+
4+
export let data: NotionEmoji;
5+
</script>
6+
7+
<div>
8+
<span class="text-4xl">{data.emoji}</span>
9+
</div>

src/lib/notion/blocks/NotionImage.svelte

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
import type { NotionFile, NotionRichTextData } from '../index';
33
import NotionRichText from './NotionRichText.svelte';
44
5-
export let data: {
6-
caption: NotionRichTextData[];
7-
type: 'file';
8-
file: NotionFile;
5+
export let data: NotionFile & {
6+
caption?: NotionRichTextData[];
97
};
108
</script>
119

src/lib/notion/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from './notion.types';
22
export * from './notion.renderer';
3-
export * from './notion.utils';

src/lib/notion/notion.utils.ts renamed to src/lib/notion/notion.server.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NOTION_KEY } from '$env/static/private';
22
import { Client } from '@notionhq/client';
3-
import type { NotionBlock } from './notion.types';
3+
import type { NotionBlock, NotionPage as NotionPage } from './notion.types';
4+
import type { QueryDatabaseParameters } from '@notionhq/client/build/src/api-endpoints';
45

56
/**
67
* Notion client
@@ -47,3 +48,21 @@ export const getAllBlocks = async (blockId: string) => {
4748

4849
return children.flat();
4950
};
51+
52+
/**
53+
* Query a database for pages.
54+
*
55+
* @param databaseId The database ID.
56+
* @param filter The filter.
57+
*/
58+
export const queryDatabase = async (
59+
databaseId: string,
60+
filter?: QueryDatabaseParameters['filter']
61+
): Promise<NotionPage[]> => {
62+
const { results } = await notionClient.databases.query({
63+
database_id: databaseId,
64+
filter
65+
});
66+
67+
return results as unknown as NotionPage[];
68+
};

src/lib/notion/notion.types.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,95 @@ export type NotionEmoji = {
121121
};
122122

123123
export type NotionFile = {
124+
type: 'file';
125+
file: {
126+
url: string;
127+
expiry_time: string;
128+
};
129+
};
130+
131+
export type NotionExternal = {
132+
type: 'external';
133+
external: {
134+
url: string;
135+
};
136+
};
137+
138+
/**
139+
* Notion Database
140+
* @see https://developers.notion.com/reference/database
141+
* @see https://developers.notion.com/reference/property-object
142+
*/
143+
export type NotionPage = {
144+
object: 'database';
145+
id: string;
146+
147+
created_time: string; // ISO 8601
148+
last_edited_time: string; // ISO 8601
149+
150+
cover?: NotionFile | NotionExternal;
151+
icon?: NotionEmoji | NotionFile | NotionExternal;
152+
153+
properties: {
154+
[key: string]: NotionProperty;
155+
};
156+
};
157+
158+
export type NotionProperty = GenericProperty &
159+
TitleProperty &
160+
RichTextProperty &
161+
CheckboxProperty &
162+
UrlProperty &
163+
DateProperty;
164+
165+
export type GenericProperty = {
166+
id: string;
167+
type:
168+
| 'title'
169+
| 'rich_text'
170+
| 'number'
171+
| 'select'
172+
| 'multi_select'
173+
| 'date'
174+
| 'people'
175+
| 'file'
176+
| 'checkbox'
177+
| 'url'
178+
| 'email'
179+
| 'phone_number'
180+
| 'formula'
181+
| 'relation'
182+
| 'rollup'
183+
| 'created_time'
184+
| 'created_by'
185+
| 'last_edited_time'
186+
| 'last_edited_by';
187+
};
188+
189+
export type TitleProperty = {
190+
type: 'title';
191+
title: NotionRichTextData[];
192+
};
193+
194+
export type RichTextProperty = {
195+
type: 'rich_text';
196+
rich_text: NotionRichTextData[];
197+
};
198+
199+
export type CheckboxProperty = {
200+
type: 'checkbox';
201+
checkbox: boolean;
202+
};
203+
204+
export type UrlProperty = {
205+
type: 'url';
124206
url: string;
125-
expiry_time: string;
207+
};
208+
209+
export type DateProperty = {
210+
type: 'date';
211+
date: {
212+
start: string; // ISO 8601
213+
end?: string; // ISO 8601
214+
};
126215
};

src/routes/+layout.svelte

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
<script lang="ts">
2-
// Highlight Js
3-
import highlightTheme from 'svelte-highlight/styles/material-darker';
4-
52
// Layout
63
import Footer from '$components/base/Footer.svelte';
74
import GradientCanvas from '$components/base/GradientCanvas.svelte';
@@ -28,10 +25,6 @@
2825
}
2926
</script>
3027

31-
<svelte:head>
32-
{@html highlightTheme}
33-
</svelte:head>
34-
3528
<div class="mx-auto flex min-h-screen w-full flex-col">
3629
<div class="px-8 pt-8">
3730
<NavBar />

src/routes/+page.server.ts

Lines changed: 27 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,42 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { NOTION_CLIENTS_DB_ID, NOTION_KEY } from '$env/static/private';
3-
import { Client } from '@notionhq/client';
4-
import type { PageObjectResponse } from '@notionhq/client/build/src/api-endpoints';
5-
import { convertNotionDates, convertRichText } from '$lib/util/notion';
2+
import { NOTION_CLIENTS_DB_ID } from '$env/static/private';
3+
import type { DateProperty } from '$lib/notion';
4+
import { queryDatabase } from '$lib/notion/notion.server';
65
import type { ServerLoadEvent } from '@sveltejs/kit';
76

8-
const notion = new Client({ auth: NOTION_KEY });
9-
107
// noinspection JSUnusedGlobalSymbols
118
export const load = async ({ fetch }: ServerLoadEvent) => {
9+
// Fetch pinned repositories
1210
const req = await fetch('https://gh-pinned-repos.egoist.dev/?username=KodingDev');
1311
const pinned: Repository[] = await req.json();
1412

15-
const pages = await notion.databases
16-
.query({
17-
database_id: NOTION_CLIENTS_DB_ID,
18-
filter: {
19-
property: 'Public',
20-
checkbox: {
21-
equals: true
22-
}
23-
}
24-
})
25-
.catch((err) => {
26-
console.error('Failed to fetch clients from Notion', err);
27-
return { results: [] };
28-
});
29-
30-
const clients: ClientItem[] = pages.results
31-
.map((_page) => {
32-
const page = _page as PageObjectResponse;
33-
const properties = page.properties;
34-
35-
// Type assertion
36-
if (properties.Name.type !== 'title') throw new Error('Name is not a title');
37-
if (properties.Description.type !== 'rich_text')
38-
throw new Error('Description is not rich text');
39-
if (properties.Website.type !== 'url') throw new Error('Website is not a URL');
40-
if (properties['Working Since'].type !== 'date')
41-
throw new Error('Working Since is not a date');
42-
43-
if (page.icon?.type !== 'file') throw new Error('Logo is not a file');
44-
if (page.cover?.type !== 'file') throw new Error('Cover is not a file');
45-
46-
// Convert dates
47-
const dates = properties['Working Since'].date
48-
? convertNotionDates(properties['Working Since'].date)
49-
: { start: new Date(), end: null };
13+
// Fetch clients
14+
const pages = await queryDatabase(NOTION_CLIENTS_DB_ID, {
15+
property: 'Public',
16+
checkbox: {
17+
equals: true
18+
}
19+
}).catch((err) => {
20+
console.error('Failed to fetch clients from Notion', err);
21+
return [];
22+
});
23+
24+
// Type the clients
25+
const clients = pages
26+
.map((page) => {
27+
// Dates
28+
const prop = page.properties['Working Since'] as DateProperty;
29+
30+
// Convert date
31+
const date = prop.date.end ? new Date(prop.date.end) : new Date();
5032

5133
return {
52-
dates,
53-
properties,
54-
55-
name: convertRichText(properties.Name.title) || '',
56-
description: convertRichText(properties.Description.rich_text) || 'Very mysterious!',
57-
website: properties.Website.url || undefined,
58-
logo: page.icon.file.url,
59-
cover: page.cover.file.url
34+
page,
35+
date
6036
};
6137
})
62-
.sort((a, b) => (b.dates.end ?? new Date()).getTime() - (a.dates.end ?? new Date()).getTime());
38+
.sort((a, b) => b.date.getTime() - a.date.getTime())
39+
.map((item) => item.page);
6340

6441
return { pinned, clients };
6542
};
@@ -78,13 +55,3 @@ export interface Skill {
7855
color: string;
7956
link?: string;
8057
}
81-
82-
export interface ClientItem {
83-
name: string;
84-
description: string;
85-
website?: string;
86-
dates: ReturnType<typeof convertNotionDates>;
87-
88-
logo?: string;
89-
cover?: string;
90-
}

0 commit comments

Comments
 (0)