commit
						3c7fed5712
					
				| @ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |   "extends": "next/core-web-vitals" | ||||||
|  | } | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | # 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* | ||||||
|  | .pnpm-debug.log* | ||||||
|  | 
 | ||||||
|  | # local env files | ||||||
|  | .env*.local | ||||||
|  | 
 | ||||||
|  | # vercel | ||||||
|  | .vercel | ||||||
|  | 
 | ||||||
|  | # typescript | ||||||
|  | *.tsbuildinfo | ||||||
|  | next-env.d.ts | ||||||
| @ -0,0 +1,34 @@ | |||||||
|  | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). | ||||||
|  | 
 | ||||||
|  | ## Getting Started | ||||||
|  | 
 | ||||||
|  | First, run the development server: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | npm run dev | ||||||
|  | # or | ||||||
|  | yarn dev | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||||||
|  | 
 | ||||||
|  | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. | ||||||
|  | 
 | ||||||
|  | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. | ||||||
|  | 
 | ||||||
|  | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. | ||||||
|  | 
 | ||||||
|  | ## Learn More | ||||||
|  | 
 | ||||||
|  | To learn more about Next.js, take a look at the following resources: | ||||||
|  | 
 | ||||||
|  | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | ||||||
|  | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||||||
|  | 
 | ||||||
|  | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! | ||||||
|  | 
 | ||||||
|  | ## Deploy on Vercel | ||||||
|  | 
 | ||||||
|  | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | ||||||
|  | 
 | ||||||
|  | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. | ||||||
| @ -0,0 +1,45 @@ | |||||||
|  | import NextHead from 'next/head'; | ||||||
|  | import { useRouter } from 'next/router'; | ||||||
|  | import React from 'react'; | ||||||
|  | import { MetaProps } from '../types/layout'; | ||||||
|  | 
 | ||||||
