diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d828299 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e73f84c --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Samuel Kraft +Copyright (c) 2021 Giuseppe Pascale + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..bcfa855 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "baseUrl": "." + } +} \ No newline at end of file diff --git a/lib/notion.js b/lib/notion.js new file mode 100644 index 0000000..b946e7e --- /dev/null +++ b/lib/notion.js @@ -0,0 +1,293 @@ +/* + * SERVER SIDE + * Shortcuts for the Server calls + */ + +import { Client, APIResponseError } from "@notionhq/client"; + +const notion = new Client({ + auth: process.env.NOTION_TOKEN, +}); + +export const getDatabase = async (databaseId) => { + const response = await notion.databases.query({ + database_id: databaseId, + }); + return response.results; +}; + +export const getPage = async (pageId) => { + const response = await notion.pages.retrieve({ page_id: pageId }); + return response; +}; + +export const getBlocks = async (blockId) => { + let r = undefined; + try{ + r = await notion.blocks.children.list({ + block_id: blockId, + page_size: 100, + }); + } catch (APIResponseError) { + // TODO: needs review. It may be a problem with the Notion API itself. + console.log('debug: ' + blockId); + } + return r ? r.results : null; +}; + +export const search = async (query) => { + const response = await notion.search({ + query, + sort: { + direction: 'ascending', + timestamp: 'last_edited_time', + } + }); + return response; +}; + +/* + * CLIENT SIDE + * Notion block and text interpreter plus some client utils. + */ +import TeX from '@matejmazur/react-katex'; +import { Fragment, useEffect, useState } from 'react'; +import QRCode from "react-qr-code"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { monoBlue } from 'react-syntax-highlighter'; + + +const API_HOST = process.env.API_HOST; +const FAVICON_API_URL = "https://www.google.com/s2/favicons?sz=64&domain_url=" +const API_URL = '/api'; + +export const getColor = (c) => c; +export const getFavicon = (u) => FAVICON_API_URL + u; +export const getFormattedId = (id) => id.substr(0,8) + '-' + id.substr(8, 4) + '-' + id.substr(12, 4) + '-' + id.substr(16, 4) + '-' + id.substr(20, 12); + +export const getClientSearch = (q) => { + const searchUrl = API_URL + `/search?q=${q}`; + return fetch(searchUrl).then((r) => r.json()).then(d => d); +}; + +export const Text = ({ text, icon }) => { + if (!text) { + return null; + } + + return text.map((value, index) => { + const { + annotations + } = value; + + + switch (value.type) { + case 'equation': + const equation = value.equation + return ( + {equation.expression} + ) + case 'text': + const text = value.text; + return ( + { + if (v == "color") return annotations[v] == "default" ? null : annotations[v]; + return annotations[v] == true ? v : undefined; + }).join(" ")} + key={index} + > + {text.link + ? {text.content} + : text.content + } + + ) + + default: + console.log(value); + const content = value.plain_text; + return ( + { + if (v == "color") return annotations[v] == "default" ? null : annotations[v]; + return annotations[v] == true ? v : undefined; + }).join(" ")} + key={index} + > + {content} + + ) + } + }); +}; + +export const Cover = ({ cover, alt }) => { + let url; + if (!cover) return null; + else + if (cover.type == "external") url = cover.external.url; + else if (cover.type == "file") url = cover.file.url; + + return ( + {alt} + ) +} + +export const BookmarkComponent = ({ url, caption }) => { + const [bookmarkMetadata, setBookmarkMetadata] = useState(undefined); + + return ( + +
+
+
+ +

{url}

+
+ +
+

+ +

+
+
+ ) + +} + +export const renderBlock = (block) => { + const { type, id } = block; + const value = block[type]; + + switch (type) { + case "paragraph": + return ( +

+ +

+ ); + case "heading_1": + return ( +

+ +

+ ); + case "heading_2": + return ( +

+ +

+ ); + case "heading_3": + return ( +

+ +

+ ); + case "bulleted_list_item": + case "numbered_list_item": + return ( +
  • + +
  • + ); + case "to_do": + return ( +
    + +
    + ); + case "toggle": + return ( +
    + + + + {value.children?.map((block) => ( + {renderBlock(block)} + ))} +
    + ); + case "child_page": + return

    {value.title}

    ; + case "image": + const src = + value.type === "external" ? value.external.url : value.file.url; + const caption = value.caption.length > 0 ? value.caption[0].plain_text : ""; + return ( +
    + {caption} + {caption &&
    {caption}
    } +
    + ); + case "embed": + const url = value.url; + return ( +
    +