From 6b9dd12883201c3a56d8e08135c974a8d8fce627 Mon Sep 17 00:00:00 2001 From: ctbw Date: Fri, 23 Feb 2024 11:43:25 -0600 Subject: [PATCH 1/3] v1 -- forked code from travis fischer template. gets blog up and running --- .changeset/config.json | 2 +- .editorconfig | 2 +- .env.example | 25 + .eslintignore | 10 - .eslintrc | 38 - .eslintrc.json | 27 + .github/issue_template.md | 15 + .github/pull_request_template.md | 15 + .github/workflows/build.yml | 22 + .gitignore | 5 +- .nvmrc | 1 - .prettierignore | 5 +- .prettierrc | 20 +- .vscode/launch.json | 27 + .vscode/settings.json | 37 +- CHANGELOG.md | 6 +- LICENSE | 121 - README.md | 16 - __mocks__/fileMock.ts | 3 - __mocks__/styleMock.ts | 3 - __tests__/index.test.js | 19 - components/ErrorPage.tsx | 24 + components/Footer.tsx | 145 + components/Loading.tsx | 10 + components/LoadingIcon.tsx | 63 + components/NotionPage.tsx | 284 + components/NotionPageHeader.tsx | 88 + components/Page404.tsx | 39 + components/PageActions.tsx | 35 + components/PageAside.tsx | 30 + components/PageHead.tsx | 93 + components/PageSocial.module.css | 141 + components/PageSocial.tsx | 94 + components/styles.module.css | 220 + contributing.md | 83 + jest.config.ts | 26 - jest.setup.ts | 1 - lib/acl.ts | 55 + lib/bootstrap-client.ts | 13 + lib/config.ts | 221 + lib/db.ts | 14 + lib/get-canonical-page-id.ts | 27 + lib/get-config-value.ts | 55 + lib/get-page-tweet.ts | 10 + lib/get-site-map.ts | 83 + lib/get-social-image-url.ts | 16 + lib/map-image-url.ts | 12 + lib/map-page-url.ts | 43 + lib/notion-api.ts | 5 + lib/notion.ts | 70 +- lib/oembed.ts | 64 + lib/preview-images.ts | 76 + lib/resolve-notion-page.ts | 91 + lib/search-notion.ts | 50 +- lib/site-config.ts | 46 + lib/types.ts | 72 + lib/use-dark-mode.ts | 10 + license | 21 + next.config.js | 22 + package.json | 111 +- pages/404.tsx | 3 + pages/[pageId].tsx | 123 +- pages/_app.tsx | 77 +- pages/_document.tsx | 81 +- pages/_error.tsx | 3 + pages/api/notion-page-info.tsx | 154 + pages/api/search-notion.ts | 24 +- pages/api/social-image.tsx | 179 + pages/feed.tsx | 101 + pages/index.tsx | 50 +- pages/robots.txt.tsx | 45 + pages/sitemap.xml.tsx | 57 + patches/notion-client+6.15.6.patch | 318 + pnpm-lock.yaml | 18467 +++++---------------------- public/404.png | Bin 0 -> 63927 bytes public/android-chrome-192x192.png | Bin 5142 -> 0 bytes public/android-chrome-512x512.png | Bin 16510 -> 0 bytes public/apple-touch-icon.png | Bin 4635 -> 0 bytes public/error.png | Bin 0 -> 37295 bytes public/favicon-128x128.png | Bin 0 -> 2964 bytes public/favicon-16x16.png | Bin 318 -> 0 bytes public/favicon-192x192.png | Bin 0 -> 4812 bytes public/favicon-32x32.png | Bin 618 -> 0 bytes public/favicon.ico | Bin 15406 -> 15086 bytes public/favicon.png | Bin 0 -> 608 bytes public/fonts/Inter-Regular.ttf | Bin 0 -> 309828 bytes public/fonts/Inter-SemiBold.ttf | Bin 0 -> 315756 bytes public/manifest.json | 24 + public/site.webmanifest | 1 - readme.md | 8 + site.config.ts | 55 + styles/global.css | 62 + styles/globals.css | 8 - styles/notion.css | 405 + styles/prism-theme.css | 125 + tsconfig.json | 27 +- types/index.ts | 1 - 97 files changed, 7586 insertions(+), 15794 deletions(-) create mode 100644 .env.example delete mode 100644 .eslintignore delete mode 100644 .eslintrc create mode 100644 .eslintrc.json create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build.yml delete mode 100644 .nvmrc create mode 100644 .vscode/launch.json delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 __mocks__/fileMock.ts delete mode 100644 __mocks__/styleMock.ts delete mode 100644 __tests__/index.test.js create mode 100644 components/ErrorPage.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Loading.tsx create mode 100644 components/LoadingIcon.tsx create mode 100644 components/NotionPage.tsx create mode 100644 components/NotionPageHeader.tsx create mode 100644 components/Page404.tsx create mode 100644 components/PageActions.tsx create mode 100644 components/PageAside.tsx create mode 100644 components/PageHead.tsx create mode 100644 components/PageSocial.module.css create mode 100644 components/PageSocial.tsx create mode 100644 components/styles.module.css create mode 100644 contributing.md delete mode 100644 jest.config.ts delete mode 100644 jest.setup.ts create mode 100644 lib/acl.ts create mode 100644 lib/bootstrap-client.ts create mode 100644 lib/config.ts create mode 100644 lib/db.ts create mode 100644 lib/get-canonical-page-id.ts create mode 100644 lib/get-config-value.ts create mode 100644 lib/get-page-tweet.ts create mode 100644 lib/get-site-map.ts create mode 100644 lib/get-social-image-url.ts create mode 100644 lib/map-image-url.ts create mode 100644 lib/map-page-url.ts create mode 100644 lib/notion-api.ts create mode 100644 lib/oembed.ts create mode 100644 lib/preview-images.ts create mode 100644 lib/resolve-notion-page.ts create mode 100644 lib/site-config.ts create mode 100644 lib/types.ts create mode 100644 lib/use-dark-mode.ts create mode 100644 license create mode 100644 next.config.js create mode 100644 pages/404.tsx create mode 100644 pages/_error.tsx create mode 100644 pages/api/notion-page-info.tsx create mode 100644 pages/api/social-image.tsx create mode 100644 pages/feed.tsx create mode 100644 pages/robots.txt.tsx create mode 100644 pages/sitemap.xml.tsx create mode 100644 patches/notion-client+6.15.6.patch create mode 100644 public/404.png delete mode 100644 public/android-chrome-192x192.png delete mode 100644 public/android-chrome-512x512.png delete mode 100644 public/apple-touch-icon.png create mode 100644 public/error.png create mode 100644 public/favicon-128x128.png delete mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-192x192.png delete mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.png create mode 100644 public/fonts/Inter-Regular.ttf create mode 100644 public/fonts/Inter-SemiBold.ttf create mode 100644 public/manifest.json delete mode 100644 public/site.webmanifest create mode 100644 readme.md create mode 100644 site.config.ts create mode 100644 styles/global.css delete mode 100644 styles/globals.css create mode 100644 styles/notion.css create mode 100644 styles/prism-theme.css delete mode 100644 types/index.ts diff --git a/.changeset/config.json b/.changeset/config.json index 6d2119a..91b6a95 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,5 +1,5 @@ { - "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], diff --git a/.editorconfig b/.editorconfig index 62c29d8..94ce435 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,4 +4,4 @@ indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true -trim_trailing_whitespace = true \ No newline at end of file +trim_trailing_whitespace = true diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bca6ff5 --- /dev/null +++ b/.env.example @@ -0,0 +1,25 @@ +# ------------------------------------------------------------------------------ +# This is an example .env file. +# +# All of these environment vars must be defined either in your environment or in +# a local .env file in order to run this app. +# +# @see https://github.com/rolodato/dotenv-safe for more details. +# ------------------------------------------------------------------------------ + +# Optional (for fathom analytics) +#NEXT_PUBLIC_FATHOM_ID= + +# Optional (for PostHog analytics) +#NEXT_PUBLIC_POSTHOG_ID= + +# Optional (for rendering tweets more efficiently) +#TWITTER_ACCESS_TOKEN= + +# Optional (for persisting preview images to redis) +# NOTE: if you want to enable redis, only REDIS_HOST and REDIS_PASSWORD are required +# NOTE: don't forget to set isRedisEnabled to true in the site.config.ts file +#REDIS_HOST= +#REDIS_PASSWORD= +#REDIS_USER='default' +#REDIS_NAMESPACE='preview-images' diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 4769bd7..0000000 --- a/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -**/dist/** -**/storybook-static/** -**/coverage/** -**/build/** -**/.git/** -**/public/** -!.eslintrc.js -.next -node_modules -next-env.d.ts \ No newline at end of file diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index c2abc56..0000000 --- a/.eslintrc +++ /dev/null @@ -1,38 +0,0 @@ -{ - "root": true, - "parserOptions": { - "ecmaVersion": 2020, - "ecmaFeatures": { - "legacyDecorators": true, - "jsx": true - } - }, - "settings": { - "react": { - "version": "detect" - } - }, - "ignorePatterns": ["**/*.js", "**/*.jsx"], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "extends": [ - "plugin:@typescript-eslint/recommended", - "standard", - "standard-react", - "plugin:prettier/recommended" - ], - "env": { "browser": true, "node": true }, - "rules": { - "@typescript-eslint/no-explicit-any": 0, - "@typescript-eslint/explicit-module-boundary-types": 0, - "no-use-before-define": 0, - "@typescript-eslint/no-use-before-define": 0, - "space-before-function-paren": 0, - "react/prop-types": 0, - "react/jsx-handler-names": 0, - "react/jsx-fragments": 0, - "react/no-unused-prop-types": 0, - "import/export": 0, - "standard/no-callback-literal": 0 - } -} \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..545d039 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "react", "react-hooks"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "prettier" + ], + "settings": { + "react": { + "version": "detect" + } + }, + "env": { + "browser": true, + "node": true + }, + "rules": { + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-non-null-assertion": 0, + "@typescript-eslint/no-unused-vars": 2, + "react/prop-types": 0 + } +} diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..bfa4bf5 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,15 @@ +#### Description + + + +#### Notion Test Page ID + + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..1db1ff3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +#### Description + + + +#### Notion Test Page ID + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c45ca85 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,22 @@ +name: Build + +on: [push, pull_request] + +jobs: + Build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 20 + cache: pnpm + + - run: pnpm install --frozen-lockfile + - name: build + # TODO Enable those lines below if you use a Redis cache, you'll also need to configure GitHub Repository Secrets + # env: + # REDIS_HOST: ${{ secrets.REDIS_HOST }} + # REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }} + run: pnpm build diff --git a/.gitignore b/.gitignore index 527db31..d8b6db9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ .DS_Store *.pem +# ide +.idea + # debug npm-debug.log* yarn-debug.log* @@ -33,4 +36,4 @@ yarn-error.log* .env.production.local # vercel -.vercel \ No newline at end of file +.vercel diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index b162e0c..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ ->=20 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 0e57050..18d2304 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,7 @@ build/ dist/ node_modules/ .next/ -.vercel/ \ No newline at end of file +.vercel/ + +.demo/ +.renderer/ diff --git a/.prettierrc b/.prettierrc index c7ecda4..26d4848 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,20 @@ { "singleQuote": true, - "trailingComma": "es5", - "printWidth": 100, - "tabWidth": 2, + "jsxSingleQuote": true, + "semi": false, "useTabs": false, - "semi": true + "tabWidth": 2, + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "trailingComma": "none", + "importOrder": [ + "^(react/(.*)$)|^(react$)|^(next/(.*)$)|^(next$)", + "", + "^(@/lib/(.*)$)|^(@/components/(.*)$)|^(@/styles/(.*)$)", + "^[./]" + ], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "importOrderGroupNamespaceSpecifiers": true } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..62b7917 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "next dev", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/next", + "runtimeArgs": ["dev"], + "cwd": "${workspaceFolder}", + "port": 9229, + "smartStep": true, + "console": "integratedTerminal", + "skipFiles": ["/**"], + "env": { + "NODE_OPTIONS": "--inspect" + } + }, + { + "type": "node", + "request": "attach", + "name": "Next.js App", + "skipFiles": ["/**"], + "port": 9229 + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 124dc6d..218eea6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,37 @@ { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "typescript.tsdk": "./node_modules/typescript/lib", + "files.exclude": { + "**/logs": true, + "**/*.log": true, + "**/npm-debug.log*": true, + "**/yarn-debug.log*": true, + "**/yarn-error.log*": true, + "**/pids": true, + "**/*.pid": true, + "**/*.seed": true, + "**/*.pid.lock": true, + "**/.dummy": true, + "**/lib-cov": true, + "**/coverage": true, + "**/.nyc_output": true, + "**/.grunt": true, + "**/.snapshots/": true, + "**/bower_components": true, + "**/.lock-wscript": true, + "build/Release": true, + "**/node_modules/": true, + "**/jspm_packages/": true, + "**/typings/": true, + "**/.npm": true, + "**/.eslintcache": true, + "**/.node_repl_history": true, + "**/*.tgz": true, + "**/.yarn-integrity": true, + "**/.next/": true, + "**/dist/": true, + "**/build/": true, + "**/.now/": true, + "**/.vercel/": true, + "**/.google.json": true } } diff --git a/CHANGELOG.md b/CHANGELOG.md index e47785f..f4c7e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # ctbw.dev -## 0.2.3 +## 1.0.0 -### Patch Changes +### Major Changes -- added .nvmrc, switch from yarn to pnpm, update packages +- Forked project template from travis fischer. Gets blog up and running. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1625c17..0000000 --- a/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 8375e79..0000000 --- a/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Hello, my name is Charlie. Welcome to my source code. - -## This code is what powers [ctbw.dev](https://ctbw.dev), my blog about coding, tech in general, climbing, gaming, and other random stuff. - -## I wrote this code with the following tools/patterns: - -- [_ideology_ | **bulletproof react**](https://github.com/alan2207/bulletproof-react) -- [_cms_ | **notion**](https://www.notion.so/) - - [react-notion-x](https://github.com/NotionX/react-notion-x) -- [_framework_ | **next.js**](https://nextjs.org/) -- [ci/_cd_ | **vercel**](https://vercel.com/) - -## If you have any critiques, suggestions, or comments about this code please feel free to submit a PR here, or contact me through one of these methods: - -- [email](mailto:ctbwilhite@gmail.com) -- [linkedin](https://www.linkedin.com/in/charliewilhite/) diff --git a/__mocks__/fileMock.ts b/__mocks__/fileMock.ts deleted file mode 100644 index 035a92a..0000000 --- a/__mocks__/fileMock.ts +++ /dev/null @@ -1,3 +0,0 @@ -export {}; - -module.exports = 'test-file-stub'; diff --git a/__mocks__/styleMock.ts b/__mocks__/styleMock.ts deleted file mode 100644 index 3c225f1..0000000 --- a/__mocks__/styleMock.ts +++ /dev/null @@ -1,3 +0,0 @@ -export {}; - -module.exports = {}; diff --git a/__tests__/index.test.js b/__tests__/index.test.js deleted file mode 100644 index 05faf12..0000000 --- a/__tests__/index.test.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @jest-environment jsdom - */ - - import React from 'react' - import { render, screen } from '@testing-library/react' - import Home from '../pages/index' - - describe('Home', () => { - it('renders notion', () => { - render() - - const heading = screen.getByRole('span', { - name: /Hello, I'm Charlie\. Nice to meet you! I'm a software engineer from Austin, TX\./i, - }) - - expect(heading).toBeInTheDocument() - }) - }) diff --git a/components/ErrorPage.tsx b/components/ErrorPage.tsx new file mode 100644 index 0000000..43720d4 --- /dev/null +++ b/components/ErrorPage.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' + +import { PageHead } from './PageHead' +import styles from './styles.module.css' + +export const ErrorPage: React.FC<{ statusCode: number }> = ({ statusCode }) => { + const title = 'Error' + + return ( + <> + + +
+
+

