Это продолжение серии статей:

В этой статье речь пойдет про подключение темы к блогу, который мы создали в Блог на Gatsby + Obsidian from scratch ч.1

Мне понравилась вот эта бесплатная тема для блога, но проблема в том, что с ходу у меня ничего не завелось, даже после того, как я конвертировал посты в common markdown, об этом я написал тут: Как конвертировать Obsidian vault в common markdown ?

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

Поэтому мной было принято решение - идти по своему "особому" творческому пути 💩

картинка костыли и велосипеды

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

  • Не отображаются изображения
  • Нет ссылок для пагинации, то есть сам механизм работает, но в интерфейсе это никак не отображается
  • Нигде не задействована метаинформация об авторе
  • Сейчас никак не обрабатываются теги и для них нет никакой странички
  • Нет никаких sitemap и прочих robots.txt seo штук - а они нужны, чтобы то что я пишу, хоть кто-то читал

Исправляем отображение картинок

Опытным путем, исследуя шаблон для блога было установлено, что у меня не хватает плагинов, которые отвечают за обработку и отображение картинок.

Важное отступление: Я в своих заметках использую стандартный способ ссылаться на картинки, вот такой:

![alt text](./images/ some title)

А, не тот, что зачастую используется в Obsidian

![[link-to-image.jpeg]]

Поэтому я не знаю, насколько хорошо будут работать картинки, если вы используете вышеупомянутый синтаксис

Глубже, чем написано тут - я не вникал, потому что установив эти пакеты и поправив gatsby-config.js по аналогии с указанным выше шаблоном - все магическим образом заработало:

  • Устанавливаем необходимые пакеты
npm install gatsby-transformer-sharp gatsby-plugin-sharp gatsby-plugin-image
  • Устанавливаем плагин для корректного отображения gif
npm install gatsby-remark-gifs --legacy-peer-deps
  • Правим gatsby-config.js (привожу, только часть, которая поменялась)

Было

{
	resolve: "gatsby-transformer-remark",
	options: {
		plugins: [
			{
				resolve: 'gatsby-remark-obsidian',
				options: {
					titleToURL: (title) => `/${require('slugify')(title, { lower: true })}/`,
					markdownFolder: `${__dirname}/content/knowledgebase`,
					highlightClassName: 'highlight',
				},
			},
		]
	}
}

Стало

