Markdown и сообщения в блогах

Эквивалент нуклеарной семьи 60-х для современного человека - это веб-сайт; и сегодня построить такой проще, чем когда-либо. Имея на выбор множество платформ, нетрудно найти, какая из них соответствует вашим потребностям, но как только вы ее найдете, вам еще предстоит поработать.

Независимо от того, где размещен ваш сайт или на какой платформе он построен, у них всех есть одно общее - это сообщения в блогах. Об этом следует подумать, прежде чем переходить с другого хоста и менять платформу.

К счастью, Gatsby поддержит идеальный конструктор статических веб-сайтов для блогов, использующий форматирование Markdown для этих сообщений в блогах, что означает простой переход с другого хоста и конструктора сайтов!

Я собираюсь показать вам, как взять файлы разметки в Gatsby и превратить их в сгенерированные сообщения блога в формате HTML, так что приступим.

Настройка проекта

В этом уроке я буду использовать созданный мной бесплатный шаблон Peanut Butter & Jelly Gatsby. Полная версия также доступна, если вам нравится шаблон и вы хотите поддержать меня, купив его.

Вы можете посмотреть демонстрацию шаблона здесь:



А скачать его можно здесь: https://gum.co/pbj-template

or

Клонировать репо:



Это даст вам возможность работать с тем же проектом, что и тот, который я использовал для создания своей целевой страницы. Чтобы запустить этот шаблон, в терминале перейдите в каталог, в который вы поместили проект, и запустите:

yarn

Это загрузит все зависимости, необходимые для начала работы, и как только это будет выполнено, запустится:

yarn develop

Это запустит разработку, и вы сможете перейти к localhost: 8000, чтобы увидеть целевую страницу.

Если вы еще этого не сделали, откройте проект в любом текстовом редакторе по вашему выбору, я использую Vscode.

Уделите несколько минут тому, чтобы обратить внимание на файловую структуру, все, что включено, задокументировано в файле readme.

Для начала нам понадобится еще несколько пакетов, поэтому запустите эту команду в отдельном терминале.

yarn add gatsby-transformer-remark rehype-react

Создание типов и конфигурации

Этот шаблон использует инструмент разработки для генерации типов Typescript из схем Graphql. Если вам все по-гречески, то ничего страшного, я беру большую часть настройки за вас. Все, что вам нужно знать, это то, что нам понадобятся типы для нового трансформатора, который мы добавили. Но сначала нам нужно выполнить некоторую настройку. В файле codegen.yml в корне проекта добавьте эту строку под документами.

// codegen.yml
  - node_modules/gatsby-transformer-remark/!(node_modules)/**/*.js

Это добавит новые типы для Remark в наш файл сгенерированных типов. Это отлично работает для большинства случаев, но нам нужно расширить поле «frontmatter», чтобы добавить некоторые дополнительные свойства, такие как slug. Итак, откройте файл typedefs.js в src / graphql / typedefs.js, чтобы включить эти новые типы.

// src/grapql/typedefs.js
type MarkdownRemarkFrontmatter {
   author: AttributedUser
   title: String!
   slug: String!
   date: String
   featuredImage: String
}
type MarkdownRemark implements Node {
   frontmatter: MarkdownRemarkFrontmatter
}

Последнее, что нам нужно сделать перед генерацией типов, - это обновить gatsby-config добавленным плагином. Итак, где-нибудь в массиве плагинов добавьте это:

// gatsby-config.js
plugins: [
     `gatsby-transformer-remark`
]

Затем остановите и перезапустите процесс разработки и запустите:

yarn generate-types

Шаблоны Гэтсби со стилизованными компонентами

Теперь нам нужно будет сказать Гэтсби, чтобы он сгенерировал файлы HTML для нашей уценки. Нам нужно контролировать внешний вид каждой из этих страниц, но мы также хотим, чтобы все они работали одинаково. Вот тут-то и пригодятся шаблоны Гэтсби.

Вы можете увидеть пример этого в документации Гэтсби:



