diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..18354f2 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Connection to Notion API +# see: https://developers.notion.com/docs/create-a-notion-integration +NOTION_API_KEY="" + +# Edge Config is a key-value data store associated with your Vercel account +# see: https://vercel.com/docs/concepts/edge-network/edge-config/get-started +EDGE_CONFIG="https://edge-config.vercel.com/your_edge_config_id_here?token=your_edge_config_read_access_token_here" diff --git a/package.json b/package.json index c3457d5..acc1a95 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,14 @@ }, "dependencies": { "@next/font": "13.1.6", + "@notionhq/client": "^2.2.3", "@radix-ui/react-collapsible": "^1.0.1", "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.2", "@types/node": "18.13.0", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", + "@vercel/edge-config": "^0.1.1", "eslint": "8.33.0", "eslint-config-next": "13.1.6", "lucide-react": "^0.112.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad7526c..d8edf28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,12 +2,14 @@ lockfileVersion: 5.4 specifiers: '@next/font': 13.1.6 + '@notionhq/client': ^2.2.3 '@radix-ui/react-collapsible': ^1.0.1 '@radix-ui/react-dropdown-menu': ^2.0.2 '@radix-ui/react-scroll-area': ^1.0.2 '@types/node': 18.13.0 '@types/react': 18.0.27 '@types/react-dom': 18.0.10 + '@vercel/edge-config': ^0.1.1 autoprefixer: ^10.4.13 eslint: 8.33.0 eslint-config-next: 13.1.6 @@ -25,12 +27,14 @@ specifiers: dependencies: '@next/font': 13.1.6 + '@notionhq/client': 2.2.3 '@radix-ui/react-collapsible': 1.0.1_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-dropdown-menu': 2.0.2_5ndqzdd6t4rivxsukjv3i3ak2q '@radix-ui/react-scroll-area': 1.0.2_biqbaboplfbrettd7655fr4n2y '@types/node': 18.13.0 '@types/react': 18.0.27 '@types/react-dom': 18.0.10 + '@vercel/edge-config': 0.1.1 eslint: 8.33.0 eslint-config-next: 13.1.6_4vsywjlpuriuw3tl5oq6zy5a64 lucide-react: 0.112.0_react@18.2.0 @@ -267,6 +271,16 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@notionhq/client/2.2.3: + resolution: {integrity: sha512-ZqUzY0iRg/LIrwS+wzz/6osSB2nxIpmqdAtdUwzpcimc9Jlu1j85FeYdaU26Shr193CFrl2TLFeKqpk/APRQ4g==} + engines: {node: '>=12'} + dependencies: + '@types/node-fetch': 2.6.2 + node-fetch: 2.6.9 + transitivePeerDependencies: + - encoding + dev: false + /@pkgr/utils/2.3.1: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -652,6 +666,13 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: false + /@types/node-fetch/2.6.2: + resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} + dependencies: + '@types/node': 18.13.0 + form-data: 3.0.1 + dev: false + /@types/node/18.13.0: resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} dev: false @@ -740,6 +761,11 @@ packages: eslint-visitor-keys: 3.3.0 dev: false + /@vercel/edge-config/0.1.1: + resolution: {integrity: sha512-HDl+12tzW2RHIu8Y804kXg2VpZ02KVb7Nqsa+e1AFoACXXMb+7TfjI1wR19tXoDIu5enQz1dbat40YMEgaH13Q==} + engines: {node: '>=14.6'} + dev: false + /acorn-jsx/5.3.2_acorn@8.8.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -880,6 +906,10 @@ packages: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: false + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /autoprefixer/10.4.13_postcss@8.4.21: resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} @@ -999,6 +1029,13 @@ packages: /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: false @@ -1090,6 +1127,11 @@ packages: /defined/1.0.1: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /detect-node-es/1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} dev: false @@ -1584,6 +1626,15 @@ packages: is-callable: 1.2.7 dev: false + /form-data/3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -2093,6 +2144,18 @@ packages: braces: 3.0.2 picomatch: 2.3.1 + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -2163,6 +2226,18 @@ packages: - babel-plugin-macros dev: false + /node-fetch/2.6.9: + resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-releases/2.0.10: resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} dev: true @@ -2788,6 +2863,10 @@ packages: dependencies: is-number: 7.0.0 + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} dependencies: @@ -2922,6 +3001,17 @@ packages: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: false + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which-boxed-primitive/1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: diff --git a/src/app/others/dev-setup/page.tsx b/src/app/others/dev-setup/page.tsx index fd52753..814d549 100644 --- a/src/app/others/dev-setup/page.tsx +++ b/src/app/others/dev-setup/page.tsx @@ -1,25 +1,21 @@ import { CodePreview } from '@/components/CodePreview' +import { getCodeBlockFromNotion } from '@/lib/notion-client' +import { getNotionPagesId } from '@/lib/vercel-edge-config' import shiki from 'shiki' export const metadata = { title: 'Dev Setup', } -const markdown = ` -# Dev Setup - -- MacBook M1 Max (64gb Memory) -- LG 25" UltraWide Display - -That's it, nothing more. -`.trim() - export default async function DevSetup() { + const { setup_dev } = await getNotionPagesId() + const { content } = await getCodeBlockFromNotion(setup_dev) + const highlighter = await shiki.getHighlighter({ theme: 'rose-pine-moon', }) - const code = highlighter.codeToHtml(markdown, { lang: 'md' }) + const code = highlighter.codeToHtml(content, { lang: 'md' }) return -} \ No newline at end of file +} diff --git a/src/app/others/gaming-setup/page.tsx b/src/app/others/gaming-setup/page.tsx index 09c0ab3..d200940 100644 --- a/src/app/others/gaming-setup/page.tsx +++ b/src/app/others/gaming-setup/page.tsx @@ -1,36 +1,21 @@ import { CodePreview } from '@/components/CodePreview' +import { getCodeBlockFromNotion } from '@/lib/notion-client' +import { getNotionPagesId } from '@/lib/vercel-edge-config' import shiki from 'shiki' export const metadata = { title: 'Gaming Setup', } -const markdown = ` -# Gaming Setup - -- Intel Core i5-9600KF 3.7Ghz -- 2x HyperX Fury 16GB 3000Mhz -- Gigabyte Z390M Gaming -- Cooler Master ATX 500W 80 Plus -- 2x Corsair SSD MP510 480GB NVMe -- Gigabyte NVIDIA GeForce RTX 2060 6G - - -## Peripherals - -- Logitech G PRO Wireless Mouse -- Keychron K2 Keyboard (Brown Switch) -- Samsung 23.5" Curved 144hz 1ms Display - -That's it, nothing more. -`.trim() - export default async function GamingSetup() { + const { setup_gaming } = await getNotionPagesId() + const { content } = await getCodeBlockFromNotion(setup_gaming) + const highlighter = await shiki.getHighlighter({ theme: 'rose-pine-moon', }) - const code = highlighter.codeToHtml(markdown, { lang: 'md' }) + const code = highlighter.codeToHtml(content, { lang: 'md' }) return } diff --git a/src/app/terminal/fish/page.tsx b/src/app/terminal/fish/page.tsx index 38840da..04c4263 100644 --- a/src/app/terminal/fish/page.tsx +++ b/src/app/terminal/fish/page.tsx @@ -1,27 +1,21 @@ import { CodePreview } from '@/components/CodePreview' +import { getCodeBlockFromNotion } from '@/lib/notion-client' +import { getNotionPagesId } from '@/lib/vercel-edge-config' import shiki from 'shiki' export const metadata = { title: 'Fish', } -const fishConfig = `if status is-interactive -# Commands to run in interactive sessions can go here -end - -set SPACEFISH_PROMPT_ADD_NEWLINE false - -starship init fish | source - -# Aliases -# alias cat="bat --theme=\$(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo default || echo GitHub)"` - export default async function FishConfig() { + const { terminal_fish } = await getNotionPagesId() + const { content } = await getCodeBlockFromNotion(terminal_fish) + const highlighter = await shiki.getHighlighter({ theme: 'rose-pine-moon', }) - const code = highlighter.codeToHtml(fishConfig, { lang: 'fish' }) + const code = highlighter.codeToHtml(content, { lang: 'fish' }) - return + return } diff --git a/src/app/terminal/general/page.tsx b/src/app/terminal/general/page.tsx index 289e34f..4415735 100644 --- a/src/app/terminal/general/page.tsx +++ b/src/app/terminal/general/page.tsx @@ -1,37 +1,21 @@ import { CodePreview } from '@/components/CodePreview' +import { getCodeBlockFromNotion } from '@/lib/notion-client' +import { getNotionPagesId } from '@/lib/vercel-edge-config' import shiki from 'shiki' export const metadata = { title: 'Terminal', } -const markdown = ` -# General - -Currently I'm using the combo Fish + Starship in my terminal. - -Fish Shell: https://fishshell.com/ -Starship: https://starship.rs/ - ---- - -I'm also using Warp as my terminal emulator. - -Warp: https://www.warp.dev/ - ---- - -For the theme, I chose Rosé Pine Moon variant: - -Theme: https://github.com/austintraver/warp-theme/blob/main/base16_rose_pine_moon.yaml -`.trim() - export default async function General() { + const { terminal_general } = await getNotionPagesId() + const { content } = await getCodeBlockFromNotion(terminal_general) + const highlighter = await shiki.getHighlighter({ theme: 'rose-pine-moon', }) - const code = highlighter.codeToHtml(markdown, { lang: 'md' }) + const code = highlighter.codeToHtml(content, { lang: 'md' }) return } diff --git a/src/lib/notion-client.ts b/src/lib/notion-client.ts new file mode 100644 index 0000000..b90129a --- /dev/null +++ b/src/lib/notion-client.ts @@ -0,0 +1,20 @@ +import { Client, isFullBlock } from '@notionhq/client' +import { CodeBlockObjectResponse } from '@notionhq/client/build/src/api-endpoints' + +export const notionClient = new Client({ auth: process.env.NOTION_API_KEY }) + +export async function getCodeBlockFromNotion(pageId: string) { + const { results } = await notionClient.blocks.children.list({ block_id: pageId }) + + const codeBlock = results.find( + block => isFullBlock(block) && block.type === 'code' + ) as CodeBlockObjectResponse | undefined + + if (!codeBlock) { + throw new Error(`Failed to fetch Notion content of ID: ${pageId}`) + } + + const { plain_text } = codeBlock.code.rich_text[0] + + return { content: plain_text } +} diff --git a/src/lib/vercel-edge-config.ts b/src/lib/vercel-edge-config.ts new file mode 100644 index 0000000..dd866a1 --- /dev/null +++ b/src/lib/vercel-edge-config.ts @@ -0,0 +1,15 @@ +import { get } from '@vercel/edge-config' +import { z } from 'zod' + +const notionPagesIdStore = z.object({ + terminal_general: z.string(), + terminal_fish: z.string(), + setup_dev: z.string(), + setup_gaming: z.string(), +}) + +export async function getNotionPagesId() { + const pagesId = await get('notion') + + return notionPagesIdStore.parse(pagesId) +}