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