Мы собираемся создать наш собственный шаблон и использовать его для макета и стилизации наших сообщений. В папке src добавьте папку шаблонов. А внутри добавьте папку стилей с файлами article-template.styled.tsx и index.ts. Добавьте эти стили в файл article-template.styled.tsx.

// templates/styles/article-template.styled.tsx
import styled from 'styled-components'
export const Article = styled.article`
   background-color: white;
   color: ${({ theme }) => theme.colors.mediumGray};
   display: flex;
   flex-direction: column;
   padding: 2em 10vw 2em 10vw;
`
export const Author = styled.div`
   display: flex;
   justify-content: center;
`
export const AfterTitle = styled.div`
   margin: auto auto;
   width: 100%;
   @media all and (min-width: 700px) {
     width: 70%;
   }
`
export const Content = styled.div`
   display: flex;
   flex: 1;
   margin-top: 2em;
   max-width: 100vw;
   overflow: hidden;
   word-wrap: break-word;
   div {
   max-width: 100%;
   }
`
export const Heading = styled.h1`
  font-size: 2em;
`
export const List = styled.ul`
  list-style-type: disc;
`
export const Paragraph = styled.p`
  font-size: 1.2em;
  line-height: 1.5;
`
export const SubHeading = styled.h2`
  font-size: 1.5em;
`
export const Title = styled.h1`
  font-size: 3em;
  text-align: center;
`

И экспортируйте все стили из файла index.ts вот так:

// templates/styles/index.ts
export * from './article-template.styled'

Наконец, создайте файл article-template.tsx в корне шаблонов:

// src/templates/article-template.tsx
import * as React from 'react'
import * as Styled from './styles'
import { Avatar, Image, Layout } from '../components'
import { ImageType } from '../enums'
import { Query } from '../interfaces'
import RehypeReact from 'rehype-react'
import { format } from 'date-fns'
import { graphql } from 'gatsby'
export const query = graphql`
   query($slug: String!) {
      allMarkdownRemark(filter: 
      { frontmatter: { slug: { eq: $slug }} }) {
            edges {
                node {
                    frontmatter {
                          author {
                               avatar
                               name
                       }
                       date
                       featuredImage
                       title
                    }
                    excerpt
                    htmlAst
                    }
               }
          }
     }
`
const articleTemplate: React.FC<{ data: { allMarkdownRemark: Query['allMarkdownRemark'] } }> = ({ data }) => {
  if (!data) {
    return null
  }
  const {
      allMarkdownRemark: {
             edges: [
                 {
                   node: { frontmatter, htmlAst }
                 }
              ]
          }
      } = { ...data }
  const renderAst = new (RehypeReact as any)({
      components: {
           h1: Styled.Heading,
           h2: Styled.SubHeading,
           p: Styled.Paragraph,
           ul: Styled.List
        },
        createElement: React.createElement
   }).Compiler
return (
  <Layout>
     <Styled.Article>
         {frontmatter && (<>
            <Styled.Title>{frontmatter.title}</Styled.Title>
           {frontmatter.author && (
           <Styled.Author>{frontmatter.author.avatar && 
           <Avatar avatar={frontmatter.author.avatar} />}
              <Styled.SubHeading>
              {frontmatter.author.name}
             </Styled.SubHeading>
           </Styled.Author>)}
      {(frontmatter.featuredImage 
      || frontmatter.date) && 
(<Styled.AfterTitle>
    {frontmatter.featuredImage && 
(<Image src={frontmatter.featuredImage} type={ImageType.FLUID}
style={frontmatter.date ? { marginBottom: '10px' } : undefined}/>)}
   {frontmatter.date && (
       <Styled.SubHeading style={{ textAlign: 'center' }}>
        {format(new Date(frontmatter.date), 'MMM do yyyy')}
       </Styled.SubHeading>
   )}
    </Styled.AfterTitle>)}</>)}
    <Styled.Content>{renderAst(htmlAst)}</Styled.Content>   
       </Styled.Article>
   </Layout>)
}
export default articleTemplate

