Merge pull request #118 from tmshv/upd/maintenance-001

Maintenance 001
This commit is contained in:
tmshv authored and GitHub committed 2022-09-06 14:56:40 +03:00
commit 5ca2213e02
71 files changed
+4238 -2490

No files matched your search

-26
View File
@@ -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
}
}
};
+26
View File
@@ -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"
}
}
View File
File renamed without changes.
-1
View File
@@ -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
+3590 -1871
View File
File diff suppressed because it is too large. Load diff
+8 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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} />
</>
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+19 -19
View File
@@ -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>
+6 -6
View File
@@ -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 = {
+9 -9
View File
@@ -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}
+8 -8
View File
@@ -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,
})
+4 -4
View File
@@ -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 -1
View File
@@ -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);
}
+11 -14
View File
@@ -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>
))}
</>
)
+4 -4
View File
@@ -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}
>
+2 -2
View File
@@ -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)
+7 -7
View File
@@ -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>
)
}
}
+14 -10
View File
@@ -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>
)
+14 -9
View File
@@ -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>
)
+2 -2
View File
@@ -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>
)
+12 -12
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React from "react"
function script(account) {
return `
+3 -3
View File
@@ -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 (
+1 -1
View File
@@ -46,4 +46,4 @@ export const getCode = () => `
})()
</script>
<script>Widget("https://pos.gosuslugi.ru/form", 312519)</script>
`
`
+3 -3
View File
@@ -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,
}
},
})
}
+4 -2
View File
@@ -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"
+2 -2
View File
@@ -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>
)
)
+8 -8
View File
@@ -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}
+5 -5
View File
@@ -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[]
}
+4 -4
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import { IMeta } from 'src/types'
import { IMeta } from "src/types"
export type MetaProps = {
meta: IMeta
+23 -23
View File
@@ -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>
)}
</>
)
}
+21
View File
@@ -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;
}
+11 -27
View File
@@ -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>
)
+8 -3
View File
@@ -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,
+6 -6
View File
@@ -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])
+4 -4
View File
@@ -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}
+2 -2
View File
@@ -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
+15 -15
View File
@@ -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}
/>
)}
+9 -9
View File
@@ -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 : (
+5 -5
View File
@@ -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>
)}
+16 -14
View File
@@ -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"
+6 -8
View File
@@ -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>
)
+4 -4
View File
@@ -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,
}
})
},
})
+3 -3
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React from "react"
function script(account) {
return `
+1 -1
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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)
}
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
-3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,