|  | export type WithYandexMetrikaProps = { | ||||||
|  |   children: React.ReactNode; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const WEBSITE_HOST_URL = 'https://robotop.krasnikov.pro'; | ||||||
|  | 
 | ||||||
|  | const Head = ({ customMeta }: { customMeta?: MetaProps }): JSX.Element => { | ||||||
|  |   const router = useRouter(); | ||||||
|  |   const meta: MetaProps = { | ||||||
|  |     title: 'РоботТоп - робототехнический фестиваль', | ||||||
|  |     description: | ||||||
|  |       'РоботТОП – это робототехнические соревнования, в которых могут принять участие молодые любители робототехники, объединившись в команды.', | ||||||
|  |     image: `${WEBSITE_HOST_URL}/images/site-preview.png`, | ||||||
|  |     type: 'website', | ||||||
|  |     ...customMeta, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <NextHead> | ||||||
|  |       <title>{meta.title}</title> | ||||||
|  |       <meta content={meta.description} name="РоботТОП – это робототехнические соревнования, в которых могут принять участие молодые любители робототехники, объединившись в команды." /> | ||||||
|  |       <meta property="og:url" content={`${WEBSITE_HOST_URL}${router.asPath}`} /> | ||||||
|  |       <link rel="canonical" href={`${WEBSITE_HOST_URL}${router.asPath}`} /> | ||||||
|  |       <meta property="og:type" content={meta.type} /> | ||||||
|  |       <meta property="og:site_name" content="РоботТоп - робототехнический фестиваль" /> | ||||||
|  |       <meta property="og:description" content={meta.description} /> | ||||||
|  |       <meta property="og:title" content={meta.title} /> | ||||||
|  |       <meta property="og:image" content={meta.image} /> | ||||||
|  |       <meta name="twitter:card" content="summary_large_image" /> | ||||||
|  |       <meta name="twitter:title" content={meta.title} /> | ||||||
|  |       <meta name="twitter:description" content={meta.description} /> | ||||||
|  |       <meta name="twitter:image" content={meta.image} /> | ||||||
|  |       {meta.date && ( | ||||||
|  |         <meta property="article:published_time" content={meta.date} /> | ||||||
|  |       )} | ||||||
|  |     </NextHead> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Head; | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { MetaProps } from '../types/layout'; | ||||||
|  | import Head from './Head'; | ||||||
|  | import Navigation from './Navigation'; | ||||||
|  | import ThemeSwitch from './ThemeSwitch'; | ||||||
|  | 
 | ||||||
|  | type LayoutProps = { | ||||||
|  |     children: React.ReactNode; | ||||||
|  |     customMeta?: MetaProps; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const WEBSITE_HOST_URL = 'https://   krasnikov.pro/'; | ||||||
|  | 
 | ||||||
|  | export const Layout = ({ children, customMeta }: LayoutProps): JSX.Element => { | ||||||
|  |     return ( | ||||||
|  |             <> | ||||||
|  |             <Head customMeta={customMeta} /> | ||||||
|  |             <header> | ||||||
|  |                 <div className="max-w-5xl px-8 mx-auto max-w"> | ||||||
|  |                     <div className="flex items-center justify-between py-6"> | ||||||
|  |                         <Navigation /> | ||||||
|  |                         <ThemeSwitch /> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </header> | ||||||
|  |             <main> | ||||||
|  |                 <div className="max-w-5xl px-8 py-4 mx-auto max-w"> | ||||||
|  |                     {children} | ||||||
|  |                 </div> | ||||||
|  |             </main> | ||||||
|  |             <footer className="py-8"> | ||||||
|  |                 <div className="max-w-5xl px-8 mx-auto max-w"> | ||||||
|  |                     Разработано {' '} | ||||||
|  |                     <a | ||||||
|  |                         className="text-gray-900 dark:text-white" | ||||||
|  |                         href="https://krasnikov.pro" target='_blank' rel="noreferrer" | ||||||
|  |                         > | ||||||
|  |                         Krasnikov.pro - {(new Date()).getFullYear()} год | ||||||
|  |                     </a> | ||||||
|  |                 </div> | ||||||
|  |             </footer> | ||||||
|  |             </> | ||||||
|  |             ); | ||||||
|  | }; | ||||||
| @ -0,0 +1,104 @@ | |||||||
|  | import Link from 'next/link'; | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { MenuIcon, XIcon } from '@heroicons/react/outline' | ||||||
|  | import { Transition } from "@headlessui/react"; | ||||||
|  | import { useRouter } from 'next/router' | ||||||
|  | 
 | ||||||
|  | const navigation = [ | ||||||
|  |   { name: 'Главная', href: '/', as: false }, | ||||||
|  |   { name: 'Робототехника', href: '/posts/[slug]', as:'regulations'}, | ||||||
|  |   { name: 'Программирование', href: '/registration', as: false } | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | function classNames(...classes: string[]) { | ||||||
|  |   return classes.filter(Boolean).join(' ') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const Navigation = (): JSX.Element => { | ||||||
|  |   const [isOpen, setIsOpen] = useState(false); | ||||||
|  |   const router = useRouter(); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <nav className=""> | ||||||
|  |       <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | ||||||
|  |         <div className="flex items-center justify-between h-16"> | ||||||
|  |           <div className="flex items-center"> | ||||||
|  |             <div className="hidden md:block"> | ||||||
|  |               <div className="ml-10 flex items-baseline space-x-4"> | ||||||
|  |                 {navigation.map((item) => ( | ||||||
|  |                   <Link as={ item.as ? '/posts/'+item.as : ''} href={item.href} key={item.name}> | ||||||
|  |                     <a | ||||||
|  |                       className={classNames( | ||||||
|  |                         item.as == router.query.slug && router.query.slug !== 'undefined'? | ||||||
|  |                             'bg-gray-900 text-white' : | ||||||
|  |                             item.href == router.pathname && item.as === false ? | ||||||
|  |                             'bg-gray-900 text-white' : | ||||||
|  |                             'text-gray-900 hover:bg-gray-700 hover:text-white', | ||||||
|  |                           'px-3 py-2 rounded-md text-sm font-medium' | ||||||
|  |                       )} | ||||||
|  |                         aria-current={item.href == router.pathname || item.as == router.query.slug ? 'page' : 'false'} | ||||||
|  |                     > | ||||||
|  |                       {item.name} | ||||||
|  |                     </a> | ||||||
|  |                   </Link> | ||||||
|  |                 ))} | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           <div className="-mr-2 flex md:hidden"> | ||||||
|  |             <button | ||||||
|  |               onClick={() => setIsOpen(!isOpen)} | ||||||
|  |               type="button" | ||||||
|  |               aria-controls="mobile-menu" | ||||||
|  |               aria-expanded="false" | ||||||
|  |             > | ||||||
|  |               <span className="sr-only">Открыть главное меню</span> | ||||||
|  |               {!isOpen ? ( | ||||||
|  |                 <MenuIcon className="block h-6 w-6" aria-hidden="false" /> | ||||||
|  |               ) : ( | ||||||
|  |                 <XIcon className="block h-6 w-6" aria-hidden="true" /> | ||||||
|  |               )} | ||||||
|  |             </button> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <Transition | ||||||
|  |         show={isOpen} | ||||||
|  |         enter="transition ease-out duration-100 transform" | ||||||
|  |         enterFrom="opacity-0 scale-95" | ||||||
|  |         enterTo="opacity-100 scale-100" | ||||||
|  |         leave="transition ease-in duration-75 transform" | ||||||
|  |         leaveFrom="opacity-100 scale-100" | ||||||
|  |         leaveTo="opacity-0 scale-95" | ||||||
|  |         className="absolute bg-gray-100 z-50" | ||||||
|  |       > | ||||||
|  |         {(ref) => ( | ||||||
|  |           <div className="md:hidden" id="mobile-menu"> | ||||||
|  |             <div ref={ref} className="px-2 pt-2 pb-3 space-y-1 sm:px-3"> | ||||||
|  |               {navigation.map((item) => ( | ||||||
|  |                 <Link as={'/posts/'+item.as}  href={item.href} key={item.name}> | ||||||
|  |                   <a | ||||||
|  |                     className={classNames( | ||||||
|  |                       item.as == router.query.slug && router.query.slug !== 'undefined'? | ||||||
|  |                         'bg-gray-900 text-white' : | ||||||
|  |                           item.href == router.pathname && item.as === 'true' ? | ||||||
|  |                             'bg-gray-900 text-white' : | ||||||
|  |                             'text-gray-900 hover:bg-gray-900 hover:text-white', | ||||||
|  |                             'block px-3 py-2 rounded-md text-base font-medium' | ||||||
|  |                     )} | ||||||
|  |                     aria-current={item.href ? 'page' : undefined} | ||||||
|  |                   > | ||||||
|  |                     {item.name} | ||||||
|  |                   </a> | ||||||
|  |                 </Link> | ||||||
|  |                 ))} | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |       </Transition> | ||||||
|  |   </nav> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Navigation; | ||||||
| @ -0,0 +1,102 @@ | |||||||
|  | import { useTheme } from 'next-themes'; | ||||||
|  | import React from 'react'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const ThemeSwitch = (): JSX.Element => { | ||||||
|  |   const [mounted, setMounted] = React.useState(false); | ||||||
|  |   const { theme, setTheme } = useTheme(); | ||||||
|  | 
 | ||||||
|  |   // After mounting, we have access to the theme
 | ||||||
|  |   React.useEffect(() => setMounted(true), []); | ||||||
|  | 
 | ||||||
|  |   if (!mounted) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const isDark = theme === 'dark'; | ||||||
|  |   const color = isDark ? '#fff' : '#000'; | ||||||
|  |   const maskColor = isDark ? '#000' : '#fff'; | ||||||
|  |   return ( | ||||||
|  |     <button | ||||||
|  |       className="theme-button" | ||||||
|  |       type="button" | ||||||
|  |       aria-label="Toggle Dark Mode" | ||||||
|  |       onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} | ||||||
|  |     > | ||||||
|  |       <div className="moon-or-sun" /> | ||||||
|  |       <div className="moon-mask" /> | ||||||
|  |       <style jsx>{` | ||||||
|  |         .theme-button { | ||||||
|  |           opacity: 0.5; | ||||||
|  |           position: relative; | ||||||
|  |           border-radius: 5px; | ||||||
|  |           width: 42px; | ||||||
|  |           height: 42px; | ||||||
|  |           display: flex; | ||||||
|  |           align-items: center; | ||||||
|  |           justify-content: center; | ||||||
|  |           transition: opacity 0.3s ease; | ||||||
|  |         } | ||||||
|  |         .theme-button:hover { | ||||||
|  |           opacity: 1; | ||||||
|  |         } | ||||||
|  |         .moon-or-sun { | ||||||
|  |           position: relative; | ||||||
|  |           width: 20px; | ||||||
|  |           height: 20px; | ||||||
|  |           border-radius: 50%; | ||||||
|  |           border: ${isDark ? '4px' : '2px'} solid; | ||||||
|  |           border-color: ${color}; | ||||||
|  |           background: ${color}; | ||||||
|  |           transform: scale(${isDark ? 0.5 : 1}); | ||||||
|  |           transition: all 0.45s ease; | ||||||
|  |           overflow: ${isDark ? 'visible' : 'hidden'}; | ||||||
|  |         } | ||||||
|  |         .moon-or-sun::before { | ||||||
|  |           content: ''; | ||||||
|  |           position: absolute; | ||||||
|  |           right: -9px; | ||||||
|  |           top: -9px; | ||||||
|  |           height: 20px; | ||||||
|  |           width: 20px; | ||||||
|  |           border: 2px solid; | ||||||
|  |           border-color: ${color}; | ||||||
|  |           border-radius: 50%; | ||||||
|  |           transform: translate(${isDark ? '14px, -14px' : '0, 0'}); | ||||||
|  |           opacity: ${isDark ? 0 : 1}; | ||||||
|  |           transition: transform 0.45s ease; | ||||||
|  |         } | ||||||
|  |         .moon-or-sun::after { | ||||||
|  |           content: ''; | ||||||
|  |           width: 8px; | ||||||
|  |           height: 8px; | ||||||
|  |           border-radius: 50%; | ||||||
|  |           margin: -4px 0 0 -4px; | ||||||
|  |           position: absolute; | ||||||
|  |           top: 50%; | ||||||
|  |           left: 50%; | ||||||
|  |           box-shadow: 0 -23px 0 ${color}, 0 23px 0 ${color}, 23px 0 0 ${color}, | ||||||
|  |             -23px 0 0 ${color}, 15px 15px 0 ${color}, -15px 15px 0 ${color}, | ||||||
|  |             15px -15px 0 ${color}, -15px -15px 0 ${color}; | ||||||
|  |           transform: scale(${isDark ? 1 : 0}); | ||||||
|  |           transition: all 0.35s ease; | ||||||
|  |         } | ||||||
|  |         .moon-mask { | ||||||
|  |           position: absolute; | ||||||
|  |           right: 4px; | ||||||
|  |           top: 4px; | ||||||
|  |           height: 20px; | ||||||
|  |           width: 20px; | ||||||
|  |           border-radius: 50%; | ||||||
|  |           border: 0; | ||||||
|  |           background: ${maskColor}; | ||||||
|  |           transform: translate(${isDark ? '4px, -4px' : '0, 0'}); | ||||||
|  |           opacity: ${isDark ? 0 : 1}; | ||||||
|  |           transition: transform 0.45s ease; | ||||||
|  |         } | ||||||
|  |       `}</style>
 | ||||||
|  |     </button> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default ThemeSwitch; | ||||||
| @ -0,0 +1 @@ | |||||||
|  | export * from './Layout'; | ||||||
| @ -0,0 +1,45 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import matter from 'gray-matter'; | ||||||
|  | import { join } from 'path'; | ||||||
|  | import { POSTS_PATH } from '../utils/mdxUtils'; | ||||||
|  | 
 | ||||||
|  | export function getPostSlugs(): string[] { | ||||||
|  |   return fs.readdirSync(POSTS_PATH); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PostItems = { | ||||||
|  |   [key: string]: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function getPostBySlug(slug: string, fields: string[] = []): PostItems { | ||||||
|  |   const realSlug = slug.replace(/\.mdx$/, ''); | ||||||
|  |   const fullPath = join(POSTS_PATH, `${realSlug}.mdx`); | ||||||
|  |   const fileContents = fs.readFileSync(fullPath, 'utf8'); | ||||||
|  |   const { data, content } = matter(fileContents); | ||||||
|  | 
 | ||||||
|  |   const items: PostItems = {}; | ||||||
|  | 
 | ||||||
|  |   // Ensure only the minimal needed data is exposed
 | ||||||
|  |   fields.forEach((field) => { | ||||||
|  |     if (field === 'slug') { | ||||||
|  |       items[field] = realSlug; | ||||||
|  |     } | ||||||
|  |     if (field === 'content') { | ||||||
|  |       items[field] = content; | ||||||
|  |     } | ||||||
|  |     if (data[field]) { | ||||||
|  |       items[field] = data[field]; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return items; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getAllPosts(fields: string[] = []): PostItems[] { | ||||||
|  |   const slugs = getPostSlugs(); | ||||||
|  |   const posts = slugs | ||||||
|  |     .map((slug) => getPostBySlug(slug, fields)) | ||||||
|  |     // sort posts by date in descending order
 | ||||||
|  |     .sort((post1, post2) => (post1.date > post2.date ? -1 : 1)); | ||||||
|  |   return posts; | ||||||
|  | } | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | /** @type {import('next').NextConfig} */ | ||||||
|  | const nextConfig = { | ||||||
|  |   reactStrictMode: true, | ||||||
|  |   swcMinify: true, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = nextConfig | ||||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -0,0 +1,40 @@ | |||||||
|  | { | ||||||
|  |   "name": "krasnikov_blog", | ||||||
|  |   "version": "0.1.0", | ||||||
|  |   "private": true, | ||||||
|  |   "scripts": { | ||||||
|  |     "dev": "next dev", | ||||||
|  |     "build": "next build", | ||||||
|  |     "start": "next start", | ||||||
|  |     "lint": "next lint" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@code-hike/mdx": "^0.7.4", | ||||||
|  |     "@headlessui/react": "^1.7.3", | ||||||
|  |     "@heroicons/react": "^1.0.6", | ||||||
|  |     "@mdx-js/loader": "^2.1.5", | ||||||
|  |     "@next/mdx": "^12.3.1", | ||||||
|  |     "@tailwindcss/typography": "^0.5.7", | ||||||
|  |     "date-fns": "^2.29.3", | ||||||
|  |     "gray-matter": "^4.0.3", | ||||||
|  |     "next": "12.3.1", | ||||||
|  |     "next-mdx-remote": "^4.1.0", | ||||||
|  |     "next-themes": "^0.2.1", | ||||||
|  |     "react": "18.2.0", | ||||||
|  |     "react-dom": "18.2.0", | ||||||
|  |     "rehype-autolink-headings": "^6.1.1", | ||||||
|  |     "rehype-slug": "^5.0.1", | ||||||
|  |     "remark-code-titles": "^0.1.2" | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "@types/node": "18.11.3", | ||||||
|  |     "@types/react": "18.0.21", | ||||||
|  |     "@types/react-dom": "18.0.6", | ||||||
|  |     "autoprefixer": "^10.4.12", | ||||||
|  |     "eslint": "8.26.0", | ||||||
|  |     "eslint-config-next": "12.3.1", | ||||||
|  |     "postcss": "^8.4.18", | ||||||
|  |     "tailwindcss": "^3.2.1", | ||||||
|  |     "typescript": "4.8.4" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | import '../styles/globals.css' | ||||||
|  | import type { AppProps } from 'next/app' | ||||||
|  | 
 | ||||||
|  | function MyApp({ Component, pageProps }: AppProps) { | ||||||
|  |   return <Component {...pageProps} /> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default MyApp | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
 | ||||||
|  | import type { NextApiRequest, NextApiResponse } from 'next' | ||||||
|  | 
 | ||||||
|  | type Data = { | ||||||
|  |   name: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default function handler( | ||||||
|  |   req: NextApiRequest, | ||||||
|  |   res: NextApiResponse<Data> | ||||||
|  | ) { | ||||||
|  |   res.status(200).json({ name: 'John Doe' }) | ||||||
|  | } | ||||||
| @ -0,0 +1,49 @@ | |||||||
|  | import type { GetStaticProps, NextPage } from 'next' | ||||||
|  | import { format, parseISO } from 'date-fns'; | ||||||
|  | import Link from 'next/link'; | ||||||
|  | import { Layout } from '../components/index' | ||||||
|  | import { PostType } from '../types/post'; | ||||||
|  | import { getAllPosts } from '../lib/api'; | ||||||
|  | 
 | ||||||
|  | type IndexProps = { | ||||||
|  |   posts: PostType[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const Home = ({ posts }: IndexProps): JSX.Element => { | ||||||
|  |   return ( | ||||||
|  |     <Layout> | ||||||
|  |       <h1>Красников Павел</h1> | ||||||
|  |       <p>Робототехника, программирование</p> | ||||||
|  |       {posts.map((post) => ( | ||||||
|  |         <article key={post.slug} className="mt-12"> | ||||||
|  |           <p className="mb-1 text-sm text-gray-500 dark:text-gray-400"> | ||||||
|  |             {format(parseISO(post.date), 'MMMM dd, yyyy')} | ||||||
|  |           </p> | ||||||
|  |           <h1 className="mb-2 text-xl"> | ||||||
|  |             <Link as={`/posts/${post.slug}`} href={`/posts/[slug]`}> | ||||||
|  |               <a className="text-gray-900 dark:hover:text-blue-400"> | ||||||
|  |                 {post.title} | ||||||
|  |               </a> | ||||||
|  |             </Link> | ||||||
|  |           </h1> | ||||||
|  |           <p className="mb-3">{post.description}</p> | ||||||
|  |           <p> | ||||||
|  |             <Link as={`/posts/${post.slug}`} href={`/posts/[slug]`}> | ||||||
|  |               <a>Подробнее...</a> | ||||||
|  |             </Link> | ||||||
|  |           </p> | ||||||
|  |         </article> | ||||||
|  |       ))} | ||||||
|  |     </Layout> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const getStaticProps: GetStaticProps = async () => { | ||||||
|  |   const posts = getAllPosts(['date', 'description', 'slug', 'title']); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     props: { posts }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Home | ||||||
| @ -0,0 +1,95 @@ | |||||||
|  | import { format, parseISO } from 'date-fns'; | ||||||
|  | import fs from 'fs'; | ||||||
|  | import matter from 'gray-matter'; | ||||||
|  | import { GetStaticPaths, GetStaticProps } from 'next'; | ||||||
|  | import { serialize } from 'next-mdx-remote/serialize'; | ||||||
|  | import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'; | ||||||
|  | import Head from 'next/head'; | ||||||
|  | import Image from 'next/image'; | ||||||
|  | import Link from 'next/link'; | ||||||
|  | import path from 'path'; | ||||||
|  | import React from 'react'; | ||||||
|  | import rehypeAutolinkHeadings from 'rehype-autolink-headings'; | ||||||
|  | import rehypeSlug from 'rehype-slug'; | ||||||
|  | import { Layout } from '../../components/index'; | ||||||
|  | import { MetaProps } from '../../types/layout'; | ||||||
|  | import { PostType } from '../../types/post'; | ||||||
|  | import { postFilePaths, POSTS_PATH } from '../../utils/mdxUtils'; | ||||||
|  | 
 | ||||||
|  | // Custom components/renderers to pass to MDX.
 | ||||||
|  | // Since the MDX files aren't loaded by webpack, they have no knowledge of how
 | ||||||
|  | // to handle import statements. Instead, you must include components in scope
 | ||||||
|  | // here.
 | ||||||
|  | const components = { | ||||||
|  |   Head, | ||||||
|  |   Image, | ||||||
|  |   Link, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type PostPageProps = { | ||||||
|  |   source: MDXRemoteSerializeResult; | ||||||
|  |   frontMatter: PostType; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const PostPage = ({ source, frontMatter }: PostPageProps): JSX.Element => { | ||||||
|  |   const customMeta: MetaProps = { | ||||||
|  |     title: `${frontMatter.title} - RoboTop`, | ||||||
|  |     description: frontMatter.description, | ||||||
|  |     image: `${frontMatter.image}`, | ||||||
|  |     date: frontMatter.date, | ||||||
|  |     type: 'article', | ||||||
|  |   }; | ||||||
|  |   return ( | ||||||
|  |     <Layout customMeta={customMeta}> | ||||||
|  |       <article className="max-w"> | ||||||
|  |         <h1 className="mb-3 text-gray-900 dark:text-white"> | ||||||
|  |           {frontMatter.title} | ||||||
|  |         </h1> | ||||||
|  |         <p className="mb-10 text-sm text-gray-500 dark:text-gray-400"> | ||||||
|  |           {format(parseISO(frontMatter.date), 'MMMM dd, yyyy')} | ||||||
|  |         </p> | ||||||
|  |         <div className="prose dark:prose-dark"> | ||||||
|  |           <MDXRemote {...source} components={components} /> | ||||||
|  |         </div> | ||||||
|  |       </article> | ||||||
|  |     </Layout> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const getStaticProps: GetStaticProps = async ({ params }) => { | ||||||
|  |   const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`); | ||||||
|  |   const source = fs.readFileSync(postFilePath); | ||||||
|  | 
 | ||||||
|  |   const { content, data } = matter(source); | ||||||
|  | 
 | ||||||
|  |   const mdxSource = await serialize(content, { | ||||||
|  |     // Optionally pass remark/rehype plugins
 | ||||||
|  |     mdxOptions: { | ||||||
|  |       remarkPlugins: [require('remark-code-titles')], | ||||||
|  |       rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings], | ||||||
|  |     }, | ||||||
|  |     scope: data, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     props: { | ||||||
|  |       source: mdxSource, | ||||||
|  |       frontMatter: data, | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const getStaticPaths: GetStaticPaths = async () => { | ||||||
|  |   const paths = postFilePaths | ||||||
|  |     // Remove file extensions for page paths
 | ||||||
|  |     .map((path) => path.replace(/\.mdx?$/, '')) | ||||||
|  |     // Map the path into the static paths object required by Next.js
 | ||||||
|  |     .map((slug) => ({ params: { slug } })); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     paths, | ||||||
|  |     fallback: false, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default PostPage; | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | // If you want to use other PostCSS plugins, see the following:
 | ||||||
|  | // https://tailwindcss.com/docs/using-with-preprocessors
 | ||||||
|  | module.exports = { | ||||||
|  |   plugins: { | ||||||
|  |     tailwindcss: { config: './tailwind.config.js' }, | ||||||
|  |     autoprefixer: {}, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @ -0,0 +1,22 @@ | |||||||
|  | --- | ||||||
|  | title: Python. Первая программа, арифметические выражения - Задание 1 | ||||||
|  | description: Решение задания 1 | ||||||
|  | date: '2021-10-23' | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | ## I Python. Первая программа, арифметические выражения - Задание 1 | ||||||
|  | 
 | ||||||
|  | Разработчики языка Python придерживаются определённой философии программирования, называемой «The Zen of Python» («Дзен Питона»). Её текст выдаётся интерпретатором Python по команде import this. | ||||||
|  | Напишите программу, состоящую из одной строки: | ||||||
|  | ```python | ||||||
|  | import this | ||||||
|  | ``` | ||||||
|  | Какое первое слово в последней строке выведет эта программа? | ||||||
|  | ### Решение | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | import this | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Ответ: | ||||||
|  | Namespaces | ||||||
| @ -0,0 +1,265 @@ | |||||||
|  | --- | ||||||
|  | title: Python. Функции | ||||||
|  | description: Интерпретатор Python имеет ряд встроенных функций и типов, которые всегда доступны. | ||||||
|  | date: '2021-10-24' | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | # abs(x) | ||||||
|  | 
 | ||||||
|  | Возвращает абсолютное значение числа. Аргументом может быть целое число, число с плавающей запятой или реализация объекта __abs__().  | ||||||
|  | Если аргумент является комплексным числом, возвращается его величина. | ||||||
|  | 
 | ||||||
|  | ## Пример | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(abs(-4)) | ||||||
|  | 
 | ||||||
|  | # 4 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # bin(x) | ||||||
|  | Преобразование целого числа в двоичную строку с префиксом “0b”.  | ||||||
|  | 
 | ||||||
|  | Результатом является допустимое выражение Python.  | ||||||
|  | Если X не является intобъектом Python, он должен определить __index__() метод, который возвращает целое число. Некоторые примеры: | ||||||
|  | ```python | ||||||
|  | print(bin(7)) | ||||||
|  | 
 | ||||||
|  | # 0b111 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # chr(i) | ||||||
|  | Возвращает строку, представляющую символ, кодовой точкой которого в Юникоде является целое число i.  | ||||||
|  | Например, chr(97)возвращает строку 'a', в то время chr(8364)как возвращает строку '€'. Это обратное ord(). | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(chr(97)) | ||||||
|  | 
 | ||||||
|  | # a | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # ord(i) | ||||||
|  | Учитывая строку, представляющую один символ Юникода, верните целое число, представляющее кодовую точку Юникода этого символа.  | ||||||
|  | Например, ord('a') возвращает целое 97 число и ord('€') (знак евро) возвращает 8364. Это обратное chr(). | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(ord('a')) | ||||||
|  | 
 | ||||||
|  | # 97 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # dir(i) | ||||||
|  | Без аргументов возвращает список имен в текущей локальной области. С помощью аргумента попытайтесь вернуть список допустимых атрибутов для этого объекта. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | 
 | ||||||
|  | class Shape: | ||||||
|  |     def __dir__(self): | ||||||
|  |         return ['area', 'perimeter', 'location'] | ||||||
|  | s = Shape() | ||||||
|  | print(dir(s)) | ||||||
|  | 
 | ||||||
|  | # ['area', 'location', 'perimeter'] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # divmod(i, j)  | ||||||
|  | Функция divmod() возвращает кортеж, содержащий частное и остаток. Не поддерживает комплексные числа.  | ||||||
|  | Со смешанными типами операндов применяются правила для двоичных арифметических операторов. | ||||||
|  | 
 | ||||||
|  | Для целых результат аналогичен (a // b, a % b). | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(divmod(5, 3)) | ||||||
|  | 
 | ||||||
|  | #  (1, 2 ) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # eval() | ||||||
|  | Аргументами являются строка и необязательные глобальные и локальные переменные. Если предусмотрено, глобальные должен быть словарь.  | ||||||
|  | Если предусмотрено, локальные может быть любым объектом сопоставления. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | x = 1 | ||||||
|  | print(eval('x + 1')) | ||||||
|  | 
 | ||||||
|  | #  2 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # float() | ||||||
|  | Возвращает число с плавающей запятой, построенное из числа или строки x. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(float('+1.23')) | ||||||
|  | #  1.23 | ||||||
|  | 
 | ||||||
|  | print(float('-Infinity')) | ||||||
|  | # -inf | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # help() | ||||||
|  | Вызовите встроенную справочную систему.  | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(help()) | ||||||
|  | #  Welcome to Python 3.10's help utility! | ||||||
|  | 
 | ||||||
|  | print(help('abs')) | ||||||
|  | # abs(x, /) | ||||||
|  | # Return the absolute value of the argument. | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # hex(x) | ||||||
|  | 
 | ||||||
|  | Преобразуйте целое число в строчную шестнадцатеричную строку с префиксом “0x”. | ||||||
|  | Если x не является intобъектом Python, он должен определить __index__()метод, который возвращает целое число. Некоторые примеры: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | print(hex(255)) | ||||||
|  | #  0xff | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # len(x) | ||||||
|  | Возвращает длину (количество элементов) объекта.  | ||||||
|  | Аргументом может быть последовательность (например, строка, байты, кортеж, список или диапазон) или коллекция (например, словарь, набор или замороженный набор). | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | x = [1,2,3,4,5,6] | ||||||
|  | print(len(x)) | ||||||
|  | #  6 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # map(i, j) | ||||||
|  | map() перебирает элементы входного итерируемого (или итерируемых) и возвращает итератор,  | ||||||
|  | который является результатом применения функции преобразования к каждому элементу в исходном входном итерируемом. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | def plus(a, b, c): | ||||||
|  |   return a + b +c | ||||||
|  | 
 | ||||||
|  | # функция 'plus' применяется к элементам  | ||||||
|  | # из всех последовательностей параллельно | ||||||
|  | x = map(plus, [1, 2], [3, 4], [5, 6]) | ||||||
|  | print(list(x)) | ||||||
|  | # [9, 12] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # max(x) | ||||||
|  | Возвращает самый большой элемент в итерации или самый большой из двух или более аргументов. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | x = [1, 3, 5, 1, 2] | ||||||
|  | 
 | ||||||
|  | print(max(x)) | ||||||
|  | # 5 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # min(x) | ||||||
|  | Возвращает наименьший элемент в итерируемом или наименьший из двух или более аргументов. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | x = [1, 3, 5, 1, 2] | ||||||
|  | 
 | ||||||
|  | print(min(x)) | ||||||
|  | # 1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # open() | ||||||
|  | 
 | ||||||
|  | Открыть файл и возвращает соответствующий файловый объект. Если файл не может быть открыт, OSError возникает проблема. См. Чтение и запись файлов. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  |     f = open('workfile', 'r', encoding="utf-8") | ||||||
|  | 
 | ||||||
|  | Варианты использования режимов: | ||||||
|  | 
 | ||||||
|  | * 'r' - Открывает файл только для чтения. Указатель файла помещается в начале файла. Это режим "по умолчанию". | ||||||
|  | * 'rb' - Открывает файл в бинарном режиме только для чтения. Указатель файла помещается в начале файла. Это режим "по умолчанию". | ||||||
|  | * 'r+' - Открывает файл для чтения и записи. Указатель файла помещается в начало файла. | ||||||
|  | * 'rb+' - Открывает файл в бинарном режиме для чтения и записи. Указатель файла помещается в начале файла. Это режим "по умолчанию". | ||||||
|  | * 'w' - Открывает файл только для записи. Перезаписывает файл, если файл существует. Если файл не существует, создает новый файл для записи. | ||||||
|  | * 'wb' - Открывает файл в бинарном режиме только для записи. Перезаписывает файл, если файл существует. Если файл не существует, создает новый файл для записи. | ||||||
|  | * 'w+' - Открывает файл для записи и чтения. Перезаписывает существующий файл, если файл существует. Если файл не существует, создается новый файл для чтения и записи. | ||||||
|  | * 'wb+' - Открывает файл в бинарном режиме для записи и чтения. Перезаписывает существующий файл, если файл существует. Если файл не существует, создается новый файл для чтения и записи. | ||||||
|  | * 'a' - Открывает файл для добавления. Указатель файла находится в конце файла, если файл существует. То есть файл находится в режиме добавления. Если файл не существует, он создает новый файл для записи. | ||||||
|  | * 'ab' - Открывает файл в бинарном режиме для добавления. Указатель файла находится в конце файла, если файл существует. То есть файл находится в режиме добавления. Если файл не существует, он создает новый файл для записи. | ||||||
|  | * 'a+' - Открывает файл для добавления и чтения. Указатель файла находится в конце файла, если файл существует. Файл открывается в режиме добавления. Если файл не существует, он создает новый файл для чтения и записи. | ||||||
|  | * 'ab+' - Открывает файл в бинарном режиме для добавления и чтения. Указатель файла находится в конце файла, если файл существует. Файл открывается в режиме добавления. Если файл не существует, он создает новый файл для чтения и записи. | ||||||
|  | 
 | ||||||
|  |     print(f.read()) | ||||||
|  | # 1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | *** | ||||||
|  | 
 | ||||||
|  | # sorted() | ||||||
|  | 
 | ||||||
|  | Функция sorted() возвращает новый отсортированный список итерируемого объекта (списка, словаря, кортежа). По умолчанию она сортирует его по возрастанию. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | x = [1, 3, 5, 1, 2] | ||||||
|  | 
 | ||||||
|  | print(sorted(x)) | ||||||
|  | 
 | ||||||
|  | # [1, 1, 2, 3, 5] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # sum() | ||||||
|  | 
 | ||||||
|  | Суммирует начало и элементы итерации слева направо и возвращает итоговое значение.  | ||||||
|  | Элементы итерации обычно представляют собой числа, а начальное значение не может быть строкой. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | x = [1, 3, 5, 1, 2] | ||||||
|  | 
 | ||||||
|  | print(sum(x)) | ||||||
|  | 
 | ||||||
|  | # 12 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ***  | ||||||
|  | 
 | ||||||
|  | # zip() | ||||||
|  | Выполняйте параллельную итерацию по нескольким итерациям, создавая кортежи с элементом из каждого. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | for item in zip([1,2,3], ['sugar', 'spice', 'everything nice']): | ||||||
|  |     print(item) | ||||||
|  | 
 | ||||||
|  | # (1, 'sugar') | ||||||
|  | # (2, 'spice') | ||||||
|  | # (3, 'everything nice') | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| After Width: | Height: | Size: 25 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| @ -0,0 +1,119 @@ | |||||||
|  | @tailwind base; | ||||||
|  | @tailwind components; | ||||||
|  | @tailwind utilities; | ||||||
|  | 
 | ||||||
|  | @layer base { | ||||||
|  |   h1 { | ||||||
|  |     @apply mb-6 text-3xl font-semibold; | ||||||
|  |   } | ||||||
|  |   h2 { | ||||||
|  |     @apply text-2xl font-semibold; | ||||||
|  |   } | ||||||
|  |   p { | ||||||
|  |     @apply mb-4; | ||||||
|  |   } | ||||||
|  |   a { | ||||||
|  |     @apply text-blue-500 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Post styles */ | ||||||
|  | .prose { | ||||||
|  |   max-width: 100vh; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .prose pre { | ||||||
|  |   @apply bg-gray-50 border border-gray-200 dark:border-gray-700 dark:bg-gray-900; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .prose code { | ||||||
|  |   @apply text-gray-800 dark:text-gray-200 px-1 py-0.5 border border-gray-100 dark:border-gray-800 rounded-md bg-gray-100 dark:bg-gray-900; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .prose img { | ||||||
|  |   /* Don't apply styles to next/image */ | ||||||
|  |   @apply m-0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Prism Styles */ | ||||||
|  | .token.comment, | ||||||
|  | .token.prolog, | ||||||
|  | .token.doctype, | ||||||
|  | .token.cdata { | ||||||
|  |   @apply text-gray-700 dark:text-gray-300; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.punctuation { | ||||||
|  |   @apply text-gray-700 dark:text-gray-300; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.property, | ||||||
|  | .token.tag, | ||||||
|  | .token.boolean, | ||||||
|  | .token.number, | ||||||
|  | .token.constant, | ||||||
|  | .token.symbol, | ||||||
|  | .token.deleted { | ||||||
|  |   @apply text-green-500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.selector, | ||||||
|  | .token.attr-name, | ||||||
|  | .token.string, | ||||||
|  | .token.char, | ||||||
|  | .token.builtin, | ||||||
|  | .token.inserted { | ||||||
|  |   @apply text-purple-500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.operator, | ||||||
|  | .token.entity, | ||||||
|  | .token.url, | ||||||
|  | .language-css .token.string, | ||||||
|  | .style .token.string { | ||||||
|  |   @apply text-yellow-500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.atrule, | ||||||
|  | .token.attr-value, | ||||||
|  | .token.keyword { | ||||||
|  |   @apply text-blue-500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.function, | ||||||
|  | .token.class-name { | ||||||
|  |   @apply text-pink-500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .token.regex, | ||||||
|  | .token.important, | ||||||
|  | .token.variable { | ||||||
|  |   @apply text-yellow-500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | code[class*='language-'], | ||||||
|  | pre[class*='language-'] { | ||||||
|  |   @apply text-gray-800 dark:text-gray-50; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pre::-webkit-scrollbar { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pre { | ||||||
|  |   -ms-overflow-style: none; /* IE and Edge */ | ||||||
|  |   scrollbar-width: none; /* Firefox */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Remark Styles */ | ||||||
|  | .remark-code-title { | ||||||
|  |   @apply text-gray-800 dark:text-gray-200 px-5 py-3 border border-b-0 border-gray-200 dark:border-gray-700 rounded-t bg-gray-200 dark:bg-gray-800 text-sm font-mono font-bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .remark-code-title + pre { | ||||||
|  |   @apply mt-0 rounded-t-none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mdx-marker { | ||||||
|  |   @apply block -mx-4 px-4 bg-gray-100 dark:bg-gray-800 border-l-4 border-blue-500; | ||||||
|  | } | ||||||
| @ -0,0 +1,75 @@ | |||||||
|  | const { spacing } = require('tailwindcss/defaultTheme'); | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   mode: 'jit', | ||||||
|  |   purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], | ||||||
|  |   darkMode: 'class', // or 'media' or 'class'
 | ||||||
|  |   theme: { | ||||||
|  |     extend: { | ||||||
|  |       typography: (theme) => ({ | ||||||
|  |         DEFAULT: { | ||||||
|  |           css: { | ||||||
|  |             color: theme('colors.gray.700'), | ||||||
|  |             a: { | ||||||
|  |               color: theme('colors.blue.500'), | ||||||
|  |               '&:hover': { | ||||||
|  |                 color: theme('colors.blue.700'), | ||||||
|  |               }, | ||||||
|  |               code: { color: theme('colors.blue.400') }, | ||||||
|  |             }, | ||||||
|  |             'h2,h3,h4': { | ||||||
|  |               'scroll-margin-top': spacing[32], | ||||||
|  |             }, | ||||||
|  |             code: { color: theme('colors.pink.500') }, | ||||||
|  |             'blockquote p:first-of-type::before': false, | ||||||
|  |             'blockquote p:last-of-type::after': false, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         dark: { | ||||||
|  |           css: { | ||||||
|  |             color: theme('colors.gray.300'), | ||||||
|  |             a: { | ||||||
|  |               color: theme('colors.blue.400'), | ||||||
|  |               '&:hover': { | ||||||
|  |                 color: theme('colors.blue.600'), | ||||||
|  |               }, | ||||||
|  |               code: { color: theme('colors.blue.400') }, | ||||||
|  |             }, | ||||||
|  |             blockquote: { | ||||||
|  |               borderLeftColor: theme('colors.gray.800'), | ||||||
|  |               color: theme('colors.gray.300'), | ||||||
|  |             }, | ||||||
|  |             'h2,h3,h4': { | ||||||
|  |               color: theme('colors.gray.100'), | ||||||
|  |               'scroll-margin-top': spacing[32], | ||||||
|  |             }, | ||||||
|  |             hr: { borderColor: theme('colors.gray.800') }, | ||||||
|  |             ol: { | ||||||
|  |               li: { | ||||||
|  |                 '&:before': { color: theme('colors.gray.500') }, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |             ul: { | ||||||
|  |               li: { | ||||||
|  |                 '&:before': { backgroundColor: theme('colors.gray.500') }, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |             strong: { color: theme('colors.gray.300') }, | ||||||
|  |             thead: { | ||||||
|  |               color: theme('colors.gray.100'), | ||||||
|  |             }, | ||||||
|  |             tbody: { | ||||||
|  |               tr: { | ||||||
|  |                 borderBottomColor: theme('colors.gray.700'), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   variants: { | ||||||
|  |     typography: ['dark'], | ||||||
|  |   }, | ||||||
|  |   plugins: [require('@tailwindcss/typography')], | ||||||
|  | }; | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "target": "es5", | ||||||
|  |     "lib": ["dom", "dom.iterable", "esnext"], | ||||||
|  |     "allowJs": true, | ||||||
|  |     "skipLibCheck": true, | ||||||
|  |     "strict": true, | ||||||
|  |     "forceConsistentCasingInFileNames": true, | ||||||
|  |     "noEmit": true, | ||||||
|  |     "esModuleInterop": true, | ||||||
|  |     "module": "esnext", | ||||||
|  |     "moduleResolution": "node", | ||||||
|  |     "resolveJsonModule": true, | ||||||
|  |     "isolatedModules": true, | ||||||
|  |     "jsx": "preserve", | ||||||
|  |     "incremental": true | ||||||
|  |   }, | ||||||
|  |   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], | ||||||
|  |   "exclude": ["node_modules"] | ||||||
|  | } | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | import { PostType } from './post'; | ||||||
|  | 
 | ||||||
|  | export interface MetaProps | ||||||
|  |   extends Pick<PostType, 'date' | 'description' | 'image' | 'title'> { | ||||||
|  |   /** | ||||||
|  |    * For the meta tag `og:type` | ||||||
|  |    */ | ||||||
|  |   type?: string; | ||||||
|  | } | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | export type PostType = { | ||||||
|  |   date?: string; | ||||||
|  |   description?: string; | ||||||
|  |   image?: string; | ||||||
|  |   slug: string; | ||||||
|  |   title: string; | ||||||
|  | }; | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import path from 'path'; | ||||||
|  | 
 | ||||||
|  | // POSTS_PATH is useful when you want to get the path to a specific file
 | ||||||
|  | export const POSTS_PATH = path.join(process.cwd(), 'posts'); | ||||||
|  | 
 | ||||||
|  | // postFilePaths is the list of all mdx files inside the POSTS_PATH directory
 | ||||||
|  | export const postFilePaths = fs | ||||||
|  |   .readdirSync(POSTS_PATH) | ||||||
|  |   // Only include md(x) files
 | ||||||
|  |   .filter((path) => /\.mdx?$/.test(path)); | ||||||
					Loading…
					
					
				
		Reference in new issue