From 81bde603a6db878299d01c0f0907241ac3e959a0 Mon Sep 17 00:00:00 2001 From: joker Date: Sat, 9 Sep 2023 23:08:20 +0300 Subject: [PATCH] new file: .eslintrc.json new file: .github/workflows/ci.yml new file: .gitignore new file: .prettierignore new file: .storybook/main.ts new file: .storybook/preview.tsx new file: LICENSE.md new file: README.md new file: app/[...slug]/not-found.tsx new file: app/[...slug]/page.tsx new file: app/layout.tsx new file: app/loading.tsx new file: app/og/route.tsx new file: app/page.tsx new file: app/posts/[...slug]/not-found.tsx new file: app/posts/[...slug]/page.tsx new file: app/posts/page.tsx new file: components/analytics.tsx new file: components/blog-title.tsx new file: components/button.tsx new file: components/callout.tsx new file: components/comments.tsx --- .eslintrc.json | 10 + .github/workflows/ci.yml | 43 + .gitignore | 38 + .prettierignore | 38 + .storybook/main.ts | 17 + .storybook/preview.tsx | 66 + LICENSE.md | 21 + README.md | 14 + app/[...slug]/not-found.tsx | 15 + app/[...slug]/page.tsx | 64 + app/layout.tsx | 117 + app/loading.tsx | 9 + app/og/route.tsx | 122 + app/page.tsx | 55 + app/posts/[...slug]/not-found.tsx | 22 + app/posts/[...slug]/page.tsx | 76 + app/posts/page.tsx | 42 + components/analytics.tsx | 7 + components/blog-title.tsx | 45 + components/button.tsx | 27 + components/callout.tsx | 63 + components/code-block.tsx | 83 + components/comments.tsx | 27 + components/font-style-provider.tsx | 18 + components/footer.tsx | 153 + components/header.tsx | 48 + components/hero-section.tsx | 123 + components/mdx-components.tsx | 86 + components/mdx-content.tsx | 20 + components/mdx-styles.tsx | 29 + components/navigation-bar.tsx | 39 + components/page-controls.tsx | 88 + components/post-card.tsx | 73 + components/post-intro.tsx | 65 + components/post-paginator.tsx | 40 + components/post-tags.tsx | 25 + components/search-input.tsx | 69 + components/search-results.tsx | 76 + components/search-tags.tsx | 42 + components/search.tsx | 84 + components/stories/blog-title.stories.tsx | 25 + components/stories/button.stories.tsx | 30 + components/stories/callout.stories.tsx | 69 + components/stories/code-block.stories.tsx | 426 + components/stories/decorators/center.tsx | 7 + components/stories/decorators/index.ts | 3 + components/stories/decorators/markdown.tsx | 11 + components/stories/decorators/padding.tsx | 7 + components/stories/footer.stories.tsx | 22 + components/stories/header.stories.tsx | 43 + components/stories/hero-section.stories.tsx | 15 + components/stories/post-card.stories.tsx | 40 + components/stories/post-intro.stories.tsx | 21 + components/stories/post-paginator.stories.tsx | 69 + components/stories/post-tags.stories.tsx | 28 + components/stories/search.stories.tsx | 35 + .../stories/table-of-contents.stories.tsx | 39 + components/stories/tooltip.stories.tsx | 42 + components/table-of-contents.tsx | 37 + components/toolbar.tsx | 139 + components/tooltip.tsx | 20 + config/index.js | 67 + config/types.ts | 135 + content/pages/about.mdx | 26 + .../olimp/practice-test-10-11-robotics.mdx | 651 + .../olimp/practice-test-5-6-robotics.mdx | 504 + contentlayer.config.ts | 170 + lib/datetime.test.ts | 59 + lib/datetime.ts | 107 + lib/search.test.ts | 36 + lib/search.ts | 67 + lib/utils.ts | 7 + next-sitemap.config.js | 7 + next.config.js | 10 + package-lock.json | 23542 ++++++++++++++++ package.json | 86 + pnpm-lock.yaml | 12536 ++++++++ postcss.config.js | 6 + prettier.config.js | 38 + public/android-chrome-192x192.png | Bin 0 -> 1155 bytes public/android-chrome-512x512.png | Bin 0 -> 2231 bytes public/apple-touch-icon.png | Bin 0 -> 920 bytes public/assets/RedHatDisplay-Bold.ttf | Bin 0 -> 47320 bytes public/assets/RedHatDisplay-Regular.ttf | Bin 0 -> 47356 bytes public/assets/RedHatDisplay-SemiBold.ttf | Bin 0 -> 47396 bytes public/favicon-16x16.png | Bin 0 -> 319 bytes public/favicon-32x32.png | Bin 0 -> 429 bytes public/favicon.ico | Bin 0 -> 15086 bytes .../lane-detection/first-frame.webp | Bin 0 -> 179452 bytes .../frame-with-lane-markings.webp | Bin 0 -> 151880 bytes .../lane-detection/lines-cropped.webp | Bin 0 -> 49308 bytes .../lane-detection/lines-too-long.webp | Bin 0 -> 153164 bytes .../lane-detection/polar-coordinates.webp | Bin 0 -> 54898 bytes .../lane-detection/polar-to-cartesian.webp | Bin 0 -> 13196 bytes .../lane-detection/preprocessing-steps.webp | Bin 0 -> 257934 bytes .../lane-detection/trapezoid-mask.webp | Bin 0 -> 50680 bytes public/images/rust/fizzbuzz/extended.webp | Bin 0 -> 11386 bytes .../web-dev/mdx-nextjs-13/frontmatter.webp | Bin 0 -> 8868 bytes public/images/web-dev/mdx-nextjs-13/meme.webp | Bin 0 -> 72582 bytes .../images/web-dev/mdx-nextjs-13/result.webp | Bin 0 -> 23004 bytes .../parallax-framer-motion/firewatch.gif | Bin 0 -> 682522 bytes .../parallax-result.gif | Bin 0 -> 225986 bytes .../parallax-step-1.webp | Bin 0 -> 12790 bytes public/robots.txt | 9 + public/site.webmanifest | 18 + public/sitemap-0.xml | 8 + public/sitemap.xml | 4 + stores/search-store.ts | 18 + stores/theme-store.ts | 54 + styles/globals.css | 15 + styles/markdown.css | 145 + tailwind.config.js | 88 + tsconfig.json | 37 + 113 files changed, 41677 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .storybook/main.ts create mode 100644 .storybook/preview.tsx create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 app/[...slug]/not-found.tsx create mode 100644 app/[...slug]/page.tsx create mode 100644 app/layout.tsx create mode 100644 app/loading.tsx create mode 100644 app/og/route.tsx create mode 100644 app/page.tsx create mode 100644 app/posts/[...slug]/not-found.tsx create mode 100644 app/posts/[...slug]/page.tsx create mode 100644 app/posts/page.tsx create mode 100644 components/analytics.tsx create mode 100644 components/blog-title.tsx create mode 100644 components/button.tsx create mode 100644 components/callout.tsx create mode 100644 components/code-block.tsx create mode 100644 components/comments.tsx create mode 100644 components/font-style-provider.tsx create mode 100644 components/footer.tsx create mode 100644 components/header.tsx create mode 100644 components/hero-section.tsx create mode 100644 components/mdx-components.tsx create mode 100644 components/mdx-content.tsx create mode 100644 components/mdx-styles.tsx create mode 100644 components/navigation-bar.tsx create mode 100644 components/page-controls.tsx create mode 100644 components/post-card.tsx create mode 100644 components/post-intro.tsx create mode 100644 components/post-paginator.tsx create mode 100644 components/post-tags.tsx create mode 100644 components/search-input.tsx create mode 100644 components/search-results.tsx create mode 100644 components/search-tags.tsx create mode 100644 components/search.tsx create mode 100644 components/stories/blog-title.stories.tsx create mode 100644 components/stories/button.stories.tsx create mode 100644 components/stories/callout.stories.tsx create mode 100644 components/stories/code-block.stories.tsx create mode 100644 components/stories/decorators/center.tsx create mode 100644 components/stories/decorators/index.ts create mode 100644 components/stories/decorators/markdown.tsx create mode 100644 components/stories/decorators/padding.tsx create mode 100644 components/stories/footer.stories.tsx create mode 100644 components/stories/header.stories.tsx create mode 100644 components/stories/hero-section.stories.tsx create mode 100644 components/stories/post-card.stories.tsx create mode 100644 components/stories/post-intro.stories.tsx create mode 100644 components/stories/post-paginator.stories.tsx create mode 100644 components/stories/post-tags.stories.tsx create mode 100644 components/stories/search.stories.tsx create mode 100644 components/stories/table-of-contents.stories.tsx create mode 100644 components/stories/tooltip.stories.tsx create mode 100644 components/table-of-contents.tsx create mode 100644 components/toolbar.tsx create mode 100644 components/tooltip.tsx create mode 100644 config/index.js create mode 100644 config/types.ts create mode 100644 content/pages/about.mdx create mode 100644 content/posts/olimp/practice-test-10-11-robotics.mdx create mode 100644 content/posts/olimp/practice-test-5-6-robotics.mdx create mode 100644 contentlayer.config.ts create mode 100644 lib/datetime.test.ts create mode 100644 lib/datetime.ts create mode 100644 lib/search.test.ts create mode 100644 lib/search.ts create mode 100644 lib/utils.ts create mode 100644 next-sitemap.config.js create mode 100644 next.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.js create mode 100644 prettier.config.js create mode 100644 public/android-chrome-192x192.png create mode 100644 public/android-chrome-512x512.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/assets/RedHatDisplay-Bold.ttf create mode 100644 public/assets/RedHatDisplay-Regular.ttf create mode 100644 public/assets/RedHatDisplay-SemiBold.ttf create mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.ico create mode 100644 public/images/computer-vision/lane-detection/first-frame.webp create mode 100644 public/images/computer-vision/lane-detection/frame-with-lane-markings.webp create mode 100644 public/images/computer-vision/lane-detection/lines-cropped.webp create mode 100644 public/images/computer-vision/lane-detection/lines-too-long.webp create mode 100644 public/images/computer-vision/lane-detection/polar-coordinates.webp create mode 100644 public/images/computer-vision/lane-detection/polar-to-cartesian.webp create mode 100644 public/images/computer-vision/lane-detection/preprocessing-steps.webp create mode 100644 public/images/computer-vision/lane-detection/trapezoid-mask.webp create mode 100644 public/images/rust/fizzbuzz/extended.webp create mode 100644 public/images/web-dev/mdx-nextjs-13/frontmatter.webp create mode 100644 public/images/web-dev/mdx-nextjs-13/meme.webp create mode 100644 public/images/web-dev/mdx-nextjs-13/result.webp create mode 100644 public/images/web-dev/parallax-framer-motion/firewatch.gif create mode 100644 public/images/web-dev/parallax-framer-motion/parallax-result.gif create mode 100644 public/images/web-dev/parallax-framer-motion/parallax-step-1.webp create mode 100644 public/robots.txt create mode 100644 public/site.webmanifest create mode 100644 public/sitemap-0.xml create mode 100644 public/sitemap.xml create mode 100644 stores/search-store.ts create mode 100644 stores/theme-store.ts create mode 100644 styles/globals.css create mode 100644 styles/markdown.css create mode 100644 tailwind.config.js create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..58494f0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["next/core-web-vitals", "plugin:storybook/recommended"], + "plugins": ["eslint-plugin-tailwindcss"], + "rules": { + "tailwindcss/classnames-order": ["error", { "callees": ["cn"] }], + "tailwindcss/no-custom-classname": ["error", { "callees": ["cn"] }], + "tailwindcss/no-contradicting-classname": ["error", { "callees": ["cn"] }], + "tailwindcss/enforces-shorthand": ["error", { "callees": ["cn"] }] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4da0e88 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: 'CI' + +on: + pull_request: + push: + branches: ['prod'] + workflow_dispatch: + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-latest'] + node-version: ['16.x'] + steps: + - name: 'Checkout repository' + uses: actions/checkout@v3 + + - name: 'Use Node.js v${{ matrix.node-version }}' + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: 'Install pnpm' + uses: pnpm/action-setup@v2 + with: + version: 6.0.2 + + - name: 'Install Dependencies' + run: pnpm install + + - name: 'Build Contentlayer' + run: NODE_ENV=production npx contentlayer build + + - name: 'Run Type Checks' + run: npx tsc --noEmit + + - name: 'Run Lint and Format Checks' + run: pnpm run style:all + + - name: 'Run Tests' + run: pnpm run test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bde1d80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# 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 +.vscode +.contentlayer + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..bde1d80 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,38 @@ +# 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 +.vscode +.contentlayer + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..86946b9 --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,17 @@ +import { type StorybookConfig } from '@storybook/nextjs'; + +const config: StorybookConfig = { + stories: ['../components/stories/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + 'storybook-tailwind-dark-mode', + ], + framework: { + name: '@storybook/nextjs', + options: {}, + }, +}; + +export default config; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000..258097c --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,66 @@ +import '@/styles/globals.css'; +import 'react-tooltip/dist/react-tooltip.css'; +import { Red_Hat_Display } from 'next/font/google'; +import type { Decorator, Parameters } from '@storybook/react'; +import type { GlobalTypes } from '@storybook/types'; + +import { cn } from '@/lib/utils'; + +const fontSans = Red_Hat_Display({ + subsets: ['latin'], + variable: '--font-red-hat', +}); + +export const parameters: Parameters = { + nextjs: { appDirectory: true }, + layout: 'fullscreen', + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; + +export const globalTypes: GlobalTypes = { + darkMode: { + type: 'boolean', + defaultValue: false, + }, +}; + +export const decorators: Decorator[] = [ + (Story) => ( +
+
+
+ +
+ + ), +]; diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5c64afe --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Kfir Fitousi + +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/README.md b/README.md new file mode 100644 index 0000000..455719a --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +[![‹kfir/blog›](https://user-images.githubusercontent.com/37262772/213866538-70e7024e-64f5-4bd8-ba92-af803e6169e7.png)](https://blog.kfirfitousi.com) +![Build Status](https://img.shields.io/github/deployments/kfirfitousi/blog/Production%20%E2%80%93%20blog?label=build&logo=vercel&style=for-the-badge) +![Website Status](https://img.shields.io/website?down_color=lightgrey&logo=vercel&style=for-the-badge&url=https%3A%2F%2Fblog.kfirfitousi.com) +![CI Status](https://img.shields.io/github/actions/workflow/status/kfirfitousi/blog/ci.yml?branch=main&label=CI&logo=github&style=for-the-badge) +![License](https://img.shields.io/github/license/kfirfitousi/blog?color=blue&style=for-the-badge) + +Blog built with Next.js 13, TypeScript, Contentlayer, Zustand and TailwindCSS. + +- 🔥 Using latest Next.js 13 features including the `/app` directory, SEO & Metadata, `next/font`, and React 18's Server Components +- 🎛 Customizable reading experience - light/dark mode, serif/sans-serif, and font size +- 🧩 MDX plugins and custom components +- 💬 Comment sections using [Giscus](https://giscus.app/) +- ⚡️ Vercel OG image generation at the Edge +- 📖 Storybook v7 integration (published to [story.blog.kfirfitousi.com](https://story.blog.kfirfitousi.com)) diff --git a/app/[...slug]/not-found.tsx b/app/[...slug]/not-found.tsx new file mode 100644 index 0000000..b5f63cb --- /dev/null +++ b/app/[...slug]/not-found.tsx @@ -0,0 +1,15 @@ +import { Home, XCircle } from 'lucide-react'; + +import { Button } from '@/components/button'; + +export default function NotFound() { + return ( +
+ +

+ Page not found +

+
+ ); +} diff --git a/app/[...slug]/page.tsx b/app/[...slug]/page.tsx new file mode 100644 index 0000000..74702dc --- /dev/null +++ b/app/[...slug]/page.tsx @@ -0,0 +1,64 @@ +import { notFound } from 'next/navigation'; +import { type Metadata } from 'next/types'; +import { allPages } from 'contentlayer/generated'; + +import { blogConfig } from '@/config'; +import { MDXContent } from '@/components/mdx-content'; + +type PageProps = { + params: { + slug: string[]; + }; +}; + +export async function generateStaticParams(): Promise { + return allPages.map(({ slug }) => ({ + slug: slug.split('/'), + })); +} + +export function generateMetadata({ params }: PageProps): Metadata { + const { title, description, url } = allPages.find( + ({ slug }) => slug === params.slug.join('/'), + ) || { + title: 'Page Not Found', + description: 'Page Not Found', + url: '/', + }; + + const ogImage = { + url: `${blogConfig.url}/og?title=${title}`, + }; + + return { + title, + description, + openGraph: { + type: 'website', + url: `${blogConfig.url}${url}`, + title, + description, + images: [ogImage], + }, + twitter: { + title, + description, + images: ogImage, + card: 'summary_large_image', + }, + }; +} + +export default async function Page({ params }: PageProps) { + const page = allPages.find(({ slug }) => slug === params.slug.join('/')); + + if (!page) { + notFound(); + } + + return ( +
+ +
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..c21f59c --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,117 @@ +import '@/styles/globals.css'; +import 'react-tooltip/dist/react-tooltip.css'; +import { Newsreader, Red_Hat_Display } from 'next/font/google'; +import { type Metadata } from 'next/types'; +import { allPosts } from 'contentlayer/generated'; + +import { blogConfig } from '@/config'; +import { Analytics } from '@/components/analytics'; +import { FontStyleProvider } from '@/components/font-style-provider'; +import { Footer } from '@/components/footer'; +import { Header } from '@/components/header'; +import { Search } from '@/components/search'; +import { cn } from '@/lib/utils'; + +const fontSans = Red_Hat_Display({ + subsets: ['latin'], + variable: '--font-red-hat', +}); + +const fontSerif = Newsreader({ + subsets: ['latin'], + variable: '--font-newsreader', +}); + +type RootLayoutProps = { + children: React.ReactNode; +}; + +export const metadata: Metadata = { + title: { + default: blogConfig.title, + template: `${blogConfig.title} | %s`, + }, + openGraph: { + title: { + default: blogConfig.title, + template: `${blogConfig.title} | %s`, + }, + }, + twitter: { + title: { + default: blogConfig.title, + template: `${blogConfig.title} | %s`, + }, + }, + robots: { + index: true, + follow: true, + }, + icons: [ + { + rel: 'apple-touch-icon', + sizes: '180x180', + url: '/apple-touch-icon.png', + }, + { + rel: 'icon', + type: 'image/png', + sizes: '32x32', + url: '/favicon-32x32.png', + }, + { + rel: 'icon', + type: 'image/png', + sizes: '16x16', + url: '/favicon-16x16.png', + }, + ], +}; + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + + + + +
+
+
+
+ {children} +
+
+
+
+