Это может показаться сложным, но все, что мы делаем, - это запрашиваем все уценки и фильтруем их по слагу. Заголовок используется для определения URL-адреса сообщения, а заголовок - это такие поля, как избранное изображение и автор. После того, как у нас будет правильный пост, мы отрендерим все упомянутые мной обложки. Затем используйте Rehype React, чтобы превратить необработанную строку HTML в компонент. Каждый из определенных базовых HTML-элементов, которые мы указали, преобразуется в стилизованные компоненты. Поступая таким образом, мы получаем больший контроль над стилем наших сообщений.

Создание страниц как сообщений блога

Вот где происходит волшебство.

Мы будем использовать ловушку create Pages, предоставленную Gatsby, чтобы запросить нашу уценку на страницах, используя созданный нами шаблон. В файле gatsby-config.js добавьте ловушку.

// gatsby-config.js
exports.createPages = async ({ actions: { createPage }, graphql })   => {
  const {
    data: { allMarkdownRemark, errors }
   } = await graphql(`
       {
         allMarkdownRemark {
              edges {
                  node {
                    frontmatter {
                         slug
                     }
                  }
              }
          }
      }
`)
if (!allMarkdownRemark || errors) {
console.log('Error retrieving data', errors || 'No data could be found for this query!')
return
}
const articleTemplate = require.resolve('./src/templates/article-template.tsx')
allMarkdownRemark.edges.forEach((edge) => {
createPage({
      component: articleTemplate,
      context: {
         slug: edge.node.frontmatter.slug
      },
      path: `/blog/${edge.node.frontmatter.slug}/`
     })
  })
}

Навигация по сообщениям

Мы могли бы просто вручную перейти к URL-адресу в каждом из наших постов, но пользователь должен иметь возможность находить наши сообщения и переходить к ним. Итак, сначала создайте папку блога в компонентах, а внутри этой папки создайте папку для сообщений. Оттуда создайте папку стилей и заполните ее файлами post.styled.tsx и index.ts.

// blog/post/styles/post.styled.tsx
import { Card } from '@material-ui/core'
import { Image } from '../../../image'
import { Link } from 'gatsby'
import styled from 'styled-components'
export const AuthorInfo = styled.div`
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  h4 {
   margin: 0px;
  }
`
export const FeaturedImage = styled(Image).attrs(() => ({
   aspectRatio: 21 / 11,
   type: 'fluid'
}))`
   flex: 1;
`
export const Info = styled.div`
   align-items: center;
   display: flex;
   flex-direction: column;
   margin-top: 1em;
`
export const PostItem = styled(Card).attrs({
   raised: true
})`
   align-items: center;
   display: flex;
   flex-direction: column;
   text-align: center;
`
export const PostContent = styled.span`
   padding: 1em;
`
export const PostContentUpper = styled.div`
   margin-bottom: 10px;
   
   h3 {
   margin: 0px;
   }
`
export const PostLink = styled(Link)`
   color: ${({ theme }) => theme.colors.black};
   display: flex;
   flex: 1;
   flex-direction: column;
   text-decoration: none;
   width: 100%;
`

Еще раз экспортируйте стили:

// blog/post/styles/index.ts
export * from './post.styled'

Теперь давайте создадим собственно пост. Нам нужно будет передать "аватарку" каждого сообщения, чтобы читатель мог понять, о чем идет речь.

// blog/post/post.tsx
import * as React from 'react'
import * as Styled from './styles'
import { MarkdownRemark, MarkdownRemarkFrontmatter } from '../../../interfaces'
import { Avatar } from '../../avatar'
import { CardProps } from '@material-ui/core'
import { GatsbyLinkProps } from 'gatsby'
import { format } from 'date-fns'
interface Post extends MarkdownRemarkFrontmatter, Omit<CardProps, 'title' | 'onClick'> {
  excerpt: MarkdownRemark['excerpt']
  onClick?: GatsbyLinkProps<Record<string, unknown>>['onClick']
}
const Post: React.FC<Post> = ({ author, className, date, excerpt, featuredImage, onClick, slug, title }) => {
return (<Styled.PostItem className={className}>
<Styled.PostLink to={`/blog/${slug}`} onClick={onClick}>   {featuredImage && <Styled.FeaturedImage src={featuredImage} />}   <Styled.PostContent>
   <Styled.PostContentUpper>
   <h3>{title}</h3>
<Styled.Info>{author && (<Styled.AuthorInfo>
           {author.avatar && <Avatar avatar={author.avatar} />}
           <h4>{author.name}</h4>
      </Styled.AuthorInfo>
     )}
    {date && <h5>{format(new Date(date), 'MMM do yyyy')}</h5>}   </Styled.Info>
    </Styled.PostContentUpper><p>{excerpt}</p></Styled.PostContent>       </Styled.PostLink>
</Styled.PostItem>)
}
export default Post