Error Loading Page

+ + {statusCode &&

Error code: {statusCode}

} + + Error +
+
+ + ) +} diff --git a/components/Footer.tsx b/components/Footer.tsx new file mode 100644 index 0000000..32d5e34 --- /dev/null +++ b/components/Footer.tsx @@ -0,0 +1,145 @@ +import * as React from 'react' + +import { FaEnvelopeOpenText } from '@react-icons/all-files/fa/FaEnvelopeOpenText' +import { FaGithub } from '@react-icons/all-files/fa/FaGithub' +import { FaLinkedin } from '@react-icons/all-files/fa/FaLinkedin' +import { FaMastodon } from '@react-icons/all-files/fa/FaMastodon' +import { FaTwitter } from '@react-icons/all-files/fa/FaTwitter' +import { FaYoutube } from '@react-icons/all-files/fa/FaYoutube' +import { FaZhihu } from '@react-icons/all-files/fa/FaZhihu' +import { IoMoonSharp } from '@react-icons/all-files/io5/IoMoonSharp' +import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline' + +import * as config from '@/lib/config' +import { useDarkMode } from '@/lib/use-dark-mode' + +import styles from './styles.module.css' + +// TODO: merge the data and icons from PageSocial with the social links in Footer + +export const FooterImpl: React.FC = () => { + const [hasMounted, setHasMounted] = React.useState(false) + const { isDarkMode, toggleDarkMode } = useDarkMode() + const currentYear = new Date().getFullYear() + + const onToggleDarkMode = React.useCallback( + (e) => { + e.preventDefault() + toggleDarkMode() + }, + [toggleDarkMode] + ) + + React.useEffect(() => { + setHasMounted(true) + }, []) + + return ( +
+
+ Copyright {currentYear} {config.author} +
+ + {/*
+ {hasMounted && ( + + {isDarkMode ? : } + + )} +
*/} + +
+ {config.twitter && ( + + + + )} + + {config.mastodon && ( + + + + )} + + {config.zhihu && ( + + + + )} + + {config.github && ( + + + + )} + + {config.linkedin && ( + + + + )} + + {config.newsletter && ( + + + + )} + + {config.youtube && ( + + + + )} +
+
+ ) +} + +export const Footer = React.memo(FooterImpl) diff --git a/components/Loading.tsx b/components/Loading.tsx new file mode 100644 index 0000000..bdfaac7 --- /dev/null +++ b/components/Loading.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' + +import { LoadingIcon } from './LoadingIcon' +import styles from './styles.module.css' + +export const Loading: React.FC = () => ( +
+ +
+) diff --git a/components/LoadingIcon.tsx b/components/LoadingIcon.tsx new file mode 100644 index 0000000..9249fea --- /dev/null +++ b/components/LoadingIcon.tsx @@ -0,0 +1,63 @@ +import * as React from 'react' + +import cs from 'classnames' + +import styles from './styles.module.css' + +export const LoadingIcon = (props) => { + const { className, ...rest } = props + return ( + + + + + + + + + + + + + + + + + + + ) +} diff --git a/components/NotionPage.tsx b/components/NotionPage.tsx new file mode 100644 index 0000000..b3d4512 --- /dev/null +++ b/components/NotionPage.tsx @@ -0,0 +1,284 @@ +import * as React from 'react' +import dynamic from 'next/dynamic' +import Image from 'next/image' +import Link from 'next/link' +import { useRouter } from 'next/router' + +import cs from 'classnames' +import { PageBlock } from 'notion-types' +import { formatDate, getBlockTitle, getPageProperty } from 'notion-utils' +import BodyClassName from 'react-body-classname' +import { NotionRenderer } from 'react-notion-x' +import TweetEmbed from 'react-tweet-embed' +import { useSearchParam } from 'react-use' + +import * as config from '@/lib/config' +import * as types from '@/lib/types' +import { mapImageUrl } from '@/lib/map-image-url' +import { getCanonicalPageUrl, mapPageUrl } from '@/lib/map-page-url' +import { searchNotion } from '@/lib/search-notion' +import { useDarkMode } from '@/lib/use-dark-mode' + +import { Footer } from './Footer' +import { Loading } from './Loading' +import { NotionPageHeader } from './NotionPageHeader' +import { Page404 } from './Page404' +import { PageAside } from './PageAside' +import { PageHead } from './PageHead' +import styles from './styles.module.css' + +// ----------------------------------------------------------------------------- +// dynamic imports for optional components +// ----------------------------------------------------------------------------- + +const Code = dynamic(() => + import('react-notion-x/build/third-party/code').then(async (m) => { + // add / remove any prism syntaxes here + await Promise.allSettled([ + import('prismjs/components/prism-markup-templating.js'), + import('prismjs/components/prism-markup.js'), + import('prismjs/components/prism-bash.js'), + import('prismjs/components/prism-c.js'), + import('prismjs/components/prism-cpp.js'), + import('prismjs/components/prism-csharp.js'), + import('prismjs/components/prism-docker.js'), + import('prismjs/components/prism-java.js'), + import('prismjs/components/prism-js-templates.js'), + import('prismjs/components/prism-coffeescript.js'), + import('prismjs/components/prism-diff.js'), + import('prismjs/components/prism-git.js'), + import('prismjs/components/prism-go.js'), + import('prismjs/components/prism-graphql.js'), + import('prismjs/components/prism-handlebars.js'), + import('prismjs/components/prism-less.js'), + import('prismjs/components/prism-makefile.js'), + import('prismjs/components/prism-markdown.js'), + import('prismjs/components/prism-objectivec.js'), + import('prismjs/components/prism-ocaml.js'), + import('prismjs/components/prism-python.js'), + import('prismjs/components/prism-reason.js'), + import('prismjs/components/prism-rust.js'), + import('prismjs/components/prism-sass.js'), + import('prismjs/components/prism-scss.js'), + import('prismjs/components/prism-solidity.js'), + import('prismjs/components/prism-sql.js'), + import('prismjs/components/prism-stylus.js'), + import('prismjs/components/prism-swift.js'), + import('prismjs/components/prism-wasm.js'), + import('prismjs/components/prism-yaml.js') + ]) + return m.Code + }) +) + +const Collection = dynamic(() => + import('react-notion-x/build/third-party/collection').then( + (m) => m.Collection + ) +) +const Equation = dynamic(() => + import('react-notion-x/build/third-party/equation').then((m) => m.Equation) +) +const Pdf = dynamic( + () => import('react-notion-x/build/third-party/pdf').then((m) => m.Pdf), + { + ssr: false + } +) +const Modal = dynamic( + () => + import('react-notion-x/build/third-party/modal').then((m) => { + m.Modal.setAppElement('.notion-viewport') + return m.Modal + }), + { + ssr: false + } +) + +const Tweet = ({ id }: { id: string }) => { + return +} + +const propertyLastEditedTimeValue = ( + { block, pageHeader }, + defaultFn: () => React.ReactNode +) => { + if (pageHeader && block?.last_edited_time) { + return `Last updated ${formatDate(block?.last_edited_time, { + month: 'long' + })}` + } + + return defaultFn() +} + +const propertyDateValue = ( + { data, schema, pageHeader }, + defaultFn: () => React.ReactNode +) => { + if (pageHeader && schema?.name?.toLowerCase() === 'published') { + const publishDate = data?.[0]?.[1]?.[0]?.[1]?.start_date + + if (publishDate) { + return `${formatDate(publishDate, { + month: 'long' + })}` + } + } + + return defaultFn() +} + +const propertyTextValue = ( + { schema, pageHeader }, + defaultFn: () => React.ReactNode +) => { + if (pageHeader && schema?.name?.toLowerCase() === 'author') { + return {defaultFn()} + } + + return defaultFn() +} + +export const NotionPage: React.FC = ({ + site, + recordMap, + error, + pageId +}) => { + const router = useRouter() + const lite = useSearchParam('lite') + + const components = React.useMemo( + () => ({ + nextImage: Image, + nextLink: Link, + Code, + Collection, + Equation, + Pdf, + Modal, + Tweet, + Header: NotionPageHeader, + propertyLastEditedTimeValue, + propertyTextValue, + propertyDateValue + }), + [] + ) + + // lite mode is for oembed + const isLiteMode = lite === 'true' + + const { isDarkMode } = useDarkMode() + + const siteMapPageUrl = React.useMemo(() => { + const params: any = {} + if (lite) params.lite = lite + + const searchParams = new URLSearchParams(params) + return mapPageUrl(site, recordMap, searchParams) + }, [site, recordMap, lite]) + + const keys = Object.keys(recordMap?.block || {}) + const block = recordMap?.block?.[keys[0]]?.value + + // const isRootPage = + // parsePageId(block?.id) === parsePageId(site?.rootNotionPageId) + const isBlogPost = + block?.type === 'page' && block?.parent_table === 'collection' + + const showTableOfContents = !!isBlogPost + const minTableOfContentsItems = 3 + + const pageAside = React.useMemo( + () => ( + + ), + [block, recordMap, isBlogPost] + ) + + const footer = React.useMemo(() =>