mirror of
https://github.com/sergeyyarkov/devthread.ru.git
synced 2026-10-11 10:19:13 +03:00
format
This commit is contained in:
parent
f0b960eb22
commit
5778d12166
@ -25,14 +25,14 @@ tags:
|
|||||||
|
|
||||||
## Содержание
|
## Содержание
|
||||||
|
|
||||||
* [Описание списка зависимостей](#Описание-списка-зависимостей)
|
- [Описание списка зависимостей](#Описание-списка-зависимостей)
|
||||||
* [Структура проекта](#Структура-проекта)
|
- [Структура проекта](#Структура-проекта)
|
||||||
* [Предварительная настройка](#Предварительная-настройка)
|
- [Предварительная настройка](#Предварительная-настройка)
|
||||||
* [Создание Type Definitions](#Создание-Type-Definitions)
|
- [Создание Type Definitions](#Создание-Type-Definitions)
|
||||||
* [Создание Resolvers](#Создание-Resolvers)
|
- [Создание Resolvers](#Создание-Resolvers)
|
||||||
* [Создание схемы](#Создание-схемы)
|
- [Создание схемы](#Создание-схемы)
|
||||||
* [Использование Apollo Server Express](#Использование-Apollo-Server-Express)
|
- [Использование Apollo Server Express](#Использование-Apollo-Server-Express)
|
||||||
* [Заключение](#Заключение)
|
- [Заключение](#Заключение)
|
||||||
|
|
||||||
💡 Данное руководство расчитано на тех разработчиков, кто уже имел небольшой опыт с использованием технологий **Apollo Server** и **TypeScript**.
|
💡 Данное руководство расчитано на тех разработчиков, кто уже имел небольшой опыт с использованием технологий **Apollo Server** и **TypeScript**.
|
||||||
|
|
||||||
@ -56,9 +56,11 @@ npm install @types/graphql-depth-limit apollo-server-express cors dotenv express
|
|||||||
// инициализировать файл с настройками typescript
|
// инициализировать файл с настройками typescript
|
||||||
npx tsc --init
|
npx tsc --init
|
||||||
```
|
```
|
||||||
|
|
||||||
Теперь по порядку узнаем для чего нужны эти пакеты:
|
Теперь по порядку узнаем для чего нужны эти пакеты:
|
||||||
|
|
||||||
**devDependencies:**
|
**devDependencies:**
|
||||||
|
|
||||||
1. **@typesnode** - устанавливаем type definitions node.js для нашего typescript **~717 kB**
|
1. **@typesnode** - устанавливаем type definitions node.js для нашего typescript **~717 kB**
|
||||||
2. **prettier** - этот пакет нужен для форматирования вашего кода, вы можете установить его <ins>по желанию</ins> **~9.08 MB**
|
2. **prettier** - этот пакет нужен для форматирования вашего кода, вы можете установить его <ins>по желанию</ins> **~9.08 MB**
|
||||||
3. **ts-node** - данные пакет предназначен для транспиляции typescript кода в javascript **~377 kB**
|
3. **ts-node** - данные пакет предназначен для транспиляции typescript кода в javascript **~377 kB**
|
||||||
@ -66,6 +68,7 @@ npx tsc --init
|
|||||||
5. **typescript** - сам typescript **~56.8 MB**
|
5. **typescript** - сам typescript **~56.8 MB**
|
||||||
|
|
||||||
**dependencies:**
|
**dependencies:**
|
||||||
|
|
||||||
1. **apollo-server-express** - GraphQL сервер вместе в веб-фреймворком Express.js **~70.1 kB**
|
1. **apollo-server-express** - GraphQL сервер вместе в веб-фреймворком Express.js **~70.1 kB**
|
||||||
2. **express** - веб-фреймворк Express.js **~208 kB**
|
2. **express** - веб-фреймворк Express.js **~208 kB**
|
||||||
3. **graphql** - среда выполнения запросов к нашей API **~1.92 MB**
|
3. **graphql** - среда выполнения запросов к нашей API **~1.92 MB**
|
||||||
@ -78,6 +81,7 @@ npx tsc --init
|
|||||||
## Структура проекта
|
## Структура проекта
|
||||||
|
|
||||||
Теперь после того как все пакеты установлены, определимся со структурой проекта:
|
Теперь после того как все пакеты установлены, определимся со структурой проекта:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.
|
.
|
||||||
└── typescript-apollo-server
|
└── typescript-apollo-server
|
||||||
@ -103,13 +107,16 @@ npx tsc --init
|
|||||||
## Предварительная настройка
|
## Предварительная настройка
|
||||||
|
|
||||||
Перед тем как начать разрабатывать наш сервер, зайдем в **package.json** и установим скрипты запуска:
|
Перед тем как начать разрабатывать наш сервер, зайдем в **package.json** и установим скрипты запуска:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node index.ts",
|
"start": "ts-node index.ts",
|
||||||
"server": "ts-node-dev --respawn index.ts"
|
"server": "ts-node-dev --respawn index.ts"
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
Теперь необходимо создать схему, создаем папку **graphql** и в ней также создаем еще две папки **resolvers** и **typeDefs**, также не забываем создать файл **schema.ts**:
|
Теперь необходимо создать схему, создаем папку **graphql** и в ней также создаем еще две папки **resolvers** и **typeDefs**, также не забываем создать файл **schema.ts**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir graphql
|
mkdir graphql
|
||||||
cd ./graphql
|
cd ./graphql
|
||||||
@ -134,6 +141,7 @@ touch Book.graphql
|
|||||||
Далее напишем наши типы. Обратите внимание что для удобства и разделения кода мы будем использовать импорты. Возможность использовать импорты нам предоставляет пакет **graphql-tools**. Для того чтобы импортировать файл **.graphql** ставится знак **#** и указывается путь.
|
Далее напишем наши типы. Обратите внимание что для удобства и разделения кода мы будем использовать импорты. Возможность использовать импорты нам предоставляет пакет **graphql-tools**. Для того чтобы импортировать файл **.graphql** ставится знак **#** и указывается путь.
|
||||||
|
|
||||||
Главный файл **schema.graphql**:
|
Главный файл **schema.graphql**:
|
||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
# ./graphql/typeDefs/schema.graphql
|
# ./graphql/typeDefs/schema.graphql
|
||||||
|
|
||||||
@ -147,6 +155,7 @@ type Query {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Файл **Book.graphql**:
|
Файл **Book.graphql**:
|
||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
# ./graphql/typeDefs/Book.graphql
|
# ./graphql/typeDefs/Book.graphql
|
||||||
|
|
||||||
@ -157,6 +166,7 @@ type Book {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Файл **User.graphql**:
|
Файл **User.graphql**:
|
||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
# ./graphql/typeDefs/User.graphql
|
# ./graphql/typeDefs/User.graphql
|
||||||
|
|
||||||
@ -191,10 +201,11 @@ cd ..
|
|||||||
Наконец прописываем обработчики:
|
Наконец прописываем обработчики:
|
||||||
|
|
||||||
Файл **Books.ts**:
|
Файл **Books.ts**:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// ./graphql/resolvers/Books/Books.ts
|
// ./graphql/resolvers/Books/Books.ts
|
||||||
|
|
||||||
import { IResolvers } from 'graphql-tools';
|
import { IResolvers } from "graphql-tools"
|
||||||
|
|
||||||
interface BookType {
|
interface BookType {
|
||||||
id: string;
|
id: string;
|
||||||
@ -202,17 +213,17 @@ interface BookType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const books = [
|
const books = [
|
||||||
{ id: '1', title: 'First Book' },
|
{ id: "1", title: "First Book" },
|
||||||
{ id: '2', title: 'Second Book' },
|
{ id: "2", title: "Second Book" },
|
||||||
];
|
]
|
||||||
|
|
||||||
const booksResolver: IResolvers = {
|
const booksResolver: IResolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
books: (): BookType[] => books,
|
books: (): BookType[] => books,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default booksResolver;
|
export default booksResolver
|
||||||
```
|
```
|
||||||
|
|
||||||
Файл **Users.ts**:
|
Файл **Users.ts**:
|
||||||
@ -220,7 +231,7 @@ export default booksResolver;
|
|||||||
```js
|
```js
|
||||||
// ./graphql/resolvers/Users/Users.ts
|
// ./graphql/resolvers/Users/Users.ts
|
||||||
|
|
||||||
import { IResolvers } from 'graphql-tools';
|
import { IResolvers } from "graphql-tools"
|
||||||
|
|
||||||
interface UserType {
|
interface UserType {
|
||||||
id: string;
|
id: string;
|
||||||
@ -228,17 +239,17 @@ interface UserType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const users = [
|
const users = [
|
||||||
{ id: '1', name: 'John' },
|
{ id: "1", name: "John" },
|
||||||
{ id: '2', name: 'Ivan' },
|
{ id: "2", name: "Ivan" },
|
||||||
];
|
]
|
||||||
|
|
||||||
const usersResolver: IResolvers = {
|
const usersResolver: IResolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
users: (): UserType[] => users,
|
users: (): UserType[] => users,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default usersResolver;
|
export default usersResolver
|
||||||
```
|
```
|
||||||
|
|
||||||
Резолверы получились довольно простые и понятные, теперь нужно собрать их в один массив для дальнейшего экспорта. В файле **index.ts** прописываем следующее:
|
Резолверы получились довольно простые и понятные, теперь нужно собрать их в один массив для дальнейшего экспорта. В файле **index.ts** прописываем следующее:
|
||||||
@ -246,17 +257,18 @@ export default usersResolver;
|
|||||||
```js
|
```js
|
||||||
// ./graphql/resolvers/index.ts
|
// ./graphql/resolvers/index.ts
|
||||||
|
|
||||||
import { IResolvers } from 'graphql-tools';
|
import { IResolvers } from "graphql-tools"
|
||||||
|
|
||||||
import booksResolver from './Books/Books';
|
import booksResolver from "./Books/Books"
|
||||||
import usersResolver from './Users/Users'
|
import usersResolver from "./Users/Users"
|
||||||
|
|
||||||
const resolvers: IResolvers[] = [usersResolver, booksResolver];
|
const resolvers: IResolvers[] = [usersResolver, booksResolver]
|
||||||
|
|
||||||
export default resolvers;
|
export default resolvers
|
||||||
```
|
```
|
||||||
|
|
||||||
## Создание схемы
|
## Создание схемы
|
||||||
|
|
||||||
Как говорилось ранее, пакет **graphql-tools** предназначен для создания схемы. Теперь перейдем в наш файл **schema.ts** и соберем нашу схему.
|
Как говорилось ранее, пакет **graphql-tools** предназначен для создания схемы. Теперь перейдем в наш файл **schema.ts** и соберем нашу схему.
|
||||||
|
|
||||||
Раньше для того чтобы работали наши graphql импорты мы применяли пакет **graphql-import**, но теперь его не нужно устанавливать так как он уже входит в состав **graphql-tools**. Подробнее об этом можно прочитать [здесь](https://www.graphql-tools.com/docs/migration-from-import).
|
Раньше для того чтобы работали наши graphql импорты мы применяли пакет **graphql-import**, но теперь его не нужно устанавливать так как он уже входит в состав **graphql-tools**. Подробнее об этом можно прочитать [здесь](https://www.graphql-tools.com/docs/migration-from-import).
|
||||||
@ -265,20 +277,26 @@ export default resolvers;
|
|||||||
```js
|
```js
|
||||||
// ./graphql/schema.ts
|
// ./graphql/schema.ts
|
||||||
|
|
||||||
import { mergeResolvers, loadSchemaSync, loadFilesSync, GraphQLFileLoader, addResolversToSchema } from 'graphql-tools';
|
import {
|
||||||
import { GraphQLSchema } from 'graphql';
|
mergeResolvers,
|
||||||
|
loadSchemaSync,
|
||||||
|
loadFilesSync,
|
||||||
|
GraphQLFileLoader,
|
||||||
|
addResolversToSchema,
|
||||||
|
} from "graphql-tools"
|
||||||
|
import { GraphQLSchema } from "graphql"
|
||||||
|
|
||||||
const schema = loadSchemaSync(`${__dirname}/typeDefs/schema.graphql`, {
|
const schema = loadSchemaSync(`${__dirname}/typeDefs/schema.graphql`, {
|
||||||
loaders: [new GraphQLFileLoader()],
|
loaders: [new GraphQLFileLoader()],
|
||||||
});
|
})
|
||||||
|
|
||||||
const resolvers = loadFilesSync(`${__dirname}/resolvers`);
|
const resolvers = loadFilesSync(`${__dirname}/resolvers`)
|
||||||
const schemaWithResolvers: GraphQLSchema = addResolversToSchema({
|
const schemaWithResolvers: GraphQLSchema = addResolversToSchema({
|
||||||
schema,
|
schema,
|
||||||
resolvers: mergeResolvers(resolvers),
|
resolvers: mergeResolvers(resolvers),
|
||||||
});
|
})
|
||||||
|
|
||||||
export default schemaWithResolvers;
|
export default schemaWithResolvers
|
||||||
```
|
```
|
||||||
|
|
||||||
## Использование Apollo Server Express
|
## Использование Apollo Server Express
|
||||||
@ -294,38 +312,38 @@ touch index.ts
|
|||||||
```js
|
```js
|
||||||
// ./index.ts
|
// ./index.ts
|
||||||
|
|
||||||
import express, { Application, Request, Response } from 'express';
|
import express, { Application, Request, Response } from "express"
|
||||||
import cors from 'cors';
|
import cors from "cors"
|
||||||
import depthLimit from 'graphql-depth-limit';
|
import depthLimit from "graphql-depth-limit"
|
||||||
import { ApolloServer } from 'apollo-server-express';
|
import { ApolloServer } from "apollo-server-express"
|
||||||
import schema from './graphql/schema';
|
import schema from "./graphql/schema"
|
||||||
|
|
||||||
const PORT = process.env.PORT || 4000;
|
const PORT = process.env.PORT || 4000
|
||||||
|
|
||||||
const startServer = (): void => {
|
const startServer = (): void => {
|
||||||
try {
|
try {
|
||||||
const app: Application = express();
|
const app: Application = express()
|
||||||
|
|
||||||
const server: ApolloServer = new ApolloServer({
|
const server: ApolloServer = new ApolloServer({
|
||||||
schema,
|
schema,
|
||||||
validationRules: [depthLimit(10)], // выставляем правило deph limit = 10
|
validationRules: [depthLimit(10)], // выставляем правило deph limit = 10
|
||||||
playground: true,
|
playground: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
app.use('*', cors());
|
app.use("*", cors())
|
||||||
app.get('/', (req: Request, res: Response) => res.send('GraphQL API'));
|
app.get("/", (req: Request, res: Response) => res.send("GraphQL API"))
|
||||||
|
|
||||||
server.applyMiddleware({ app });
|
server.applyMiddleware({ app })
|
||||||
|
|
||||||
app.listen({ port: PORT }, () =>
|
app.listen({ port: PORT }, () =>
|
||||||
console.log(`🚀 Server ready on port: ${PORT}`)
|
console.log(`🚀 Server ready on port: ${PORT}`)
|
||||||
);
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`❌ Something went wrong: \n ${err}`);
|
console.log(`❌ Something went wrong: \n ${err}`)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
startServer();
|
startServer()
|
||||||
```
|
```
|
||||||
|
|
||||||
Как видим, ничего сложного нет, просто применяем функцию [applyMiddleware](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#applymiddleware) для того чтобы соедениеть Apollo Server с фрэймворком Express.js. Далее можно протестировать наш сервер, запускаем команду **npm run start** и переходим по адресу **/graphql**. Для тестирования можно выполнить вот такой запрос:
|
Как видим, ничего сложного нет, просто применяем функцию [applyMiddleware](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#applymiddleware) для того чтобы соедениеть Apollo Server с фрэймворком Express.js. Далее можно протестировать наш сервер, запускаем команду **npm run start** и переходим по адресу **/graphql**. Для тестирования можно выполнить вот такой запрос:
|
||||||
@ -372,7 +390,6 @@ startServer();
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Заключение
|
## Заключение
|
||||||
|
|
||||||
Как видим, использование Apollo Server вместе с TypeScript оказалась совсем не сложной задачей, так как и Apollo Server и пакет graphql-import имеют поддержку typescript из коробки.
|
Как видим, использование Apollo Server вместе с TypeScript оказалась совсем не сложной задачей, так как и Apollo Server и пакет graphql-import имеют поддержку typescript из коробки.
|
||||||
@ -3,7 +3,7 @@ import React from "react"
|
|||||||
import Layout from "./src/components/Layout/Layout"
|
import Layout from "./src/components/Layout/Layout"
|
||||||
|
|
||||||
import "prismjs/themes/prism-tomorrow.css"
|
import "prismjs/themes/prism-tomorrow.css"
|
||||||
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
|
import "prismjs/plugins/line-numbers/prism-line-numbers.css"
|
||||||
|
|
||||||
import "./src/styles/global.scss"
|
import "./src/styles/global.scss"
|
||||||
import "./src/components/Header/Header.scss"
|
import "./src/components/Header/Header.scss"
|
||||||
@ -30,8 +30,12 @@ export const shouldUpdateScroll = ({ routerProps: { location } }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const wrapPageElement = ({ element, props }) => {
|
export const wrapPageElement = ({ element, props }) => {
|
||||||
const { location: { pathname } } = props
|
const {
|
||||||
pathname.includes('/amp/article/') && props.pageContext.slug ? props.amp = true : props.amp = false
|
location: { pathname },
|
||||||
|
} = props
|
||||||
|
pathname.includes("/amp/article/") && props.pageContext.slug
|
||||||
|
? (props.amp = true)
|
||||||
|
: (props.amp = false)
|
||||||
|
|
||||||
return <Layout {...props}>{element}</Layout>
|
return <Layout {...props}>{element}</Layout>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,12 +161,12 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
resolve: `gatsby-plugin-amp`,
|
resolve: `gatsby-plugin-amp`,
|
||||||
options: {
|
options: {
|
||||||
canonicalBaseUrl: 'https://devthread.ru/',
|
canonicalBaseUrl: "https://devthread.ru/",
|
||||||
components: ['amp-sidebar', 'amp-accordion', 'amp-form'],
|
components: ["amp-sidebar", "amp-accordion", "amp-form"],
|
||||||
excludedPaths: ['/404*', '/'],
|
excludedPaths: ["/404*", "/"],
|
||||||
pathIdentifier: '/amp/',
|
pathIdentifier: "/amp/",
|
||||||
relAmpHtmlPattern: '{{canonicalBaseUrl}}{{pathIdentifier}}{{pathname}}',
|
relAmpHtmlPattern: "{{canonicalBaseUrl}}{{pathIdentifier}}{{pathname}}",
|
||||||
relCanonicalPattern: '{{canonicalBaseUrl}}{{pathname}}/',
|
relCanonicalPattern: "{{canonicalBaseUrl}}{{pathname}}/",
|
||||||
useAmpClientIdApi: false,
|
useAmpClientIdApi: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const OnDark = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const onRenderBody = ({ pathname, setPreBodyComponents }) => {
|
export const onRenderBody = ({ pathname, setPreBodyComponents }) => {
|
||||||
if (pathname.includes('/amp/article/')) {
|
if (pathname.includes("/amp/article/")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +48,12 @@ export const onRenderBody = ({ pathname, setPreBodyComponents }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const wrapPageElement = ({ element, props }) => {
|
export const wrapPageElement = ({ element, props }) => {
|
||||||
const { location: { pathname } } = props
|
const {
|
||||||
pathname.includes('/amp/article/') && props.pageContext.slug ? props.amp = true : props.amp = false
|
location: { pathname },
|
||||||
|
} = props
|
||||||
|
pathname.includes("/amp/article/") && props.pageContext.slug
|
||||||
|
? (props.amp = true)
|
||||||
|
: (props.amp = false)
|
||||||
|
|
||||||
return <Layout {...props}>{element}</Layout>
|
return <Layout {...props}>{element}</Layout>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import FacebookIcon from "../../images/facebook-icon__large.svg"
|
|||||||
const Article = ({
|
const Article = ({
|
||||||
data: { slug, title, image, description, category, tags, time, date },
|
data: { slug, title, image, description, category, tags, time, date },
|
||||||
html,
|
html,
|
||||||
amp
|
amp,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
siteMetadata: {
|
siteMetadata: {
|
||||||
@ -40,10 +40,12 @@ const Article = ({
|
|||||||
<div className="article-info__time">
|
<div className="article-info__time">
|
||||||
<span>~ {time} мин</span>
|
<span>~ {time} мин</span>
|
||||||
</div>
|
</div>
|
||||||
{!amp ? <div className="article-info__views">
|
{!amp ? (
|
||||||
|
<div className="article-info__views">
|
||||||
<ViewsIcon />
|
<ViewsIcon />
|
||||||
<ViewCounter id={pathname} />
|
<ViewCounter id={pathname} />
|
||||||
</div> : null}
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="right-info">
|
<div className="right-info">
|
||||||
<div className="article-info__date">
|
<div className="article-info__date">
|
||||||
@ -93,19 +95,20 @@ const Article = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="article-image">
|
<div className="article-image">
|
||||||
{!amp
|
{!amp ? (
|
||||||
? <Img
|
<Img
|
||||||
loading="eager"
|
loading="eager"
|
||||||
fadeIn={false}
|
fadeIn={false}
|
||||||
fluid={image.childImageSharp.fluid}
|
fluid={image.childImageSharp.fluid}
|
||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
: <img
|
) : (
|
||||||
|
<img
|
||||||
src={image.childImageSharp.fluid.src}
|
src={image.childImageSharp.fluid.src}
|
||||||
height='350'
|
height="350"
|
||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="article-inner inner">
|
<div className="article-inner inner">
|
||||||
<div dangerouslySetInnerHTML={{ __html: html }}></div>
|
<div dangerouslySetInnerHTML={{ __html: html }}></div>
|
||||||
|
|||||||
@ -6,21 +6,26 @@ import { Link } from "gatsby"
|
|||||||
|
|
||||||
const ArticleCard = ({
|
const ArticleCard = ({
|
||||||
data: { slug, title, image, category, description, tags },
|
data: { slug, title, image, category, description, tags },
|
||||||
amp
|
amp,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<article className="article-card">
|
<article className="article-card">
|
||||||
<div className="article-card__image">
|
<div className="article-card__image">
|
||||||
<Link to={`/article/${slug}/`}>
|
<Link to={`/article/${slug}/`}>
|
||||||
{!amp
|
{!amp ? (
|
||||||
? <Img
|
<Img
|
||||||
loading="eager"
|
loading="eager"
|
||||||
fadeIn={false}
|
fadeIn={false}
|
||||||
fluid={image.childImageSharp.fluid}
|
fluid={image.childImageSharp.fluid}
|
||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
: <img src={image.childImageSharp.fluid.src} height='380' alt={title} />
|
) : (
|
||||||
}
|
<img
|
||||||
|
src={image.childImageSharp.fluid.src}
|
||||||
|
height="380"
|
||||||
|
alt={title}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="article-card__heading">
|
<div className="article-card__heading">
|
||||||
|
|||||||
@ -90,4 +90,3 @@ export default Footer
|
|||||||
Footer.propTypes = {
|
Footer.propTypes = {
|
||||||
amp: PropTypes.bool,
|
amp: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,9 @@ import React from "react"
|
|||||||
import Header from "../Header/Header"
|
import Header from "../Header/Header"
|
||||||
import Footer from "../Footer/Footer"
|
import Footer from "../Footer/Footer"
|
||||||
|
|
||||||
import { HeaderAMP } from '../../lib/amp'
|
import { HeaderAMP } from "../../lib/amp"
|
||||||
|
|
||||||
const Layout = ({ children, amp }) => {
|
const Layout = ({ children, amp }) => {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
amp is accelerated mobile pages
|
amp is accelerated mobile pages
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -3,17 +3,21 @@ import React from "react"
|
|||||||
const Newsletter = () => {
|
const Newsletter = () => {
|
||||||
const submitHandler = e => {
|
const submitHandler = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="newsletter">
|
<div className="newsletter">
|
||||||
<div className="newsletter-heading"><span role="img" aria-label="hot">📫</span>{" "}Новостная рассылка</div>
|
<div className="newsletter-heading">
|
||||||
|
<span role="img" aria-label="hot">
|
||||||
|
📫
|
||||||
|
</span>{" "}
|
||||||
|
Новостная рассылка
|
||||||
|
</div>
|
||||||
<div className="newsletter-content">
|
<div className="newsletter-content">
|
||||||
<p>
|
<p>
|
||||||
Подпишитесь на новостную рассылку чтобы не пропустить вышедшие статьи
|
Подпишитесь на новостную рассылку чтобы не пропустить вышедшие статьи
|
||||||
</p>
|
</p>
|
||||||
<form onSubmit={submitHandler} action="#" target='_blank'>
|
<form onSubmit={submitHandler} action="#" target="_blank">
|
||||||
<div className="email-field">
|
<div className="email-field">
|
||||||
<input placeholder="ваш e-mail" type="email" name="email" />
|
<input placeholder="ваш e-mail" type="email" name="email" />
|
||||||
<button>Подписаться</button>
|
<button>Подписаться</button>
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const SEO = ({
|
|||||||
type,
|
type,
|
||||||
canonical,
|
canonical,
|
||||||
url,
|
url,
|
||||||
amp
|
amp,
|
||||||
}) => {
|
}) => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const { siteMetadata } = useSiteMetadataQuery()
|
const { siteMetadata } = useSiteMetadataQuery()
|
||||||
@ -71,7 +71,8 @@ const SEO = ({
|
|||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#191919" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#191919" />
|
||||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
{!amp ? <script type="application/ld+json">
|
{!amp ? (
|
||||||
|
<script type="application/ld+json">
|
||||||
{JSON.stringify({
|
{JSON.stringify({
|
||||||
"@context": "http://schema.org",
|
"@context": "http://schema.org",
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
@ -79,7 +80,8 @@ const SEO = ({
|
|||||||
url: siteMetadata.siteUrl,
|
url: siteMetadata.siteUrl,
|
||||||
sameAs: [siteMetadata.social.twitter],
|
sameAs: [siteMetadata.social.twitter],
|
||||||
})}
|
})}
|
||||||
</script> : null}
|
</script>
|
||||||
|
) : null}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const useMoreArticlesQuery = () => {
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 650 quality: 100) {
|
fluid(maxWidth: 650, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from "react"
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet"
|
||||||
import useSiteMetadataQuery from '../hooks/useSiteMetadataQuery'
|
import useSiteMetadataQuery from "../hooks/useSiteMetadataQuery"
|
||||||
|
|
||||||
import LogoIcon from '../images/logo-icon.svg'
|
import LogoIcon from "../images/logo-icon.svg"
|
||||||
import TelegramIcon from "../images/telegram-icon.svg"
|
import TelegramIcon from "../images/telegram-icon.svg"
|
||||||
import TwitterIcon from "../images/twitter-icon.svg"
|
import TwitterIcon from "../images/twitter-icon.svg"
|
||||||
import YadzenIcon from "../images/yadzen-icon.svg"
|
import YadzenIcon from "../images/yadzen-icon.svg"
|
||||||
@ -25,7 +25,7 @@ const HeaderAMP = () => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0px 24px;
|
padding: 0px 24px;
|
||||||
padding-top: 25px;
|
padding-top: 25px;
|
||||||
background-color: rgba(7,11,14,.99);
|
background-color: rgba(7, 11, 14, 0.99);
|
||||||
width: 320px;
|
width: 320px;
|
||||||
}
|
}
|
||||||
amp-sidebar nav li {
|
amp-sidebar nav li {
|
||||||
@ -79,9 +79,14 @@ const HeaderAMP = () => {
|
|||||||
<div className="header-content amp">
|
<div className="header-content amp">
|
||||||
<div className="header-content__logo">
|
<div className="header-content__logo">
|
||||||
<LogoIcon />
|
<LogoIcon />
|
||||||
<a href='/'>devthread.ru</a>
|
<a href="/">devthread.ru</a>
|
||||||
</div>
|
</div>
|
||||||
<button tabindex="0" on='tap:sidebar-header' aria-label="menu" className="header-content__button mobile">
|
<button
|
||||||
|
tabindex="0"
|
||||||
|
on="tap:sidebar-header"
|
||||||
|
aria-label="menu"
|
||||||
|
className="header-content__button mobile"
|
||||||
|
>
|
||||||
<span />
|
<span />
|
||||||
<span />
|
<span />
|
||||||
<span />
|
<span />
|
||||||
@ -90,7 +95,13 @@ const HeaderAMP = () => {
|
|||||||
</header>
|
</header>
|
||||||
<amp-sidebar id="sidebar-header" layout="nodisplay" side="left">
|
<amp-sidebar id="sidebar-header" layout="nodisplay" side="left">
|
||||||
<nav>
|
<nav>
|
||||||
<div role='button' tabindex="0" on='tap:sidebar-header.toggle' aria-label="menu" className='amp-sidebar__toggleBtn'>
|
<div
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
on="tap:sidebar-header.toggle"
|
||||||
|
aria-label="menu"
|
||||||
|
className="amp-sidebar__toggleBtn"
|
||||||
|
>
|
||||||
✕
|
✕
|
||||||
</div>
|
</div>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
|
|||||||
@ -138,7 +138,7 @@ export const query = graphql`
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 650 quality: 100) {
|
fluid(maxWidth: 650, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const IndexPage = ({ data: { allMarkdownRemark: edges } }) => {
|
|||||||
<Col lg={9} xs={12}>
|
<Col lg={9} xs={12}>
|
||||||
<Articles data={edges} limit={limit} setLimit={setLimit} />
|
<Articles data={edges} limit={limit} setLimit={setLimit} />
|
||||||
<Snippets>
|
<Snippets>
|
||||||
{snippets.length <= 0 ? null : <h2>Сниппеты</h2> }
|
{snippets.length <= 0 ? null : <h2>Сниппеты</h2>}
|
||||||
{snippets.map(({ node: { frontmatter } }, i) => (
|
{snippets.map(({ node: { frontmatter } }, i) => (
|
||||||
<SnippetItem
|
<SnippetItem
|
||||||
key={i}
|
key={i}
|
||||||
@ -78,7 +78,7 @@ export const query = graphql`
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 650 quality: 100) {
|
fluid(maxWidth: 650, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const SnippetsPage = ({
|
|||||||
Сниппеты это готовые решения некоторых проблем, которые можно
|
Сниппеты это готовые решения некоторых проблем, которые можно
|
||||||
скопировать и вставить
|
скопировать и вставить
|
||||||
</p>
|
</p>
|
||||||
{snippets.length <= 0 ? <p>Soon...</p> : null }
|
{snippets.length <= 0 ? <p>Soon...</p> : null}
|
||||||
{snippets.map(({ node: { frontmatter } }, i) => (
|
{snippets.map(({ node: { frontmatter } }, i) => (
|
||||||
<SnippetItem
|
<SnippetItem
|
||||||
key={i}
|
key={i}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { parse } from 'node-html-parser'
|
import { parse } from "node-html-parser"
|
||||||
import { Main } from "../ui/ui"
|
import { Main } from "../ui/ui"
|
||||||
import { Grid, Row, Col } from "react-flexbox-grid"
|
import { Grid, Row, Col } from "react-flexbox-grid"
|
||||||
import SEO from "../components/SEO/SEO"
|
import SEO from "../components/SEO/SEO"
|
||||||
@ -16,12 +16,17 @@ const articleTemplateAmp = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
// gatsbyImage to default image
|
// gatsbyImage to default image
|
||||||
const parsedHTML = parse(html, { pre: true })
|
const parsedHTML = parse(html, { pre: true })
|
||||||
const pictures = [...parsedHTML.querySelectorAll('.gatsby-resp-image-link')]
|
const pictures = [...parsedHTML.querySelectorAll(".gatsby-resp-image-link")]
|
||||||
|
|
||||||
pictures.forEach(picture => picture.parentNode.parentNode.set_content(`<img src="${picture.getAttribute('href')}" alt="${picture.querySelector('.gatsby-resp-image-image').getAttribute('alt')}" height="350" />`))
|
pictures.forEach(picture =>
|
||||||
|
picture.parentNode.parentNode.set_content(
|
||||||
|
`<img src="${picture.getAttribute("href")}" alt="${picture
|
||||||
|
.querySelector(".gatsby-resp-image-image")
|
||||||
|
.getAttribute("alt")}" height="350" />`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -40,7 +45,11 @@ const articleTemplateAmp = ({
|
|||||||
<Grid fluid style={{ maxWidth: 800 }}>
|
<Grid fluid style={{ maxWidth: 800 }}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={12}>
|
<Col xs={12}>
|
||||||
<Article data={frontmatter} html={parsedHTML.toString().replace(/loading="[^"]*"/, "")} amp={true} />
|
<Article
|
||||||
|
data={frontmatter}
|
||||||
|
html={parsedHTML.toString().replace(/loading="[^"]*"/, "")}
|
||||||
|
amp={true}
|
||||||
|
/>
|
||||||
<Newsletter />
|
<Newsletter />
|
||||||
<MoreArticles amp={true} />
|
<MoreArticles amp={true} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -61,7 +70,7 @@ export const pageQuery = graphql`
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 1400 quality: 100) {
|
fluid(maxWidth: 1400, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export const pageQuery = graphql`
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 1200 quality: 100) {
|
fluid(maxWidth: 1200, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export const pageQuery = graphql`
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 650 quality: 100) {
|
fluid(maxWidth: 650, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export const pageQuery = graphql`
|
|||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(maxWidth: 650 quality: 100) {
|
fluid(maxWidth: 650, quality: 100) {
|
||||||
...GatsbyImageSharpFluid_withWebp
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user