{
        resolve: "gatsby-transformer-remark",
        options: {
            plugins: [
	            'gatsby-remark-gifs',
                {
                  resolve: `gatsby-remark-images`,
                  options: {
                    maxWidth: 630,
                  },
                },
                {
                    resolve: 'gatsby-remark-obsidian',
                    options: {
                        titleToURL: (title) => `/${require('slugify')(title, { lower: true })}/`,
                        markdownFolder: `${__dirname}/content/knowledgebase`,
                        highlightClassName: 'highlight',
                    },
                },
            ]
        }
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    'gatsby-plugin-image',

images-works.gif

Добавляем ссылки для постраничной навигации на главную

Для пагинации уже передаются все необходимые данные через pageContext - это я описывал в первой части этой серии статей, вот так это выглядит в файле gatsby-node.js:

context: {
	limit: POSTS_PER_PAGE,
	skip: i * POSTS_PER_PAGE,
	numPages,
	currentPage: i + 1,
},

Так что напишем самую простую реализацию, пока опустив стили:

Pagination component

src/components/Pagination/Pagination.js

import React from 'react'
import { Link } from 'gatsby'

export const Pagination = ({ pageContext }) => {
    const { currentPage, numPages } = pageContext
    const isFirst = currentPage === 1
    const isLast = currentPage === numPages
    const prevPage = currentPage - 1 === 1 ? '/' : `/page/${currentPage - 1}`
    const nextPage = `/page/${currentPage + 1}`
    
    return (
        <div className="pagination">
        {!isFirst && (
            <Link to={prevPage} rel="prev"></Link>
        )}
        {Array.from({ length: numPages }, (_, i) => (
            <Link
            key={`pagination-number${i + 1}`}
            to={i === 0 ? '/' : `/page/${i + 1}`}
            >
            {i + 1 === currentPage ? (
                <b>{i + 1}</b>
            ) : i + 1}
            </Link>
        ))}
        {!isLast && (
            <Link to={nextPage} rel="next"></Link>
        )}
        </div>
    )
}

src/components/Pagination/index.js

export { Pagination } from './Pagination'

src/templates/BlogList.js

import { Pagination } from '../components/Pagination'

export default function BlogList(props) {
	...
	<Pagination pageContext={props.pageContext} />

Проверяем! pagination test

Добавляем метаинформацию об авторе блога

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

Тогда как запросы с переменными можно писать только компонентах уровня страниц, что мы и делали для страниц со списком постов и страницы поста

UserInfo component

src/componenst/UserInfo.js

import React from 'react'
import { useStaticQuery, graphql } from 'gatsby'

export function UserInfo() {
    const data = useStaticQuery(graphql`
        query {
            site {
                siteMetadata {
                    description
                    title
                    social {
	                    siteUrl
	                    twitter
	                    twitterUsername
                    }
                }
            }
        }
    `);

    const {
        title,
        description,
        author,
    } = data.site.siteMetadata;

    const {
        twitter,
        twitterUsername,
    } = data.site.siteMetadata.social;
    
    return (
        <div>
            <h1>{title}</h1>
            <p>{description}</p>
            <p>{author}</p>
            <a target={'_blank'} href={twitter}>{twitterUsername}</a>
        </div>
    )
}

src/templates/BlogList.js

import { UserInfo } from '../components/UserInfo'

export default function BlogList(props) {
	...
	return (
		<UserInfo />
		...

Теперь на главной блога будет отображаться название и описание блога, а также ссылка на твиттер, позже можно без труда добавить туда еще информации, просто поправив gatsby-config.js

Самое время добавить немного стилей, чтобы блог не выглядел так ущербно. Подключим tailwind, с его помощью можно быстро накинуть классы на рзметку и получить вменяемый внешний вид сайта за достаточно короткое время 😁

Добавляем tailwind

Устанавливаем необходимые пакеты и создаем конфиг для tailwind

npm install -D tailwindcss postcss autoprefixer gatsby-plugin-postcss

npx tailwindcss init -p

После выполнения последней команды у нас в корне проекта появилось два файлика c конфигурациями для tailwind и postcss:

  • tailwind.config.js
  • postcss.config.js

Указываем где хранятся наши компоненты в tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/pages/**/*.{js,jsx,ts,tsx}",
    "./src/components/**/*.{js,jsx,ts,tsx}",
    "./src/templates/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Добавляем в gatsby-config.js в секцию plugins еще один плагин

plugins: [
	'gatsby-plugin-postcss',
	...
]

Создаем файлик src/styles/global.css  и подключаем в него tailwind

@tailwind base;

/*
 * Описываем базовые стили для всего
 * только так мы можем стилизовать статьи
 */
@layer base {
    html {
        @apply tracking-tighter text-slate-800;
    }
    
    h1 {
        @apply mb-6 text-4xl text-black;
    }
    
    h2 {
        @apply mt-4 mb-4 text-2xl font-medium text-black;
    }
    
    h3 {
        @apply mt-2 mb-2 text-xl font-medium text-black;
    }
    
    p {
        @apply text-lg;
    }
    
    .gatsby-resp-image-wrapper {
        @apply mb-2 mt-2;
    }

    small {
        @apply text-sm;
    }
    
    time {
        @apply text-sm;
    }
    
    a {
        @apply text-indigo-600 hover:underline hover:text-indigo-900 transition-all ease-in-out duration-200;
    }

    code {
        @apply bg-sky-100 
    }

    pre {
        @apply bg-sky-100 p-8 rounded-lg my-4;
    }
}

@tailwind components;
@tailwind utilities;

Подключаем стили в файлике gatsby-browser.js. в корне проекта (если нет, то создаем)

import './src/styles/global.css'

Добавляем еще немного стилей

Я решил немного упороться и добавить немного стилей в компоненты UserInfo и Pagination и накинуть соответсвующие классы для контейнеров, которые оборачивают весь контент.

Приведу в пример только часть кода, остальное вы сможете посмотреть в репозитории. Как добавлять классы, можно почитать в документации tailwind - там очень крутой поиск и сама дока очень подробная, что позволяет максимально быстро накидывать классы для того, что вам нужно.

/* src/templates/BlogPost.js */
<div
	className='container mx-auto px-4 mt-16' // Контейнер, центрируется, с внутренними и верхним отступом
	dangerouslySetInnerHTML={{ __html: html }}
/>

/**
 * src/templates/Bloglist.js
 */
export const query = graphql`
	query BlogListQuery($skip: Int!, $limit: Int!) {
	    allMarkdownRemark(
	      sort: { frontmatter: { date: DESC }}
	      limit: $limit
	      skip: $skip
	    ) {
	      edges {
	        node {
	          ...
	          excerpt <---- Добавил поле excerpt для превью текста статьи
	        }
	      }
`

/**
 * Настроил вывод статей ввиде сетки
 * количество колонок меняется 
 * в зависимости от размера экрана 
 */
export default function BlogList(props) {
	...
	<UserInfo />
	<div className='grid xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8'>
	  {edges.map(({ node }) => {
		  const { slug, title } = node.fields;
		  const { date } = node.frontmatter;
		  return (
			  <div className="mb-4" key={slug}>
				  <h2 className="text-bold text-2xl">
					<Link to={`/${slug}/`}>{title}</Link>
				  </h2>
				  <p className="text-gray-500">{node.excerpt}</p>
				  <span className="text-gray-300" >{date}</span>
			  </div>
		  )
	  })}
	</div>

После того, как я немного поколдовал со стилями, получилось вполне приемлемо:

blog list  after tailwind

Подключаем wikilink плагин

Это нужно, чтобы корректно обрабатывать ссылки на другие статьи в формате

[[note name]]

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

npm i @idmyn/gatsby-remark-wiki-link

// gatsby-config.js
{
        resolve: "gatsby-transformer-remark",
        options: {
            plugins: [
			    // ...остальные плагины
                {
                  resolve: "@idmyn/gatsby-remark-wiki-link",
                  options: {
                    pageResolver: (name) => {
                      console.log('name', name);
                      return [name.replace(/ /g, '-').toLowerCase()]
                    },
                    hrefTemplate: (permalink) => {
                      return `/${slufify(permalink, { lower: true })}/`
                    }
                  }
                },

Подключаем prismjs

Последний штрих - подключим prismjs - чтобы красиво отображать фрагменты кода в статьях

npm install gatsby-remark-prismjs prismjs

Добавляем еще один плагин в gatsby-transformer-remark. Подробнее про возможные опции и их назначение можно почитать тут

		// gatsby-config.js
		...
        resolve: "gatsby-transformer-remark",
        options: {
            plugins: [
                {
                  resolve: `gatsby-remark-prismjs`,
                  options: {
                    classPrefix: "language-",
                    aliases: { sh: "bash" }
                  }
                },
                ...
		

Импортируем стили в gatsby-browser.js

// gatsby-browser.js
import 'prismjs/themes/prism-solarizedlight.css';

Теперь сниппеты с кодом выглядят гораздо приятнее: prismjs exmaple

Тем для подсветки кода предоставляется несколько, так что можно поэксперементировать и вместо solarizedlight поставить другую:

  • dark
  • twiilight
  • okaidia
  • tomorrow
  • funky

В следующей части разберемся со страницей тегов, выложим блог и прикрутим аналитику!