Мы могли бы захотеть использовать этот компонент в других местах на нашем сайте, так что продолжайте и экспортируйте его из корня почтовой папки с другим файлом index.ts.

// blog/post/index.ts
export { default as Post } from './post'

Нам понадобится компонент для отображения наших вкусных постов, так что сделайте папку стилей в корне components / blog. Как и в примере с публикацией, вы создадите файлы blog.styled.tsx и index.ts внутри папки стилей.

// blog/styles/blog.styled.tsx
import styled from 'styled-components'
export const Blog = styled.div`
   align-items: center;
   background-color: ${({ theme }) => theme.colors.white};
   display: flex;
   justify-content: center;
   min-height: 100vh;
   padding: 1em 0 1em 0;
`

И не забудьте экспортировать:

// blog/styles/index.ts
export * from './blog.styled'

Если наши сообщения представляют собой арахисовое масло внутри сэндвича на странице блога, то компонент блога - это желе. Он использует компонент сетки, который я предоставил для простого, но эффективного хранения сообщений на странице.

// blog/blog.tsx
import * as React from 'react'
import * as Styled from './styles'
import { MarkdownRemark, MarkdownRemarkFrontmatter } from '../../interfaces'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Grid } from '../grid'
import { Post } from './post'
import { faBlog } from '@fortawesome/free-solid-svg-icons'
interface BlogProps {
   posts: MarkdownRemark[]
}
const Blog: React.FC<BlogProps> = ({ posts }) => {
const blogItems = React.useMemo(() => {
   const postsWithFrontMatter = 
   posts.filter(({ frontmatter }) => frontmatter)
if (postsWithFrontMatter.length <= 0) {
   return null
}
return postsWithFrontMatter.map(({ frontmatter, excerpt, id }) => (<Post key={id} {...(frontmatter as MarkdownRemarkFrontmatter)} excerpt={excerpt} />))
}, [posts])
return (<Styled.Blog>
         {blogItems ? (<Grid items={blogItems} style={{ width: '90%'
          }} />) : 
            (<h2>No blog posts yet but check back soon!&nbsp;
             <FontAwesomeIcon icon={faBlog} /></h2>)}
        </Styled.Blog>)
}
export default Blog

Как и любой другой компонент в папке компонентов, я экспортирую его с файлом индекса, чтобы упростить использование групп компонентов в проекте.

// components/blog/index.ts
export { default as Blog } from './blog'
export { Post } from './post'

И это последний раз, когда я прошу вас экспортировать что-то из другого файла, я обещаю. В файле index.ts в корне папки компонентов добавьте эту строку вверху.

// components/index.ts
export * from './blog'

Если вы взглянули на демонстрацию, которую я давал ранее для этого шаблона, вы заметили, что в разделе последней публикации была знакомая статья. В этом руководстве я не буду вдаваться в подробности создания этого раздела с последними сообщениями, но я попрошу вас экспортировать компоненты Blog и Post, чтобы их можно было использовать в другом месте.

Собираем все вместе

Теперь мы закончили самое сложное. У нас есть элементы, необходимые для отображения наших блестящих публикаций, все, что осталось, - это создать страницу для их отображения и хотя бы один образец сообщения, чтобы опробовать его. Найдите папку страниц в src / pages и добавьте файл blog.tsx. Это будет страница, на которой будет отображаться компонент нашего блога и сообщения.

