Skip to content

Commit

Permalink
Merge pull request #334 from significa/sign-622-notion-handbook
Browse files Browse the repository at this point in the history
feat: add notion handbook integration
  • Loading branch information
nunopolonia authored Feb 16, 2024
2 parents 22d6bfc + 5c9f7fd commit 1c04f98
Show file tree
Hide file tree
Showing 48 changed files with 1,316 additions and 444 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ vite.config.ts.timestamp-*

.vercel

static/sitemap.xml
static/sitemap.xml

# Handbook data
static/handbook/*
handbook-data.json
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "vite dev",
"prebuild": "npm run datasources && npm run sitemap",
"prebuild": "npm run datasources && npm run sitemap && npm run handbook -- -d",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
Expand All @@ -18,6 +18,7 @@
"sb:pull": "npx storyblok@latest pull-components --space=198185",
"sb:types": "npx storyblok-generate-ts@latest source=./components.198185.json target=./src/types/bloks.d.ts && prettier ./src/types/bloks.d.ts --write",
"sb": "npm run sb:pull && npm run sb:types",
"handbook": "svelte-kit sync && npx ts-node@latest --project ./node.tsconfig.json ./scripts/handbook.ts",
"datasources": "svelte-kit sync && npx ts-node@latest --project ./node.tsconfig.json ./scripts/datasources.ts",
"sitemap": "svelte-kit sync && npx ts-node@latest --project ./node.tsconfig.json ./scripts/sitemap.ts",
"prepare": "husky install"
Expand Down Expand Up @@ -61,7 +62,7 @@
"@aws-sdk/client-ses": "3.413.0",
"@aws-sdk/s3-request-presigner": "3.413.0",
"@melt-ui/svelte": "^0.70.0",
"@notionhq/client": "^2.2.13",
"@notionhq/client": "^2.2.14",
"@significa/svelte-ui": "^0.0.33",
"@storyblok/js": "^2.3.0",
"matter-js": "^0.19.0",
Expand Down
220 changes: 220 additions & 0 deletions scripts/handbook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { exec } from 'child_process';
import fs from 'fs';
import https from 'https';
import * as dotenv from 'dotenv';
import { Client } from '@notionhq/client';
import type {
BlockObjectResponse,
PageObjectResponse
} from '@notionhq/client/build/src/api-endpoints';

dotenv.config();

const NOTION_DB_HANDBOOK = process.env.NOTION_DB_HANDBOOK;
const NOTION_TOKEN = process.env.NOTION_TOKEN;

const COVERS_PATH = './static/handbook/covers/';
const IMAGES_PATH = './static/handbook/images/';
const FILES_PATH = './static/handbook/files/';
const SEO_IMAGES_PATH = './static/handbook/seo/';
const FILENAME = './handbook-data.json';

if (!NOTION_TOKEN) {
throw new Error('Missing env var: NOTION_TOKEN');
}

if (!NOTION_DB_HANDBOOK) {
throw new Error('Missing required env var: NOTION_DB_HANDBOOK');
}

const notion = new Client({ auth: NOTION_TOKEN });

function downloadFile(image, filePath) {
const verbose =
process.argv.indexOf('-v') > -1 || process.argv.indexOf('--verbose') > -1 ? true : false;

const file = fs.createWriteStream(filePath);
let url;

if (image.type === 'external') {
url = image.external.url;
} else if (image.type === 'file') {
url = image.file.url;
}

https
.get(url, (response) => {
response.pipe(file);

file.on('finish', () => {
file.close();
verbose && console.log(`File downloaded to ${filePath}`);
});
})
.on('error', (err) => {
fs.unlink(filePath, () => {});
console.error(`Error downloading file: ${err.message}`);
});
}

async function getChildren(id) {
const verbose =
process.argv.indexOf('-v') > -1 || process.argv.indexOf('--verbose') > -1 ? true : false;
const download =
process.argv.indexOf('-d') > -1 || process.argv.indexOf('--download') > -1 ? true : false;

let blocks: BlockObjectResponse[] = [];
let cursor;

verbose && console.log('Fetching page: ', id);

/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
const { results, next_cursor } = await notion.blocks.children.list({
start_cursor: cursor,
block_id: id
});

blocks = <BlockObjectResponse[]>[...blocks, ...results];

if (!next_cursor) {
break;
}
cursor = next_cursor;
}

const values = Promise.all(
blocks.map(async ({ id, has_children, ...block }) => {
let children;

verbose && console.log('Fetching children: ', id);

if (block.type === 'image' && download) {
const path = `${IMAGES_PATH}${id}`;

downloadFile(block.image, path);
}

if (block.type === 'file' && download) {
const path = `${FILES_PATH}${block.file.name}`;

downloadFile(block.file, path);
}

if (
block.type === 'synced_block' &&
block?.synced_block?.synced_from &&
'synced_from' in block.synced_block
) {
const syncedBlock = await notion.blocks.retrieve({
block_id: block.synced_block.synced_from.block_id
});

children = await getChildren(block.synced_block.synced_from.block_id);

return {
...syncedBlock,
children: children
};
}

if (has_children) {
children = await getChildren(id);
}

return {
id,
has_children,
...block,
children: children
};
})
);

return values;
}

async function main() {
const verbose =
process.argv.indexOf('-v') > -1 || process.argv.indexOf('--verbose') > -1 ? true : false;
const download =
process.argv.indexOf('-d') > -1 || process.argv.indexOf('--download') > -1 ? true : false;

let pages: PageObjectResponse[] = [];
let cursor;

if (NOTION_DB_HANDBOOK) {
verbose && console.log('Starting data fetch');
verbose && console.log('Fetching page list');

if (download) {
!fs.existsSync(COVERS_PATH) && fs.mkdirSync(COVERS_PATH, { recursive: true });
!fs.existsSync(FILES_PATH) && fs.mkdirSync(FILES_PATH, { recursive: true });
!fs.existsSync(IMAGES_PATH) && fs.mkdirSync(IMAGES_PATH, { recursive: true });
!fs.existsSync(SEO_IMAGES_PATH) && fs.mkdirSync(SEO_IMAGES_PATH, { recursive: true });
}

/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
const { results, next_cursor } = await notion.databases.query({
database_id: NOTION_DB_HANDBOOK,
start_cursor: cursor,
filter: {
property: 'Status',
status: {
equals: 'Ready to Publish'
}
}
});

pages = [...pages, ...(results as PageObjectResponse[])];

if (!next_cursor) {
break;
}
cursor = next_cursor;
}

if (download) {
pages.map((page) => {
if ('cover' in page && page.cover) {
const path = `${COVERS_PATH}${page.id}`;

downloadFile(page.cover, path);
}

if (
'files' in page.properties['OG image'] &&
page.properties['OG image'].files[0] &&
'file' in page.properties['OG image'].files[0]
) {
const path = `${SEO_IMAGES_PATH}${page.id}`;

downloadFile(page.properties['OG image'].files[0], path);
}
});
}

const children = pages.map(async ({ id, ...page }) => {
const children = await getChildren(id);

return {
id,
...page,
children: children
};
});

Promise.all(children).then((values) => {
verbose && console.log('Data fetch complete');
verbose && console.log('File writing started');

fs.writeFileSync(`${FILENAME}`, JSON.stringify(values));
exec(`npx prettier --write ${FILENAME}`);

verbose && console.log('File writing complete');
});
}
}

main();
1 change: 0 additions & 1 deletion scripts/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ async function main() {
return (
story.content.component === 'blog-post' ||
story.content.component === 'career' ||
story.content.component === 'handbook' ||
story.content.component === 'page' ||
story.content.component === 'project' ||
story.content.component === 'team-member'
Expand Down
Binary file added src/assets/handbook/HandbookOG.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 147 additions & 0 deletions src/assets/handbook/handbook.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/handbook/handbook_placeholder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions src/components/handbook-navigation.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import { getAnchorFromCmsLink } from '$lib/utils/cms';
import type { ConfigurationStoryblok } from '$types/bloks';
import { Button, Icon, Link, Logo } from '@significa/svelte-ui';
import clsx from 'clsx';
import AnHandAndABook from './an-hand-and-a-book.svelte';
import { sanitizeSlug } from '$lib/utils/paths';
import { t } from '$lib/i18n';
export let configuration: ConfigurationStoryblok;
</script>

<header
class={clsx('z-30 w-full border-b bg-background/95 backdrop-blur-md border-b-border fixed top-0')}
>
<div class={clsx('flex items-center justify-between py-4 container mx-auto px-container')}>
<a class="flex items-center gap-2" aria-label="Go to homepage" href="/handbook">
<Logo class="mt-1" variant="wordmark" />
<AnHandAndABook />
</a>

<div class="flex items-center gap-8">
<div class="flex items-center gap-4">
{#if configuration.call_to_action?.length}
{@const { href } = getAnchorFromCmsLink(configuration.call_to_action[0].link)}
<div class="block">
<Button as="a" {href}>{configuration.call_to_action[0].label}</Button>
</div>
{/if}
</div>

<div class="items-center gap-1 text-sm font-medium leading-relaxed hidden lg:flex">
<Icon icon="home" />
<Link href={sanitizeSlug('/')}>
{t('handbook.navigation.website')}
</Link>
</div>
</div>
</div>
</header>
Loading

0 comments on commit 1c04f98

Please sign in to comment.