-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #334 from significa/sign-622-notion-handbook
feat: add notion handbook integration
- Loading branch information
Showing
48 changed files
with
1,316 additions
and
444 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.