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