71 files changed
+4238
-2490
No files matched your search
@@ -1,26 +0,0 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "standard",
|
||||
"plugins": [
|
||||
"standard",
|
||||
"promise",
|
||||
"react",
|
||||
],
|
||||
"rules": {
|
||||
"semi": ["error", "never"],
|
||||
"space-before-function-paren": "off",
|
||||
"comma-dangle": "off",
|
||||
"no-tabs": "off",
|
||||
"indent": ["error", "tab"],
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"space-before-function-paren": "off",
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"always-multiline"
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"double"
|
||||
],
|
||||
"no-tabs": "off",
|
||||
"indent": [
|
||||
"warn",
|
||||
4
|
||||
],
|
||||
"eol-last": ["error", "always"],
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error"
|
||||
}
|
||||
}
|
||||
File renamed without changes.
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
|
||||
Generated
+3590
-1871
File diff suppressed because it is too large.
Load diff
+8
-6
@@ -7,6 +7,7 @@
|
||||
"start": "next start",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"lint": "next lint",
|
||||
"sitemap": "node scripts/sitemap.js",
|
||||
"feed": "node scripts/feed.js"
|
||||
},
|
||||
@@ -17,18 +18,17 @@
|
||||
"author": "Roman Timashev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.2",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.11.1",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^12.3.2",
|
||||
"next": "^12.1.6",
|
||||
"markdown-it": "^13.0.1",
|
||||
"next": "^12.2.5",
|
||||
"rc-tooltip": "^5.1.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-use": "^17.2.1",
|
||||
"sharp": "^0.30.6",
|
||||
"sharp": "^0.31.0",
|
||||
"sitemap": "^1.5.0",
|
||||
"typograf": "^6.11.1",
|
||||
"use-media": "^1.4.0"
|
||||
@@ -36,12 +36,14 @@
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/node": "^12.12.34",
|
||||
"@types/react": "^16.9.32",
|
||||
"@types/sharp": "^0.29.2",
|
||||
"eslint": "^7.1.0",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-config-next": "12.2.5",
|
||||
"feed": "^4.1.0",
|
||||
"should": "^8.2.2",
|
||||
"typescript": "^4.4.2"
|
||||
"typescript": "^4.8.2"
|
||||
}
|
||||
}
|
||||
+83
-84
@@ -1,20 +1,20 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { tail } from 'lodash'
|
||||
import { Page } from 'src/components/Page'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { MetaBuilder } from 'src/lib/meta'
|
||||
import { apiGet } from '@/next-lib'
|
||||
import { GetStaticProps, NextPage } from 'next'
|
||||
import { IBreadcumbsPart, IMenu, IMeta, ITag, Sign, Token } from '@/types'
|
||||
import { Html } from 'src/components/Html'
|
||||
import { Youtube } from '@/components/Youtube'
|
||||
import { createMenu, createPage, createPageUrls } from '@/remote/factory'
|
||||
import { paramsToSlug } from '@/remote/lib'
|
||||
import { PageGrid } from '@/components/PageGrid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FileCard } from '@/components/FIleCard'
|
||||
import { Picture } from '@/components/Picture'
|
||||
import Head from "next/head"
|
||||
import { App } from "src/components/App"
|
||||
import { tail } from "lodash"
|
||||
import { Page } from "src/components/Page"
|
||||
import { Meta } from "src/components/Meta"
|
||||
import { MetaBuilder } from "src/lib/meta"
|
||||
import { apiGet } from "@/next-lib"
|
||||
import { GetStaticProps, NextPage } from "next"
|
||||
import { IBreadcumbsPart, IMenu, IMeta, ITag, Sign, Token } from "@/types"
|
||||
import { Html } from "src/components/Html"
|
||||
import { Youtube } from "@/components/Youtube"
|
||||
import { createMenu, createPage, createPageUrls } from "@/remote/factory"
|
||||
import { paramsToSlug } from "@/remote/lib"
|
||||
import { PageGrid } from "@/components/PageGrid"
|
||||
import { useRouter } from "next/router"
|
||||
import { FileCard } from "@/components/FIleCard"
|
||||
import { Picture } from "@/components/Picture"
|
||||
|
||||
async function getUrls() {
|
||||
let urls: string[] = []
|
||||
@@ -59,8 +59,8 @@ const Index: NextPage<Props> = props => {
|
||||
return (
|
||||
<App
|
||||
contentStyle={{
|
||||
marginTop: 'var(--size-l)',
|
||||
marginBottom: 'var(--size-xl)',
|
||||
marginTop: "var(--size-l)",
|
||||
marginBottom: "var(--size-xl)",
|
||||
}}
|
||||
menu={props.menu}
|
||||
breadcrumbs={props.breadcrumb}
|
||||
@@ -76,70 +76,70 @@ const Index: NextPage<Props> = props => {
|
||||
tags={props.tags}
|
||||
date={props.date ? new Date(props.date) : undefined}
|
||||
>
|
||||
<article className={'article'}>
|
||||
<article className={"article"}>
|
||||
{props.tokens.map((x, i) => {
|
||||
switch (x.token) {
|
||||
case 'html':
|
||||
return (
|
||||
<Html
|
||||
key={i}
|
||||
html={x.data}
|
||||
/>
|
||||
)
|
||||
case "html":
|
||||
return (
|
||||
<Html
|
||||
key={i}
|
||||
html={x.data}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'instagram':
|
||||
return (
|
||||
<Html
|
||||
key={i}
|
||||
html={x.data.embed}
|
||||
/>
|
||||
)
|
||||
case "instagram":
|
||||
return (
|
||||
<Html
|
||||
key={i}
|
||||
html={x.data.embed}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'youtube':
|
||||
return (
|
||||
<Youtube
|
||||
key={i}
|
||||
url={x.data.url}
|
||||
/>
|
||||
)
|
||||
case "youtube":
|
||||
return (
|
||||
<Youtube
|
||||
key={i}
|
||||
url={x.data.url}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'image':
|
||||
return (
|
||||
<Picture
|
||||
key={i}
|
||||
src={x.data.src}
|
||||
alt={x.data.alt}
|
||||
width={x.data.width}
|
||||
height={x.data.height}
|
||||
caption={x.data.caption}
|
||||
blur={x.data.blur}
|
||||
wide={x.wide}
|
||||
/>
|
||||
)
|
||||
case "image":
|
||||
return (
|
||||
<Picture
|
||||
key={i}
|
||||
src={x.data.src}
|
||||
alt={x.data.alt}
|
||||
width={x.data.width}
|
||||
height={x.data.height}
|
||||
caption={x.data.caption}
|
||||
blur={x.data.blur}
|
||||
wide={x.wide}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'file':
|
||||
return (
|
||||
<FileCard
|
||||
key={i}
|
||||
sign={props.documentSignature}
|
||||
{...x.data}
|
||||
/>
|
||||
)
|
||||
case "file":
|
||||
return (
|
||||
<FileCard
|
||||
key={i}
|
||||
sign={props.documentSignature}
|
||||
{...x.data}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'grid':
|
||||
return (
|
||||
<PageGrid
|
||||
key={i}
|
||||
items={x.data.items}
|
||||
/>
|
||||
)
|
||||
case "grid":
|
||||
return (
|
||||
<PageGrid
|
||||
key={i}
|
||||
items={x.data.items}
|
||||
/>
|
||||
)
|
||||
|
||||
default:
|
||||
return (
|
||||
<pre key={i}>
|
||||
{JSON.stringify(x)}
|
||||
</pre>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<pre key={i}>
|
||||
{JSON.stringify(x)}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</article>
|
||||
@@ -158,8 +158,7 @@ export const getStaticProps: GetStaticProps<Props> = async ctx => {
|
||||
}
|
||||
}
|
||||
|
||||
const menu = await apiGet(createMenu)
|
||||
('https://hudozka.tmshv.com/menu', () => [])
|
||||
const menu = await apiGet(createMenu)("https://hudozka.tmshv.com/menu", () => [])
|
||||
|
||||
const description = page.description ?? undefined
|
||||
const breadcrumbSize = page?.breadcrumb?.length ?? 0
|
||||
@@ -182,12 +181,12 @@ export const getStaticProps: GetStaticProps<Props> = async ctx => {
|
||||
menu,
|
||||
breadcrumb,
|
||||
documentSignature: {
|
||||
date: '20.01.2021г.',
|
||||
person: 'Тимашева Марина Геннадьевна',
|
||||
position: 'Директор',
|
||||
signature: '0ac4ea89753a4ba9893799442325fb41',
|
||||
}
|
||||
}
|
||||
date: "20.01.2021г.",
|
||||
person: "Тимашева Марина Геннадьевна",
|
||||
position: "Директор",
|
||||
signature: "0ac4ea89753a4ba9893799442325fb41",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +197,7 @@ export const getStaticPaths = async () => {
|
||||
fallback: true,
|
||||
paths: urls
|
||||
.map(path => {
|
||||
const slug = tail(path.split('/'))
|
||||
const slug = tail(path.split("/"))
|
||||
|
||||
return {
|
||||
params: {
|
||||
|
||||
+10
-17
@@ -1,13 +1,13 @@
|
||||
import 'src/style/style.css'
|
||||
import 'src/style/content.css'
|
||||
import 'src/style/article.css'
|
||||
import 'src/style/marker/marker-table.css'
|
||||
import 'src/style/kazimir/video.css'
|
||||
import "src/style/style.css"
|
||||
import "src/style/content.css"
|
||||
import "src/style/article.css"
|
||||
import "src/style/marker/marker-table.css"
|
||||
import "src/style/kazimir/video.css"
|
||||
|
||||
import App from 'next/app'
|
||||
import Head from 'next/head'
|
||||
import { YMetrika } from 'src/components/YMetrika'
|
||||
import { GAnalytics } from 'src/components/GAnalytics'
|
||||
import App from "next/app"
|
||||
import Head from "next/head"
|
||||
import { YMetrika } from "src/components/YMetrika"
|
||||
import { GAnalytics } from "src/components/GAnalytics"
|
||||
|
||||
export default class MyApp extends App {
|
||||
render() {
|
||||
@@ -38,15 +38,8 @@ export default class MyApp extends App {
|
||||
href="/feed.xml"
|
||||
/>
|
||||
|
||||
{/* Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;900&display=swap"
|
||||
rel="preload"
|
||||
as="style"
|
||||
/>
|
||||
|
||||
<YMetrika account={24027460} />
|
||||
<GAnalytics account={'UA-60209057-1'} />
|
||||
<GAnalytics account={"UA-60209057-1"} />
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document"
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
{/* Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;900&display=swap"
|
||||
rel="preload"
|
||||
as="style"
|
||||
/>
|
||||
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
+8
-8
@@ -1,22 +1,22 @@
|
||||
import { NextPage } from 'next'
|
||||
import { Wrapper } from 'src/components/Wrapper'
|
||||
import { NotFound } from 'src/components/NotFound'
|
||||
import { Copyright } from 'src/components/Copyright'
|
||||
import { Block } from 'src/components/Block'
|
||||
import { Spacer } from 'src/components/Spacer'
|
||||
import { NextPage } from "next"
|
||||
import { Wrapper } from "src/components/Wrapper"
|
||||
import { NotFound } from "src/components/NotFound"
|
||||
import { Copyright } from "src/components/Copyright"
|
||||
import { Block } from "src/components/Block"
|
||||
import { Spacer } from "src/components/Spacer"
|
||||
|
||||
const Index: NextPage = () => (
|
||||
<Wrapper
|
||||
header={null}
|
||||
footer={(
|
||||
<Block as={'footer'} direction={'horizontal'}>
|
||||
<Block as={"footer"} direction={"horizontal"}>
|
||||
<Spacer />
|
||||
<Copyright />
|
||||
<Spacer />
|
||||
</Block>
|
||||
)}
|
||||
>
|
||||
<Block direction={'vertical'}>
|
||||
<Block direction={"vertical"}>
|
||||
<NotFound />
|
||||
<Spacer />
|
||||
</Block>
|
||||
|
||||
+16
-18
@@ -1,13 +1,13 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { PageGrid } from 'src/components/PageGrid'
|
||||
import { HudozkaTitle } from 'src/components/HudozkaTitle'
|
||||
import { MetaBuilder } from 'src/lib/meta'
|
||||
import { GetStaticProps, NextPage } from 'next'
|
||||
import { IMenu, IMeta, PageCardDto } from 'src/types'
|
||||
import { apiGet } from '@/next-lib'
|
||||
import { createHomeCards, createMenu } from '@/remote/factory'
|
||||
import Head from "next/head"
|
||||
import { App } from "src/components/App"
|
||||
import { Meta } from "src/components/Meta"
|
||||
import { PageGrid } from "src/components/PageGrid"
|
||||
import { HudozkaTitle } from "src/components/HudozkaTitle"
|
||||
import { MetaBuilder } from "src/lib/meta"
|
||||
import { GetStaticProps, NextPage } from "next"
|
||||
import { IMenu, IMeta, PageCardDto } from "src/types"
|
||||
import { apiGet } from "@/next-lib"
|
||||
import { createHomeCards, createMenu } from "@/remote/factory"
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
@@ -28,8 +28,8 @@ const Index: NextPage<Props> = props => (
|
||||
|
||||
<HudozkaTitle
|
||||
style={{
|
||||
marginTop: 'var(--size-m)',
|
||||
marginBottom: 'var(--size-m)',
|
||||
marginTop: "var(--size-m)",
|
||||
marginBottom: "var(--size-m)",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -40,16 +40,14 @@ const Index: NextPage<Props> = props => (
|
||||
)
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
const items = await apiGet(createHomeCards)
|
||||
('https://hudozka.tmshv.com/home', async () => [])
|
||||
const menu = await apiGet(createMenu)
|
||||
('https://hudozka.tmshv.com/menu', () => [])
|
||||
const items = await apiGet(createHomeCards)("https://hudozka.tmshv.com/home", async () => [])
|
||||
const menu = await apiGet(createMenu)("https://hudozka.tmshv.com/menu", () => [])
|
||||
|
||||
const title = 'Шлиссельбургская ДХШ'
|
||||
const title = "Шлиссельбургская ДХШ"
|
||||
const meta = (new MetaBuilder())
|
||||
.setTitle(title)
|
||||
.setData({
|
||||
url: '/',
|
||||
url: "/",
|
||||
})
|
||||
.build()
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import dynamic from 'next/dynamic'
|
||||
import { Wrapper } from '../Wrapper'
|
||||
import { ConfigContext } from 'src/context/ConfigContext'
|
||||
import config from 'src/config'
|
||||
import { Footer } from '../Footer'
|
||||
import { Content } from '../Content'
|
||||
import { Block } from '../Block'
|
||||
import { Breadcrumbs } from '../Breadcrumbs'
|
||||
import { IBreadcumbsPart, IMenu } from 'src/types'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useMobile } from 'src/hooks/useMobile'
|
||||
import { Gosuslugi } from '../Gosuslugi'
|
||||
import { INavigationProps } from '@/components/Navigation'
|
||||
import dynamic from "next/dynamic"
|
||||
import { Wrapper } from "../Wrapper"
|
||||
import { ConfigContext } from "src/context/ConfigContext"
|
||||
import config from "src/config"
|
||||
import { Footer } from "../Footer"
|
||||
import { Content } from "../Content"
|
||||
import { Block } from "../Block"
|
||||
import { Breadcrumbs } from "../Breadcrumbs"
|
||||
import { IBreadcumbsPart, IMenu } from "src/types"
|
||||
import { useRouter } from "next/router"
|
||||
import { useMobile } from "src/hooks/useMobile"
|
||||
import { Gosuslugi } from "../Gosuslugi"
|
||||
import { INavigationProps } from "@/components/Navigation"
|
||||
|
||||
const Navigation = dynamic<INavigationProps>(() => import('../Navigation').then(mod => mod.Navigation), {
|
||||
const Navigation = dynamic<INavigationProps>(() => import("../Navigation").then(mod => mod.Navigation), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export const App: React.FC<IAppProps> = ({ showAuthor = false, menu, ...props })
|
||||
const hideBreadcrumbs = useMobile()
|
||||
const router = useRouter()
|
||||
const blockStyle = {
|
||||
justifyContent: 'center'
|
||||
justifyContent: "center",
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -39,10 +39,10 @@ export const App: React.FC<IAppProps> = ({ showAuthor = false, menu, ...props })
|
||||
header={(
|
||||
<header>
|
||||
<Navigation style={{
|
||||
margin: '0 var(--size-m) var(--size-m)',
|
||||
margin: "0 var(--size-m) var(--size-m)",
|
||||
}} />
|
||||
{hideBreadcrumbs || !props.breadcrumbs ? null : (
|
||||
<Block direction={'horizontal'} style={blockStyle}>
|
||||
<Block direction={"horizontal"} style={blockStyle}>
|
||||
<Content>
|
||||
<Breadcrumbs
|
||||
items={props.breadcrumbs}
|
||||
@@ -58,7 +58,7 @@ export const App: React.FC<IAppProps> = ({ showAuthor = false, menu, ...props })
|
||||
showAuthor={showAuthor}
|
||||
>
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: "white",
|
||||
padding: 10,
|
||||
borderRadius: 10,
|
||||
marginBottom: 30,
|
||||
@@ -68,7 +68,7 @@ export const App: React.FC<IAppProps> = ({ showAuthor = false, menu, ...props })
|
||||
</Footer>
|
||||
)}
|
||||
>
|
||||
<Block direction={'horizontal'} style={blockStyle}>
|
||||
<Block direction={"horizontal"} style={blockStyle}>
|
||||
<Content style={props.contentStyle}>
|
||||
{props.children}
|
||||
</Content>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import { createElement } from 'react'
|
||||
import cx from "classnames"
|
||||
import { createElement } from "react"
|
||||
|
||||
export type BlockProps = {
|
||||
as?: keyof HTMLElementTagNameMap
|
||||
style?: React.CSSProperties
|
||||
direction: 'horizontal' | 'vertical'
|
||||
direction: "horizontal" | "vertical"
|
||||
}
|
||||
|
||||
export const Block: React.FC<BlockProps> = ({ as = 'div', style, direction, ...props }) => {
|
||||
const directionClass = direction === 'horizontal'
|
||||
export const Block: React.FC<BlockProps> = ({ as = "div", style, direction, ...props }) => {
|
||||
const directionClass = direction === "horizontal"
|
||||
? s.horizontal
|
||||
: s.vertical
|
||||
const newProps = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { IBreadcumbsPart } from 'src/types'
|
||||
import { Button } from '../Button'
|
||||
import { insertBetween } from 'src/lib/array'
|
||||
import { IBreadcumbsPart } from "src/types"
|
||||
import { Button } from "../Button"
|
||||
import { insertBetween } from "src/lib/array"
|
||||
|
||||
export type BreadcrumbsProps = {
|
||||
style?: React.CSSProperties
|
||||
@@ -11,15 +11,15 @@ export type BreadcrumbsProps = {
|
||||
}
|
||||
|
||||
export const Breadcrumbs: React.FC<BreadcrumbsProps> = props => {
|
||||
const items = insertBetween(props.items, () => '/')
|
||||
const items = insertBetween(props.items, () => "/")
|
||||
|
||||
return (
|
||||
<nav className={s.breadcrumbs} style={props.style}>
|
||||
{items.map((x, i) => {
|
||||
if (typeof x === 'string') {
|
||||
if (typeof x === "string") {
|
||||
return (
|
||||
<li key={i}>
|
||||
<span style={{ margin: '0 var(--size-xs)' }}>{x}</span>
|
||||
<span style={{ margin: "0 var(--size-xs)" }}>{x}</span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -28,8 +28,8 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = props => {
|
||||
<li key={x.href}>
|
||||
<Button
|
||||
href={x.href}
|
||||
theme={'ghost'}
|
||||
size={'small'}
|
||||
theme={"ghost"}
|
||||
size={"small"}
|
||||
disabled={x.href === props.path}
|
||||
>
|
||||
{x.name}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import Link from 'next/link'
|
||||
import cx from 'classnames'
|
||||
import { useCallback } from 'react'
|
||||
import Link from "next/link"
|
||||
import cx from "classnames"
|
||||
import { useCallback } from "react"
|
||||
|
||||
const sizeClass = {
|
||||
default: s.sizeDefault,
|
||||
@@ -10,7 +10,7 @@ const sizeClass = {
|
||||
}
|
||||
|
||||
const themeClass = {
|
||||
default: '',
|
||||
default: "",
|
||||
icon: s.themeIcon,
|
||||
ghost: s.themeGhost,
|
||||
}
|
||||
@@ -19,13 +19,13 @@ export type ButtonProps = {
|
||||
value?: any
|
||||
style?: React.CSSProperties
|
||||
href?: string
|
||||
theme?: 'default' | 'ghost' | 'icon'
|
||||
size?: 'default' | 'small'
|
||||
theme?: "default" | "ghost" | "icon"
|
||||
size?: "default" | "small"
|
||||
disabled?: boolean
|
||||
onClick?: (value: any, event: MouseEvent) => void
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({ size = 'default', theme = 'default', disabled = false, ...props }) => {
|
||||
export const Button: React.FC<ButtonProps> = ({ size = "default", theme = "default", disabled = false, ...props }) => {
|
||||
const className = cx(s.button, sizeClass[size], themeClass[theme], {
|
||||
[s.disabled]: disabled,
|
||||
})
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import cx from "classnames"
|
||||
import Link from "next/link"
|
||||
|
||||
const layoutClass = {
|
||||
simple: s.simple,
|
||||
featured: s.featured,
|
||||
}
|
||||
|
||||
export type CardLayout = 'simple' | 'featured'
|
||||
export type CardLayout = "simple" | "featured"
|
||||
export type CardProps = {
|
||||
style?: React.CSSProperties
|
||||
href: string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
export type CardGridProps = {
|
||||
style?: React.CSSProperties
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.strong {
|
||||
margin-bottom: var(--size-m);
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-bottom: var(--size-s);
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
import { useContext } from 'react'
|
||||
import { ConfigContext } from 'src/context/ConfigContext'
|
||||
import s from "./contacts.module.css"
|
||||
|
||||
import { useContext } from "react"
|
||||
import { ConfigContext } from "src/context/ConfigContext"
|
||||
import Link from "next/link"
|
||||
|
||||
export const Contacts: React.FC = props => {
|
||||
const { contacts } = useContext(ConfigContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx>{`
|
||||
strong {
|
||||
margin-bottom: var(--size-m);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: var(--size-s);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<strong>Контактная информация</strong>
|
||||
<strong className={s.strong}>Контактная информация</strong>
|
||||
|
||||
{contacts.map((x, i) => (
|
||||
<p key={i}><a href={x.value}>{x.title}</a></p>
|
||||
<p key={i} className={s.link}>
|
||||
<Link href={x.value}>
|
||||
<a >{x.title}</a>
|
||||
</Link>
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import cx from 'classnames'
|
||||
import { useReducedMotion } from 'src/hooks/useReducedMotion'
|
||||
import cx from "classnames"
|
||||
import { useReducedMotion } from "src/hooks/useReducedMotion"
|
||||
|
||||
export type ContentProps = {
|
||||
style?: React.CSSProperties
|
||||
@@ -10,8 +10,8 @@ export const Content: React.FC<ContentProps> = props => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx('content', {
|
||||
'reduced-motion': motionDisabled,
|
||||
className={cx("content", {
|
||||
"reduced-motion": motionDisabled,
|
||||
})}
|
||||
style={props.style}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext } from 'react'
|
||||
import { ConfigContext } from 'src/context/ConfigContext'
|
||||
import { useContext } from "react"
|
||||
import { ConfigContext } from "src/context/ConfigContext"
|
||||
|
||||
export const Copyright: React.FC = () => {
|
||||
const { yearStart, yearEnd } = useContext(ConfigContext)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'rc-tooltip/assets/bootstrap_white.css'
|
||||
import s from './signature.module.css'
|
||||
import "rc-tooltip/assets/bootstrap_white.css"
|
||||
import s from "./signature.module.css"
|
||||
|
||||
import { Sign } from '@/types'
|
||||
import { FiShield } from 'react-icons/fi'
|
||||
import Tooltip from 'rc-tooltip'
|
||||
import { Sign } from "@/types"
|
||||
import { FiShield } from "react-icons/fi"
|
||||
import Tooltip from "rc-tooltip"
|
||||
|
||||
export type SignatureProps = Sign
|
||||
export const Signature: React.FC<SignatureProps> = props => {
|
||||
@@ -17,8 +17,8 @@ export const Signature: React.FC<SignatureProps> = props => {
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip overlayClassName={'hudozka'} placement="left" trigger={['hover', 'click']} overlay={content}>
|
||||
<Tooltip overlayClassName={"hudozka"} placement="left" trigger={["hover", "click"]} overlay={content}>
|
||||
<FiShield className={s.secure} size={20} />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import s from './filecard.module.css'
|
||||
import s from "./filecard.module.css"
|
||||
|
||||
import Image from 'next/image'
|
||||
import { FileTokenData, Sign } from '@/types'
|
||||
import { size, ext } from '@/lib/file'
|
||||
import { getResizedUrl } from '@/lib/image'
|
||||
import { Signature } from './Signature'
|
||||
import Image from "next/image"
|
||||
import { FileTokenData, Sign } from "@/types"
|
||||
import { size, ext } from "@/lib/file"
|
||||
import { getResizedUrl } from "@/lib/image"
|
||||
import { Signature } from "./Signature"
|
||||
import Link from "next/link"
|
||||
|
||||
export type FileCardProps = FileTokenData & {
|
||||
sign?: Sign
|
||||
@@ -28,9 +29,10 @@ export const FileCard: React.FC<FileCardProps> = props => {
|
||||
})
|
||||
}}
|
||||
src={props.image_url}
|
||||
alt={""}
|
||||
width={100}
|
||||
height={100}
|
||||
objectFit={'contain'}
|
||||
objectFit={"contain"}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
@@ -45,9 +47,11 @@ export const FileCard: React.FC<FileCardProps> = props => {
|
||||
</div>
|
||||
|
||||
<div className={s.info}>
|
||||
<a href={props.file_url} target="_blank">
|
||||
{format} ({fileSize})
|
||||
</a>
|
||||
<Link href={props.file_url}>
|
||||
<a target="_blank" rel="noreferrer">
|
||||
{format} ({fileSize})
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { FileDefinition } from 'src/types'
|
||||
import { ext, size } from 'src/lib/file'
|
||||
import { FileDefinition } from "src/types"
|
||||
import { ext, size } from "src/lib/file"
|
||||
import Link from "next/link"
|
||||
|
||||
export type FileProps = FileDefinition & {
|
||||
style?: React.CSSProperties
|
||||
@@ -11,17 +12,21 @@ export const File: React.SFC<FileProps> = props => (
|
||||
<div className={s.file} style={props.style}>
|
||||
{!props.cover?.src ? null : (
|
||||
<div className={s.image}>
|
||||
<a href={props.file.src} target="_blank" className="invisible">
|
||||
<img src={props.cover.src} alt={props.name} />
|
||||
</a>
|
||||
<Link href={props.file.src} >
|
||||
<a target="_blank" rel="noreferrer" className="invisible">
|
||||
<img src={props.cover.src} alt={props.name} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={s.info}>
|
||||
<span>{props.name}</span>
|
||||
<a href={props.file.src} target="_blank">
|
||||
{ext(props.file.type)} ({size(props.file.size)})
|
||||
</a>
|
||||
<Link href={props.file.src} >
|
||||
<a target="_blank" rel="noreferrer">
|
||||
{ext(props.file.type)} ({size(props.file.size)})
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -1,8 +1,8 @@
|
||||
export const Author: React.FC = () => (
|
||||
<span>
|
||||
Разработка и поддержка сайта —
|
||||
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
|
||||
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
|
||||
www.tmshv.ru
|
||||
</a>
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
@@ -1,32 +1,32 @@
|
||||
import s from './footer.module.css'
|
||||
import s from "./footer.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import { Copyright } from '../Copyright'
|
||||
import { Author } from './Author'
|
||||
import { Block } from '../Block'
|
||||
import { Spacer } from '../Spacer'
|
||||
import { Contacts } from '../Contacts'
|
||||
import cx from "classnames"
|
||||
import { Copyright } from "../Copyright"
|
||||
import { Author } from "./Author"
|
||||
import { Block } from "../Block"
|
||||
import { Spacer } from "../Spacer"
|
||||
import { Contacts } from "../Contacts"
|
||||
|
||||
export interface IFooterProps {
|
||||
showAuthor: boolean
|
||||
}
|
||||
|
||||
export const Footer: React.FC<IFooterProps> = props => (
|
||||
<footer className={cx(s.footer, 'opposite')}>
|
||||
<Block direction={'vertical'} style={{
|
||||
marginBottom: 'var(--size-l)',
|
||||
<footer className={cx(s.footer, "opposite")}>
|
||||
<Block direction={"vertical"} style={{
|
||||
marginBottom: "var(--size-l)",
|
||||
}}>
|
||||
<Contacts />
|
||||
</Block>
|
||||
|
||||
{props.children}
|
||||
|
||||
<Block direction={'horizontal'}>
|
||||
<Block direction={"horizontal"}>
|
||||
<Copyright />
|
||||
|
||||
{!props.showAuthor ? null : (
|
||||
<>
|
||||
<Spacer as={'span'} />
|
||||
<Spacer as={"span"} />
|
||||
<Author />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
function script(account) {
|
||||
return `
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect } from 'react'
|
||||
import { getCode } from './lib'
|
||||
import { useEffect } from "react"
|
||||
import { getCode } from "./lib"
|
||||
|
||||
export type GosuslugiProps = {}
|
||||
|
||||
export const Gosuslugi: React.FC<GosuslugiProps> = () => {
|
||||
useEffect(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
window.dispatchEvent(new Event("resize"))
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -46,4 +46,4 @@ export const getCode = () => `
|
||||
})()
|
||||
</script>
|
||||
<script>Widget("https://pos.gosuslugi.ru/form", 312519)</script>
|
||||
`
|
||||
`
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createElement } from 'react'
|
||||
import { createElement } from "react"
|
||||
|
||||
export type HtmlProps = {
|
||||
className?: string
|
||||
@@ -6,11 +6,11 @@ export type HtmlProps = {
|
||||
html: string
|
||||
}
|
||||
|
||||
export const Html: React.FC<HtmlProps> = ({ as = 'div', ...props }) => {
|
||||
export const Html: React.FC<HtmlProps> = ({ as = "div", ...props }) => {
|
||||
return createElement(as, {
|
||||
className: props.className,
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: props.html,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import s from './title.module.css'
|
||||
import s from "./title.module.css"
|
||||
|
||||
import { memo } from 'react'
|
||||
import { memo } from "react"
|
||||
|
||||
export type HudozkaTitleProps = {
|
||||
style?: React.CSSProperties
|
||||
@@ -13,3 +13,5 @@ export const HudozkaTitle: React.FC<HudozkaTitleProps> = memo(props => (
|
||||
<span className={s.suffix}>детская художественная школа</span>
|
||||
</h1>
|
||||
))
|
||||
|
||||
HudozkaTitle.displayName = "HudozkaTitle"
|
||||
@@ -1,4 +1,4 @@
|
||||
import Link from 'next/link'
|
||||
import Link from "next/link"
|
||||
|
||||
export interface ActiveLinkProps {
|
||||
href?: string
|
||||
@@ -12,4 +12,4 @@ export const ActiveLink: React.FC<ActiveLinkProps> = props => props.href
|
||||
</Link>
|
||||
) : (
|
||||
<span style={props.activeStyle}>{props.children}</span>
|
||||
)
|
||||
)
|
||||
@@ -1,19 +1,19 @@
|
||||
import s from './styles.module.css'
|
||||
import cx from 'classnames'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ActiveLink } from './ActiveLink'
|
||||
import { isPartOfPath } from 'src/lib/url'
|
||||
import s from "./styles.module.css"
|
||||
import cx from "classnames"
|
||||
import { useRouter } from "next/router"
|
||||
import { ActiveLink } from "./ActiveLink"
|
||||
import { isPartOfPath } from "src/lib/url"
|
||||
|
||||
export type MenuItemProps = {
|
||||
href: string
|
||||
}
|
||||
|
||||
export const MenuItem: React.FC<MenuItemProps> = props => {
|
||||
const ignore = ['/']
|
||||
const ignore = ["/"]
|
||||
const router = useRouter()
|
||||
const current = router.asPath === props.href
|
||||
const selected = current || (!ignore.includes(props.href) && isPartOfPath(props.href, router.asPath))
|
||||
const href = current ? null : props.href
|
||||
const href = current ? undefined : props.href
|
||||
|
||||
return (
|
||||
<li
|
||||
@@ -24,7 +24,7 @@ export const MenuItem: React.FC<MenuItemProps> = props => {
|
||||
<ActiveLink
|
||||
href={href}
|
||||
activeStyle={{
|
||||
fontWeight: 'bold'
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import { MenuItem } from './MenuItem'
|
||||
import { IMenu } from 'src/types'
|
||||
import cx from "classnames"
|
||||
import { MenuItem } from "./MenuItem"
|
||||
import { IMenu } from "src/types"
|
||||
|
||||
const layoutClass = {
|
||||
desktop: s.desktop,
|
||||
@@ -10,7 +10,7 @@ const layoutClass = {
|
||||
}
|
||||
|
||||
export type MenuProps = {
|
||||
layout: 'desktop' | 'mobile'
|
||||
layout: "desktop" | "mobile"
|
||||
items: IMenu[]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import { Button } from '../Button'
|
||||
import cx from "classnames"
|
||||
import { Button } from "../Button"
|
||||
|
||||
export interface IMenuToggleProps {
|
||||
style?: React.CSSProperties
|
||||
@@ -14,7 +14,7 @@ export const MenuToggle: React.FC<IMenuToggleProps> = props => {
|
||||
<Button
|
||||
onClick={props.onClick}
|
||||
style={props.style}
|
||||
theme={'icon'}
|
||||
theme={"icon"}
|
||||
>
|
||||
<div
|
||||
className={cx(s.toggle, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IMeta } from 'src/types'
|
||||
import { IMeta } from "src/types"
|
||||
|
||||
export type MetaProps = {
|
||||
meta: IMeta
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { MenuToggle } from '../MenuToggle'
|
||||
import { useMobile } from 'src/hooks/useMobile'
|
||||
import { Menu } from '../Menu'
|
||||
import { Overlay } from '../Overlay'
|
||||
import { useState, useCallback, useContext } from 'react'
|
||||
import { ConfigContext } from 'src/context/ConfigContext'
|
||||
import { Block } from '../Block'
|
||||
import { Spacer } from '../Spacer'
|
||||
import { MenuToggle } from "../MenuToggle"
|
||||
import { useMobile } from "src/hooks/useMobile"
|
||||
import { Menu } from "../Menu"
|
||||
import { Overlay } from "../Overlay"
|
||||
import { useState, useCallback, useContext } from "react"
|
||||
import { ConfigContext } from "src/context/ConfigContext"
|
||||
import { Block } from "../Block"
|
||||
import { Spacer } from "../Spacer"
|
||||
|
||||
export interface INavigationProps {
|
||||
style?: React.CSSProperties
|
||||
@@ -28,24 +28,24 @@ export const Navigation: React.FC<INavigationProps> = props => {
|
||||
show={open}
|
||||
onClickOverlay={onClick}
|
||||
>
|
||||
<Block direction={'horizontal'}>
|
||||
<Block direction={"horizontal"}>
|
||||
<Spacer />
|
||||
<MenuToggle
|
||||
open={open}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
margin: 'var(--size-xs)'
|
||||
margin: "var(--size-xs)",
|
||||
}}
|
||||
/>
|
||||
</Block>
|
||||
<Block direction={'horizontal'} style={{
|
||||
justifyContent: 'center',
|
||||
<Block direction={"horizontal"} style={{
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<nav style={{
|
||||
width: '75%',
|
||||
width: "75%",
|
||||
}}>
|
||||
<Menu
|
||||
layout={'mobile'}
|
||||
layout={"mobile"}
|
||||
items={menu}
|
||||
/>
|
||||
</nav>
|
||||
@@ -53,24 +53,24 @@ export const Navigation: React.FC<INavigationProps> = props => {
|
||||
</Overlay>
|
||||
|
||||
{collapseMenu ? (
|
||||
<Block direction={'horizontal'}>
|
||||
<Block direction={"horizontal"}>
|
||||
<Spacer />
|
||||
<MenuToggle
|
||||
open={open}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
margin: 'var(--size-xs)'
|
||||
margin: "var(--size-xs)",
|
||||
}}
|
||||
/>
|
||||
</Block>
|
||||
) : (
|
||||
<nav style={props.style}>
|
||||
<Menu
|
||||
layout={'desktop'}
|
||||
items={menu}
|
||||
/>
|
||||
</nav>
|
||||
)}
|
||||
<nav style={props.style}>
|
||||
<Menu
|
||||
layout={"desktop"}
|
||||
items={menu}
|
||||
/>
|
||||
</nav>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.section {
|
||||
margin-top: var(--size-l);
|
||||
margin-bottom: var(--size-l);
|
||||
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.section h1 {
|
||||
font-size: 16pt;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.section img {
|
||||
margin-top: var(--size-m);
|
||||
margin-bottom: var(--size-m);
|
||||
max-width: 400px;
|
||||
}
|
||||
@@ -1,34 +1,18 @@
|
||||
import s from "./error.module.css"
|
||||
|
||||
import Link from "next/link"
|
||||
|
||||
export const NotFound: React.FC = () => (
|
||||
<section>
|
||||
<style jsx>{`
|
||||
section {
|
||||
margin-top: var(--size-l);
|
||||
margin-bottom: var(--size-l);
|
||||
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 16pt;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-top: var(--size-m);
|
||||
margin-bottom: var(--size-m);
|
||||
max-width: 400px;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<section className={s.section}>
|
||||
<div>
|
||||
<h1>Страница не найдена.</h1>
|
||||
<h1>Страница не найдена</h1>
|
||||
<img src="/static/graphics/skull.jpg" />
|
||||
</div>
|
||||
|
||||
<div><a href="/">На главную</a></div>
|
||||
<div>
|
||||
<Link href={"/"}>
|
||||
<a>На главную</a>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import * as React from "react"
|
||||
|
||||
export interface ITransitionProps {
|
||||
duration: number
|
||||
@@ -16,7 +16,8 @@ export const Transition: React.FC<ITransitionProps> = props => {
|
||||
}, [opacity])
|
||||
const transition = React.useMemo(
|
||||
() => `all ${props.duration}ms`,
|
||||
[props.duration])
|
||||
[props.duration]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!props.show) {
|
||||
@@ -34,7 +35,11 @@ export const Transition: React.FC<ITransitionProps> = props => {
|
||||
}
|
||||
}, [props.show])
|
||||
|
||||
return mounted && (
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...props.extraStyle,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { Transition } from './Transition'
|
||||
import { useReducedMotion } from '../../hooks/useReducedMotion'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { Transition } from "./Transition"
|
||||
import { useReducedMotion } from "../../hooks/useReducedMotion"
|
||||
import { useCallback, useEffect } from "react"
|
||||
|
||||
export interface IOverlayProps {
|
||||
show: boolean
|
||||
@@ -21,9 +21,9 @@ export const Overlay: React.FC<IOverlayProps> = props => {
|
||||
|
||||
useEffect(() => {
|
||||
if (props.show) {
|
||||
document.body.classList.add('noscroll')
|
||||
document.body.classList.add("noscroll")
|
||||
} else {
|
||||
document.body.classList.remove('noscroll')
|
||||
document.body.classList.remove("noscroll")
|
||||
}
|
||||
}, [props.show])
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { PageMeta } from '../PageMeta'
|
||||
import { ITag } from 'src/types'
|
||||
import { PageMeta } from "../PageMeta"
|
||||
import { ITag } from "src/types"
|
||||
|
||||
export type PageProps = {
|
||||
style?: React.CSSProperties
|
||||
@@ -15,7 +15,7 @@ export const Page: React.FC<PageProps> = props => (
|
||||
|
||||
<PageMeta
|
||||
style={{
|
||||
marginTop: 'var(--size-m)',
|
||||
marginTop: "var(--size-m)",
|
||||
}}
|
||||
date={props.date}
|
||||
tags={props.tags}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import s from './date.module.css'
|
||||
import s from "./date.module.css"
|
||||
|
||||
import { dateFormat } from 'src/lib/date'
|
||||
import { dateFormat } from "src/lib/date"
|
||||
|
||||
export type DateProps = {
|
||||
style?: React.CSSProperties
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Image from 'next/image'
|
||||
import { PageCardDto } from '@/types'
|
||||
import { CardGrid } from '../CardGrid'
|
||||
import { Card, CardLayout } from '../Card'
|
||||
import { Date } from './Date'
|
||||
import { Block } from '../Block'
|
||||
import { Spacer } from '../Spacer'
|
||||
import Image from "next/image"
|
||||
import { PageCardDto } from "@/types"
|
||||
import { CardGrid } from "../CardGrid"
|
||||
import { Card, CardLayout } from "../Card"
|
||||
import { Date } from "./Date"
|
||||
import { Block } from "../Block"
|
||||
import { Spacer } from "../Spacer"
|
||||
|
||||
export type PageGridProps = {
|
||||
items: PageCardDto[]
|
||||
@@ -14,23 +14,23 @@ export const PageGrid: React.FC<PageGridProps> = props => {
|
||||
return (
|
||||
<CardGrid
|
||||
style={{
|
||||
marginBottom: 'var(--size-xl)',
|
||||
marginBottom: "var(--size-xl)",
|
||||
}}
|
||||
>
|
||||
{props.items.map(item => {
|
||||
const gridColumn = item.featured ? 'span 2' : 'auto'
|
||||
const layout: CardLayout = item.featured ? 'featured' : 'simple'
|
||||
const gridColumn = item.featured ? "span 2" : "auto"
|
||||
const layout: CardLayout = item.featured ? "featured" : "simple"
|
||||
|
||||
const content = !item.featured
|
||||
? (
|
||||
<Block
|
||||
direction={'vertical'}
|
||||
direction={"vertical"}
|
||||
>
|
||||
{item.title}
|
||||
<Spacer />
|
||||
{!item.date ? null : (
|
||||
<Date style={{
|
||||
marginTop: 'var(--size-m)',
|
||||
marginTop: "var(--size-m)",
|
||||
}}>{item.date}</Date>
|
||||
)}
|
||||
</Block>
|
||||
@@ -44,11 +44,11 @@ export const PageGrid: React.FC<PageGridProps> = props => {
|
||||
href={item.url}
|
||||
cover={(
|
||||
<Image
|
||||
layout={'fill'}
|
||||
objectFit={'cover'}
|
||||
layout={"fill"}
|
||||
objectFit={"cover"}
|
||||
src={item.cover.src}
|
||||
alt={item.cover.alt}
|
||||
placeholder={'blur'}
|
||||
placeholder={"blur"}
|
||||
blurDataURL={item.cover.blur}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import s from './styles.module.css'
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import { dateFormat } from 'src/lib/date'
|
||||
import { ITag } from 'src/types'
|
||||
import { TagList, Direction } from '../TagList'
|
||||
import { useMobile } from 'src/hooks/useMobile'
|
||||
import cx from "classnames"
|
||||
import { dateFormat } from "src/lib/date"
|
||||
import { ITag } from "src/types"
|
||||
import { TagList, Direction } from "../TagList"
|
||||
import { useMobile } from "src/hooks/useMobile"
|
||||
|
||||
export type PageMetaProps = {
|
||||
style?: React.CSSProperties
|
||||
@@ -14,15 +14,15 @@ export type PageMetaProps = {
|
||||
|
||||
export const PageMeta: React.FC<PageMetaProps> = props => {
|
||||
const mobile = useMobile()
|
||||
const center = mobile ? null : 'center'
|
||||
const direction: Direction = mobile ? 'vertical' : 'horizontal'
|
||||
const center = mobile ? null : "center"
|
||||
const direction: Direction = mobile ? "vertical" : "horizontal"
|
||||
|
||||
return (
|
||||
<div className={cx(s.pageMeta, {
|
||||
[s.center]: center,
|
||||
})} style={props.style}>
|
||||
{!props.date ? null : (
|
||||
<p className={'date'}>Опубликовано {dateFormat(props.date)}</p>
|
||||
<p className={"date"}>Опубликовано {dateFormat(props.date)}</p>
|
||||
)}
|
||||
|
||||
{!(props.tags?.length) ? null : (
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Block, BlockProps } from '../Block'
|
||||
import { Button } from '../Button'
|
||||
import { Spacer } from '../Spacer'
|
||||
import { Block, BlockProps } from "../Block"
|
||||
import { Button } from "../Button"
|
||||
import { Spacer } from "../Spacer"
|
||||
|
||||
export type PagerProps = Omit<BlockProps, 'direction'> & {
|
||||
export type PagerProps = Omit<BlockProps, "direction"> & {
|
||||
nextHref: string | null
|
||||
prevHref: string | null
|
||||
}
|
||||
|
||||
export const Pager: React.FC<PagerProps> = ({ prevHref, nextHref, ...props }) => (
|
||||
<Block {...props} direction={'horizontal'}>
|
||||
<Block {...props} direction={"horizontal"}>
|
||||
{!prevHref ? null : (
|
||||
<Button href={prevHref}>Предыдущая страница</Button>
|
||||
)}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import s from './picture.module.css'
|
||||
import s from "./picture.module.css"
|
||||
|
||||
import { memo } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { Pic } from '@/types'
|
||||
import { memo } from "react"
|
||||
import Image from "next/image"
|
||||
import { Pic } from "@/types"
|
||||
|
||||
const modifiers = new Map([
|
||||
['square', s.square],
|
||||
['vertical', s.vertical],
|
||||
['horizontal', ''],
|
||||
["square", s.square],
|
||||
["vertical", s.vertical],
|
||||
["horizontal", ""],
|
||||
])
|
||||
|
||||
function size(w: number, h: number, tolerance: number) {
|
||||
const r = w / h
|
||||
if (r < 1 && r > tolerance) {
|
||||
return 'square'
|
||||
return "square"
|
||||
}
|
||||
|
||||
if (w < h) {
|
||||
return 'vertical'
|
||||
return "vertical"
|
||||
}
|
||||
|
||||
return 'horizontal'
|
||||
return "horizontal"
|
||||
}
|
||||
|
||||
export type PictureProps = Pic & {
|
||||
@@ -31,19 +31,19 @@ export type PictureProps = Pic & {
|
||||
export const Picture: React.FC<PictureProps> = memo(({ wide = false, ...props }) => {
|
||||
const ss = size(props.width, props.height, 0.95)
|
||||
const layout = modifiers.get(ss)
|
||||
const w = ss === 'horizontal' ? wide : false
|
||||
const w = ss === "horizontal" ? wide : false
|
||||
|
||||
return (
|
||||
<figure className={`${s.pic} ${layout} ${w ? s.wide : ''}`}>
|
||||
<figure className={`${s.pic} ${layout} ${w ? s.wide : ""}`}>
|
||||
<Image
|
||||
className={s.img}
|
||||
src={props.src}
|
||||
alt={props.alt}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
layout={'responsive'}
|
||||
layout={"responsive"}
|
||||
blurDataURL={props.blur}
|
||||
placeholder={'blur'}
|
||||
placeholder={"blur"}
|
||||
/>
|
||||
{!props.caption ? null : (
|
||||
<figcaption>{props.caption}</figcaption>
|
||||
@@ -51,3 +51,5 @@ export const Picture: React.FC<PictureProps> = memo(({ wide = false, ...props })
|
||||
</figure>
|
||||
)
|
||||
})
|
||||
|
||||
Picture.displayName = "Picture"
|
||||
@@ -1,10 +1,8 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export const Share: React.FC = () => (
|
||||
<div className="likely">
|
||||
<div className="vkontakte">Поделиться</div>
|
||||
<div className="facebook">Поделиться</div>
|
||||
<div className="telegram">Отправить</div>
|
||||
<div className="twitter">Твитнуть</div>
|
||||
</div>
|
||||
<div className="likely">
|
||||
<div className="vkontakte">Поделиться</div>
|
||||
<div className="facebook">Поделиться</div>
|
||||
<div className="telegram">Отправить</div>
|
||||
<div className="twitter">Твитнуть</div>
|
||||
</div>
|
||||
)
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createElement } from 'react'
|
||||
import { createElement } from "react"
|
||||
|
||||
export type SpacerProps = {
|
||||
as?: keyof HTMLElementTagNameMap
|
||||
}
|
||||
|
||||
export const Spacer: React.FC<SpacerProps> = ({ as = 'div' }) => createElement(as, {
|
||||
export const Spacer: React.FC<SpacerProps> = ({ as = "div" }) => createElement(as, {
|
||||
style: {
|
||||
flex: 1,
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import s from './wrapper.module.css'
|
||||
import s from "./wrapper.module.css"
|
||||
|
||||
import cx from 'classnames'
|
||||
import cx from "classnames"
|
||||
|
||||
export type WrapperProps = {
|
||||
header: React.ReactNode
|
||||
@@ -10,7 +10,7 @@ export type WrapperProps = {
|
||||
}
|
||||
|
||||
export const Wrapper: React.FC<WrapperProps> = props => (
|
||||
<div className={cx(s.container, 'theme-default')} style={props.style}>
|
||||
<div className={cx(s.container, "theme-default")} style={props.style}>
|
||||
{props.header}
|
||||
|
||||
<main className={s.main} style={props.mainStyle}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
function script(account) {
|
||||
return `
|
||||
|
||||
@@ -4,7 +4,7 @@ export type YoutubeProps = {
|
||||
|
||||
export const Youtube: React.FC<{ url: string }> = props => {
|
||||
const url = new URL(props.url)
|
||||
const videoId = url.searchParams.get('v')
|
||||
const videoId = url.searchParams.get("v")
|
||||
if (!videoId) {
|
||||
return null
|
||||
}
|
||||
|
||||
+40
-38
@@ -1,72 +1,74 @@
|
||||
const currentYear = () => (new Date()).getFullYear()
|
||||
|
||||
export default {
|
||||
const config = {
|
||||
yearStart: 2012,
|
||||
yearEnd: currentYear(),
|
||||
contacts: [
|
||||
{
|
||||
type: 'link',
|
||||
value: 'https://yandex.ru/maps/-/CKUkAXIb',
|
||||
title: 'г. Шлиссельбург ул. 18 января д. 3',
|
||||
type: "link",
|
||||
value: "https://yandex.ru/maps/-/CKUkAXIb",
|
||||
title: "г. Шлиссельбург ул. 18 января д. 3",
|
||||
},
|
||||
{
|
||||
type: 'tel',
|
||||
value: 'tel:+78136276312',
|
||||
title: '+7 (81362) 76-312',
|
||||
type: "tel",
|
||||
value: "tel:+78136276312",
|
||||
title: "+7 (81362) 76-312",
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
value: 'mailto:hudozka@gmail.com',
|
||||
title: 'hudozka@gmail.com',
|
||||
type: "email",
|
||||
value: "mailto:hudozka@gmail.com",
|
||||
title: "hudozka@gmail.com",
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
value: 'https://clck.ru/H83zS',
|
||||
title: 'Навигатор дополнительного образования ЛО',
|
||||
type: "link",
|
||||
value: "https://clck.ru/H83zS",
|
||||
title: "Навигатор дополнительного образования ЛО",
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
value: 'https://vk.com/shlisselburghudozka',
|
||||
title: 'Вконтакте'
|
||||
type: "link",
|
||||
value: "https://vk.com/shlisselburghudozka",
|
||||
title: "Вконтакте",
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
value: 'https://www.instagram.com/hudozka',
|
||||
title: 'Инстаграм'
|
||||
}
|
||||
type: "link",
|
||||
value: "https://www.instagram.com/hudozka",
|
||||
title: "Инстаграм",
|
||||
},
|
||||
],
|
||||
menu: [
|
||||
{
|
||||
name: 'Школа',
|
||||
href: '/',
|
||||
name: "Школа",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
name: 'Поступление',
|
||||
href: '/join',
|
||||
name: "Поступление",
|
||||
href: "/join",
|
||||
},
|
||||
{
|
||||
name: 'Предметы',
|
||||
href: '/courses',
|
||||
name: "Предметы",
|
||||
href: "/courses",
|
||||
},
|
||||
{
|
||||
name: 'Коллектив',
|
||||
href: '/collective',
|
||||
name: "Коллектив",
|
||||
href: "/collective",
|
||||
},
|
||||
{
|
||||
name: '«Мы соседи»',
|
||||
href: '/neighbors',
|
||||
name: "«Мы соседи»",
|
||||
href: "/neighbors",
|
||||
},
|
||||
{
|
||||
name: 'Документы',
|
||||
href: '/documents',
|
||||
name: "Документы",
|
||||
href: "/documents",
|
||||
},
|
||||
{
|
||||
name: 'Сведения об образовательной организации',
|
||||
href: '/info',
|
||||
name: "Сведения об образовательной организации",
|
||||
href: "/info",
|
||||
},
|
||||
{
|
||||
name: 'Дистанционное образование',
|
||||
href: '/info/online-education',
|
||||
}
|
||||
]
|
||||
name: "Дистанционное образование",
|
||||
href: "/info/online-education",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext } from 'react'
|
||||
import config from 'src/config'
|
||||
import { createContext } from "react"
|
||||
import config from "src/config"
|
||||
|
||||
export const ConfigContext = createContext(config)
|
||||
@@ -1,5 +1,5 @@
|
||||
import useMedia from 'use-media'
|
||||
import useMedia from "use-media"
|
||||
|
||||
export function useMobile(): boolean {
|
||||
return useMedia('(max-width: 31.25em)', false)
|
||||
return useMedia("(max-width: 31.25em)", false)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import useMedia from 'use-media'
|
||||
import useMedia from "use-media"
|
||||
|
||||
export function useReducedMotion(): boolean {
|
||||
return useMedia('(prefers-reduced-motion: reduce)')
|
||||
return useMedia("(prefers-reduced-motion: reduce)")
|
||||
}
|
||||
+3
-12
@@ -1,16 +1,7 @@
|
||||
export function splitBy(fn) {
|
||||
return items => {
|
||||
return items.reduce((acc, item) => {
|
||||
const key = fn(item)
|
||||
const items = acc.get(key) || []
|
||||
export type InsertBetweenFunction<T> = (i: number) => T
|
||||
|
||||
return acc.set(key, [...items, item])
|
||||
}, new Map())
|
||||
}
|
||||
}
|
||||
|
||||
export function insertBetween<T, I = T>(items: T[], fn: (i: number) => I): Array<T | I> {
|
||||
return items.reduce((acc, x, i) => {
|
||||
export function insertBetween<T, I = T>(items: T[], fn: InsertBetweenFunction<I>): Array<T | I> {
|
||||
return items.reduce<Array<T | I>>((acc, x, i) => {
|
||||
acc.push(x)
|
||||
|
||||
if (i < items.length - 1) {
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import format from 'date-fns/format'
|
||||
import { ru } from 'date-fns/locale'
|
||||
import format from "date-fns/format"
|
||||
import { ru } from "date-fns/locale"
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -8,8 +8,8 @@ import { ru } from 'date-fns/locale'
|
||||
* @param date
|
||||
*/
|
||||
export const dateFormat = (date: string | Date) => {
|
||||
const value = typeof date === 'string' ? new Date(date) : date
|
||||
const value = typeof date === "string" ? new Date(date) : date
|
||||
return format(value, "d MMMM yyyy", {
|
||||
locale: ru
|
||||
locale: ru,
|
||||
})
|
||||
}
|
||||
+6
-6
@@ -1,21 +1,21 @@
|
||||
const units = ['байт', 'КБ', 'МБ', 'ГБ', 'ТБ', 'ПБ']
|
||||
const units = ["байт", "КБ", "МБ", "ГБ", "ТБ", "ПБ"]
|
||||
|
||||
const mime = new Map([
|
||||
['application/pdf', 'ПДФ'],
|
||||
['image/jpeg', 'ЖПГ'],
|
||||
['image/png', 'ПНГ'],
|
||||
["application/pdf", "ПДФ"],
|
||||
["image/jpeg", "ЖПГ"],
|
||||
["image/png", "ПНГ"],
|
||||
])
|
||||
|
||||
export function ext(mimeType: string): string {
|
||||
if (mime.has(mimeType)) {
|
||||
return mime.get(mimeType)
|
||||
return mime.get(mimeType)!
|
||||
}
|
||||
|
||||
return mimeType
|
||||
}
|
||||
|
||||
export function size(bytes: any, precision = 1): string {
|
||||
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return ''
|
||||
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return ""
|
||||
|
||||
const number = Math.floor(Math.log(bytes) / Math.log(1024))
|
||||
const size = (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision)
|
||||
|
||||
@@ -6,9 +6,6 @@ export type ResizeOptions = {
|
||||
|
||||
export function getResizedUrl(src: string, options: ResizeOptions): string {
|
||||
const n = options.n ?? 1
|
||||
if (!src || src === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
return `https://images.weserv.nl/?url=${src}&w=${options.width}&h=${options.height}&n=${n}`
|
||||
}
|
||||
+25
-18
@@ -1,23 +1,23 @@
|
||||
import { IMeta, Pic } from 'src/types'
|
||||
import { IMeta, Pic } from "src/types"
|
||||
|
||||
export function meta(props: Partial<IMeta>): IMeta {
|
||||
const path = props.url || '/'
|
||||
const path = props.url || "/"
|
||||
const url = `https://art.shlisselburg.org${path}`
|
||||
|
||||
return {
|
||||
title: 'Шлиссельбургская ДХШ',
|
||||
description: 'Сайт Шлиссельбургской художественной школы',
|
||||
image: 'https://art.shlisselburg.org/entrance.jpg',
|
||||
title: "Шлиссельбургская ДХШ",
|
||||
description: "Сайт Шлиссельбургской художественной школы",
|
||||
image: "https://art.shlisselburg.org/entrance.jpg",
|
||||
imageWidth: 1200,
|
||||
imageHeight: 630,
|
||||
|
||||
siteName: 'Шлиссельбургская Детская Художественная Школа',
|
||||
locale: 'ru_RU',
|
||||
type: 'website',
|
||||
domain: 'art.shlisselburg.org',
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterSite: '@',
|
||||
twitterCreator: '@tmshv',
|
||||
siteName: "Шлиссельбургская Детская Художественная Школа",
|
||||
locale: "ru_RU",
|
||||
type: "website",
|
||||
domain: "art.shlisselburg.org",
|
||||
twitterCard: "summary_large_image",
|
||||
twitterSite: "@",
|
||||
twitterCreator: "@tmshv",
|
||||
|
||||
...props,
|
||||
url,
|
||||
@@ -25,10 +25,10 @@ export function meta(props: Partial<IMeta>): IMeta {
|
||||
}
|
||||
|
||||
export class MetaBuilder {
|
||||
private image: Pic
|
||||
private title: string
|
||||
private description: string
|
||||
private data: Partial<IMeta>
|
||||
private image?: Pic
|
||||
private title?: string
|
||||
private description?: string
|
||||
private data?: Partial<IMeta>
|
||||
|
||||
setData(param: Partial<IMeta>) {
|
||||
this.data = param
|
||||
@@ -53,11 +53,18 @@ export class MetaBuilder {
|
||||
build() {
|
||||
const image = this.buildImage()
|
||||
|
||||
const params: Record<string, string> = {}
|
||||
if (this.title) {
|
||||
params.title = this.title
|
||||
}
|
||||
if (this.description) {
|
||||
params.description = this.description
|
||||
}
|
||||
|
||||
return meta({
|
||||
...this.data,
|
||||
...image,
|
||||
title: this.title ?? null,
|
||||
description: this.description ?? null,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
export function titleCase(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import Typograf from 'typograf'
|
||||
import MarkdownIt from "markdown-it"
|
||||
import Typograf from "typograf"
|
||||
|
||||
const tp = new Typograf({ locale: ['ru'] })
|
||||
const tp = new Typograf({ locale: ["ru"] })
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
})
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@ export function isPartOfPath(part: string, path: string): boolean {
|
||||
|
||||
export function queryList(key: string, values: string[]): string {
|
||||
if (values.length === 0) {
|
||||
return ''
|
||||
return ""
|
||||
}
|
||||
|
||||
return values
|
||||
.map(value => `${key}=${value}`)
|
||||
.join('&')
|
||||
.join("&")
|
||||
}
|
||||
+3
-18
@@ -1,5 +1,4 @@
|
||||
import axios from 'axios'
|
||||
import { NextPageContext } from 'next'
|
||||
import { NextPageContext } from "next"
|
||||
|
||||
export interface IResponseItems<T> {
|
||||
items: T[]
|
||||
@@ -7,16 +6,6 @@ export interface IResponseItems<T> {
|
||||
prevPage: number
|
||||
}
|
||||
|
||||
export async function requestGet<T>(url: string, defaultResponse: T): Promise<T> {
|
||||
try {
|
||||
const res = await axios.get<T>(url)
|
||||
|
||||
return res.data
|
||||
} catch (e) {
|
||||
return defaultResponse
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This wrapper prevents fetching api request in static export mode and production environment
|
||||
* because nextjs call getInitialProps on the client side
|
||||
@@ -25,18 +14,14 @@ export async function requestGet<T>(url: string, defaultResponse: T): Promise<T>
|
||||
*/
|
||||
export function wrapInitialProps(fn: (ctx: NextPageContext) => void) {
|
||||
return async (ctx: NextPageContext) => {
|
||||
if (process.env.NODE_ENV === 'production' && process.browser) {
|
||||
return window['__NEXT_DATA__'].props.pageProps;
|
||||
if (process.env.NODE_ENV === "production" && process.browser) {
|
||||
return window["__NEXT_DATA__"].props.pageProps
|
||||
}
|
||||
|
||||
return fn(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
export function createApiUrl(path: string) {
|
||||
return `https://api.tmshv.com/hudozka${path}`
|
||||
}
|
||||
|
||||
export type FactoryFunction<I, O> =
|
||||
| ((response: I) => O)
|
||||
| ((response: I) => Promise<O>)
|
||||
|
||||
+63
-63
@@ -1,8 +1,8 @@
|
||||
import { IMenu, IPage, ITag, PageCardDto, Pic, Token } from "@/types";
|
||||
import { asItem } from "./lib";
|
||||
import { StrapiComponentEmbed, StrapiComponent, StrapiHome, StrapiPage, StrapiPageCard, StrapiTag, StrapiMedia, StrapiMenu } from "./types";
|
||||
import { typograf, markdownToHtml } from 'src/lib/text'
|
||||
import { encodeImageToBlurhash } from "./image";
|
||||
import { IMenu, IPage, ITag, PageCardDto, Pic, Token } from "@/types"
|
||||
import { asItem } from "./lib"
|
||||
import { StrapiComponentEmbed, StrapiComponent, StrapiHome, StrapiPage, StrapiPageCard, StrapiTag, StrapiMedia, StrapiMenu } from "./types"
|
||||
import { typograf, markdownToHtml } from "src/lib/text"
|
||||
import { encodeImageToBlurhash } from "./image"
|
||||
|
||||
const md = (text: string) => typograf(markdownToHtml(text))
|
||||
|
||||
@@ -19,79 +19,79 @@ export function isYoutubeUrl(url: string): boolean {
|
||||
export function createEmbed(component: StrapiComponentEmbed): Token {
|
||||
if (isYoutubeUrl(component.src)) {
|
||||
return {
|
||||
token: 'youtube',
|
||||
token: "youtube",
|
||||
data: {
|
||||
url: component.src,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token: 'html',
|
||||
data: `<iframe src="${component.src}" width="100%" height="480" frameborder="0"></iframe>`
|
||||
token: "html",
|
||||
data: `<iframe src="${component.src}" width="100%" height="480" frameborder="0"></iframe>`,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createPageToken(component: StrapiComponent): Promise<Token> {
|
||||
switch (component.__component) {
|
||||
case 'hudozka.text': {
|
||||
return {
|
||||
token: 'html',
|
||||
data: md(component.text),
|
||||
}
|
||||
case "hudozka.text": {
|
||||
return {
|
||||
token: "html",
|
||||
data: md(component.text),
|
||||
}
|
||||
}
|
||||
|
||||
case 'hudozka.image': {
|
||||
const data = await createPicFromMedia(component.media, {
|
||||
alternativeText: component.caption,
|
||||
caption: component.caption,
|
||||
})
|
||||
case "hudozka.image": {
|
||||
const data = await createPicFromMedia(component.media, {
|
||||
alternativeText: component.caption,
|
||||
caption: component.caption,
|
||||
})
|
||||
|
||||
return {
|
||||
token: 'image',
|
||||
wide: component.wide,
|
||||
data,
|
||||
}
|
||||
return {
|
||||
token: "image",
|
||||
wide: component.wide,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
case 'hudozka.document': {
|
||||
return {
|
||||
token: 'file',
|
||||
data: {
|
||||
url: component.media.url,
|
||||
slug: 'jopa',
|
||||
image_url: component.media.url,
|
||||
file_url: component.media.url,
|
||||
title: component.title,
|
||||
file_size: component.media.size * 1000,
|
||||
// file_size: component.media.size,
|
||||
file_format: component.media.mime,
|
||||
}
|
||||
}
|
||||
case "hudozka.document": {
|
||||
return {
|
||||
token: "file",
|
||||
data: {
|
||||
url: component.media.url,
|
||||
slug: "jopa",
|
||||
image_url: component.media.url,
|
||||
file_url: component.media.url,
|
||||
title: component.title,
|
||||
file_size: component.media.size * 1000,
|
||||
// file_size: component.media.size,
|
||||
file_format: component.media.mime,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case 'hudozka.embed': {
|
||||
return createEmbed(component)
|
||||
case "hudozka.embed": {
|
||||
return createEmbed(component)
|
||||
}
|
||||
|
||||
case "hudozka.card-grid": {
|
||||
const items = await Promise.all(component.items
|
||||
.filter(Boolean)
|
||||
.map(createCardGrid)
|
||||
)
|
||||
return {
|
||||
token: "grid",
|
||||
data: {
|
||||
items,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case 'hudozka.card-grid': {
|
||||
const items = await Promise.all(component.items
|
||||
.filter(Boolean)
|
||||
.map(createCardGrid)
|
||||
)
|
||||
return {
|
||||
token: 'grid',
|
||||
data: {
|
||||
items,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return {
|
||||
token: "text",
|
||||
data: ` ${JSON.stringify(component)}`,
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
token: 'text',
|
||||
data: ` ${JSON.stringify(component)}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export function createMenu(res: StrapiMenu): IMenu[] {
|
||||
}))
|
||||
|
||||
return [{
|
||||
href: '/',
|
||||
href: "/",
|
||||
name: res.homeLabel,
|
||||
}, ...items]
|
||||
}
|
||||
@@ -130,12 +130,12 @@ export async function createPage(res: StrapiPage | StrapiPage[]): Promise<IPage
|
||||
id: `${item.id}`,
|
||||
url: item.slug,
|
||||
|
||||
data: '',
|
||||
date: '',
|
||||
data: "",
|
||||
date: "",
|
||||
cover,
|
||||
tokens: [
|
||||
{
|
||||
token: 'html',
|
||||
token: "html",
|
||||
data: md(`# ${item.title}`),
|
||||
},
|
||||
|
||||
@@ -148,7 +148,7 @@ export async function createPage(res: StrapiPage | StrapiPage[]): Promise<IPage
|
||||
}
|
||||
|
||||
function isCardFeatured(card: StrapiPageCard): boolean {
|
||||
return card.layout === 'big' || card.layout === 'medium'
|
||||
return card.layout === "big" || card.layout === "medium"
|
||||
}
|
||||
|
||||
export async function getCoverImage(media?: StrapiMedia): Promise<Pic> {
|
||||
@@ -157,7 +157,7 @@ export async function getCoverImage(media?: StrapiMedia): Promise<Pic> {
|
||||
}
|
||||
|
||||
return {
|
||||
src: 'https://hudozkacdn.tmshv.com/main_fad9fdf29a.jpg',
|
||||
src: "https://hudozkacdn.tmshv.com/main_fad9fdf29a.jpg",
|
||||
width: 1920,
|
||||
height: 1858,
|
||||
// alt: '',
|
||||
|
||||
+18
-8
@@ -1,20 +1,30 @@
|
||||
import sharp from "sharp"
|
||||
|
||||
export async function encodeImageToBlurhash(url: string) {
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) {
|
||||
async function fetchBuffer(url: string) {
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) {
|
||||
return undefined
|
||||
}
|
||||
const data = await res.arrayBuffer()
|
||||
return Buffer.from(data)
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const data = await res.arrayBuffer()
|
||||
const buffer = Buffer.from(data)
|
||||
export async function encodeImageToBlurhash(url: string) {
|
||||
const buffer = await fetchBuffer(url)
|
||||
if (!buffer) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const t = sharp(buffer)
|
||||
.raw()
|
||||
.resize(8, 8, { fit: "inside" })
|
||||
.toFormat('jpeg')
|
||||
.toFormat("jpeg")
|
||||
|
||||
const { data: out, info } = await t.toBuffer({ resolveWithObject: true })
|
||||
const { data } = await t.toBuffer({ resolveWithObject: true })
|
||||
|
||||
return `data:image/jpeg;base64,${out.toString('base64')}`
|
||||
return `data:image/jpeg;base64,${data.toString("base64")}`
|
||||
}
|
||||
+2
-2
@@ -11,7 +11,7 @@ export function asItem<T>(value: T | T[]) {
|
||||
}
|
||||
|
||||
export function paramsToSlug(parts: string | string[]): string {
|
||||
const slug = asArray(parts).join('/')
|
||||
const slug = asArray(parts).join("/")
|
||||
|
||||
return `/${slug}`
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -128,8 +128,8 @@ export type StrapiMenu = {
|
||||
}
|
||||
|
||||
export type StrapiPageCard = {
|
||||
__component: 'hudozka.page-card',
|
||||
__component: "hudozka.page-card",
|
||||
id: number,
|
||||
page: StrapiPage,
|
||||
layout: 'small' | 'medium' | 'big',
|
||||
layout: "small" | "medium" | "big",
|
||||
}
|
||||
+7
-7
@@ -102,12 +102,12 @@ export type Pic = {
|
||||
}
|
||||
|
||||
export type TextToken = {
|
||||
token: 'text'
|
||||
token: "text"
|
||||
data: string
|
||||
}
|
||||
|
||||
export type ImageToken = {
|
||||
token: 'image'
|
||||
token: "image"
|
||||
wide: boolean
|
||||
data: Pic
|
||||
}
|
||||
@@ -123,24 +123,24 @@ export type FileTokenData = {
|
||||
}
|
||||
|
||||
export type FileToken = {
|
||||
token: 'file'
|
||||
token: "file"
|
||||
data: FileTokenData
|
||||
}
|
||||
|
||||
export type HtmlToken = {
|
||||
token: 'html'
|
||||
token: "html"
|
||||
data: string
|
||||
}
|
||||
|
||||
export type YoutubeToken = {
|
||||
token: 'youtube'
|
||||
token: "youtube"
|
||||
data: {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
export type InstagramToken = {
|
||||
token: 'instagram'
|
||||
token: "instagram"
|
||||
data: {
|
||||
url: string
|
||||
embed: string
|
||||
@@ -148,7 +148,7 @@ export type InstagramToken = {
|
||||
}
|
||||
|
||||
export type GridToken = {
|
||||
token: 'grid'
|
||||
token: "grid"
|
||||
data: {
|
||||
items: PageCardDto[]
|
||||
}
|
||||
|
||||
+4
-4
@@ -2,9 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
@@ -14,7 +14,7 @@
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
|
||||
Reference in new issue
Block a user