// src/pages/blog.tsx
import * as React from 'react'
import { Blog, Layout, SEO } from '../components'
import { Query } from '../interfaces'
import { graphql } from 'gatsby'
export const query = graphql`
   query {
        allMarkdownRemark {
            totalCount
            edges {
                node {
                   id
                   frontmatter {
                       author {
                       avatar
                       name
                   }
                   slug
                   title
                   date
                   featuredImage
                   }
                   excerpt
               }
           }
       }
   }
`
const BlogPage: React.FC<{ data: 
{ allMarkdownRemark: Query['allMarkdownRemark'] } }> = ({
data: {
   allMarkdownRemark: { edges }
}}) => 
{ return (<Layout>
   <SEO title="Blog" />
   <Blog posts={edges.map(({ node }) => node)} /></Layout>)
}
export default BlogPage

Эта страница будет искать все наши файлы разметки и передавать их компоненту блога в виде сообщений. Если вы перейдете на localhost: 8001 / blog, вы должны увидеть пустую страницу блога с сообщением об отсутствии сообщений.

Настал момент истины, нам нужно сделать образец сообщения, чтобы убедиться, что все работает. Создайте в src / content папку с именем posts и внутри нее файл what-time-is-it.md. Мы будем использовать слова песни «Peanut Butter Jelly Time» в качестве подходящего теста.

---
author: { avatar: 'bannans.png', name: 'Buckwheat Boyz' }
title: 'What time is it?'
slug: 'what-time-is-it'
date: '2/1/2021'
---
It's peanut butter jelly time!
Peanut butter jelly time!
Peanut butter jelly time!
<!-- endexcerpt -->
Now Where he at?
Where he at?
Where he at?
Where he at?
NowThere he go
There he go
There he go
There he go
## Peanut butter jelly [x4]
Do the Peanut butter jelly
Peanut butter jelly
Peanut butter jelly with a baseball bat
Do the Peanut butter jelly
Peanut butter jelly
Peanut butter jelly with a baseball bat
## Chorus
Now break it down and freeze
Take it down to your knees
Now lean back and squeeze
Now get back up and scream
## Chorus
Now sissy walk
Sissy walk
Sissy walk
Sissy walk
Now sissy walk
Sissy walk
Sissy walk
Sissy walk
## Chorus
Now walk walk walk walk
Stomp stomp stomp stomp
Slide slide slide slide
Back it up one more time
Now walk walk walk walk
Stomp stomp stomp stomp
Peanut butter jelly break it down
Throw the ball up swing that bat
Turn your head back and see where it at
Throw the ball up swing that bat
Turn you head back and see where it at
Palm beachpeanut butter
Dade countyjelly
Orlandopeanut butter
Tallahasse jelly
Hold on hold on hold on hold on
"Hey chip man what time is it?"
"I don't know what time it is ray low"
"It's peanut butter jelly time"

Вы должны увидеть, как на странице блога появится наша запись в блоге «сколько времени», и щелкнув по ней, вы сможете узнать, сколько сейчас времени.

Заключение

Теперь вы должны понимать концепции, лежащие в основе запросов к файлам разметки и их преобразования в страницы HMTL. Напомним, что мы добавили и сгенерировали типы для преобразователя Remark в Gatsby. Затем мы создали шаблон для нашей уценки, который преобразует каждый файл в допустимый HTML со стилями. Затем мы настраиваем ловушку создания страниц, которая использует шаблон для рендеринга наших сообщений. И, наконец, мы создали страницу с компонентами блога и сообщений, чтобы отображать эти сообщения для посетителей сайта.

Надеюсь, вам понравился этот урок, и вы узнали несколько вещей по пути. Это моя первая попытка создать шаблон веб-сайта Gatsby, и я хотел бы получить отзывы.

Если вы заблудились или у вас не было времени следовать инструкциям, вы можете получить версию шаблона за 5 долларов по ссылке, которую я указал в начале этого руководства. Он включает в себя весь код, который я здесь рассмотрел, а также еще несколько функций, таких как раздел с последними сообщениями.

Но самое главное, какое арахисовое масло лучший; хрустящий или гладкий? Пусть обсуждение развернется в разделе комментариев, спасибо!