Czas zacząć pracę z danymi! W tej części tworzymy artykuły w oparciu o Markdown.
Olaf Sulich
2020-11-23
Na początku wyjaśnijmy sobie czym jest Markdown. Markdown to język znaczników przeznaczony do formatowania tekstu. Możesz go kojarzyć chociażby z README na GitHubie. Jego konstrukcja jest bardzo prosta i przyjemna w użyciu, spójrzmy na przykład:
Pogrubiony tekst - **bold**
Nagłówek pierwszego stopnia - # Nagłówek
Link - [Google](https://google.com)
Nie marnujmy czasu i stwórzmy folder content/
w katalogu głównym. W nim dodajemy również foldery images/
oraz posts/
. W tym drugim utwórzmy plik first-post.md
.
---
title: "My awesome first post"
slug: "first-post"
date: "20-08-2020"
category: "Gear"
image: "../images/light.png"
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Podczas tworzenia artykułów w Markdownie mamy możliwość załączenia dodatkowych informacji w postaci kluczy i wartości. Ta konstrukcja nazywana jest frontmatterem. Będziemy mogli później, za pomocą GraphQL, wyciągnąć te informacje.
Podajemy tutaj tytuł, slug, czyli ścieżkę pod którą ma się wyświetlać artykuł, datę, kategorię i zdjęcie.
Wszystkie zdjęcia znajdziesz w repozytorium na GitHubie.
Artykuł gotowy, to teraz przyszedł czas, żeby odczytać z niego treść. Zanim to jednak zrobimy zainstalujmy specjalny plugin, który nam to umożliwi:
npm install gatsby-transformer-remark
Przejdźmy do naszego pliku konfiguracyjnego i dodajmy plugin:
const path = require(`path`);
module.exports = {
siteMetadata: {
title: "Photo blog",
},
plugins: [
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
`gatsby-transformer-remark`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: path.join(__dirname, `src/assets/images`),
},
},
{
resolve: `gatsby-plugin-sass`,
options: {
postCssPlugins: [
require("tailwindcss"),
require("./tailwind.config.js"),
],
},
},
],
};
Ten plugin parsuje nasze pliki markdown używając procesora Remark. Ostatnią rzeczą, którą musimy zrobić, żeby odczytać artykuł, jest poinformowanie Gatsbiego skąd ma czerpać dane. Robiliśmy to już wcześniej zaczytując obrazki. W tym celu wykorzystujemy plugin gatsby-source-filesystem
, a w nim podajemy ścieżkę do naszych artykułów:
const path = require(`path`);
module.exports = {
siteMetadata: {
title: "Photo blog",
},
plugins: [
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
`gatsby-transformer-remark`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: path.join(__dirname, `src/assets/images`),
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `content`,
path: path.join(__dirname, `content`),
},
},
{
resolve: `gatsby-plugin-sass`,
options: {
postCssPlugins: [
require("tailwindcss"),
require("./tailwind.config.js"),
],
},
},
],
};
Odpalmy serwer developerski i przejdźmy na http://localhost:8000/___graphql. Jeśli wszystko poszło po naszej myśli, to powinniśmy zauważyć nowe pozycje w menu zapytań - allMarkdownRemark
i markdownRemark
.
Załóżmy, że chcemy pobrać wszystkie artykuły, mamy tutaj wiele opcji, ale nas na ten moment interesują dane z frontmattera i oczywiście sama treść.
Wykonajmy zapytanie:
{
allMarkdownRemark {
edges {
node {
frontmatter {
category
date
slug
title
}
html
}
}
}
}
W odpowiedzi dostajemy interesujące nas informacje:
W poprzednich artykułach z tej serii poznałaś/eś specjalne pliki dostarczane przez Gatsbiego - gatsby.config.js
i gatsby-browser.js
. Przyszedł czas na kolejny - gatsby-node.js
. To w nim będziemy dynamicznie tworzyć statyczne strony.
Żeby wygenerować strony potrzebujemy wyeksportować specjalną, asynchroniczną funkcję createPages
:
exports.createPages = async ({ graphql, actions }) => {
...
}
Jak widzisz mamy tutaj dostęp do graphql
, którego wykorzystywaliśmy wcześniej przy zapytaniach i obiektu actions
. Skorzystajmy z tego pierwszego i wykonajmy zapytanie, które zwróci slug
naszego artykułu:
exports.createPages = async ({ graphql, actions }) => {
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
slug
}
}
}
}
}
`);
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const { slug } = node.frontmatter;
console.log(slug);
});
};
Jeśli zrestartujesz teraz serwer developerski, zobaczysz w konsoli slug dla każdego stworzonego artykułu, w naszym przypadku będzie on tylko jeden.
Lećmy dalej, z wspomnianego wcześniej obiektu wyciągnijmy akcję createPage
. Przyjmuje ona obiekt z opcjami, do którego podajemy ścieżkę dla strony, komponent, na którego podstawie ma się zbudować strona oraz kontekst.
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
slug
}
}
}
}
}
`);
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const { slug } = node.frontmatter;
createPage({
path: slug,
component: null,
context: {
slug,
},
});
});
};
Każda dynamicznie generowana strona potrzebuje swojego szablonu. Stwórzmy więc nasz komponent w katalogu templates
. Nazwiemy go postTemplate
.
import React from "react";
import { graphql } from "gatsby";
export const query = graphql`
query($slug: String!) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
frontmatter {
title
}
}
}
`;
const PostTemplate = ({ data }) => {
return <h1>{data.markdownRemark.frontmatter.title}</h1>
};
export default PostTemplate;
Wyjaśnimy co się tutaj stało. W szablonach, tak samo jak na stronach, korzystamy z zapytań GraphQL eksportując je.
W tym zapytaniu pojawiło się jednak coś nowego - zmienne. Zmienną w GraphQL poprzedzamy znakiem dolara i po dwukropku podajemy jej typ. W naszym przypadku jest to zmienna $slug
o typie String!
. W markdownRemark
wyszukujemy artykuł, którego slug
z frontmattera będzie taki sam jak $slug
z naszej zmiennej. Następnie wybieramy tytuł artykułu z frontmattera i renderujemy go w komponencie.
Wróćmy do gatsby-node.js
i wykorzystajmy stworzony template.
const path = require("path");
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const pageTemplate = path.resolve(`./src/templates/postTemplate.js`);
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
slug
}
}
}
}
}
`);
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const { slug } = node.frontmatter;
createPage({
path: slug,
component: pageTemplate,
context: {
slug,
},
});
});
};
Szablon podajemy do funkcji createPage
. Wspominałem Ci wcześniej o kontekście, to właśnie on definiuje jaką zmienną podamy do naszego szablonu! Jest on łącznikiem pomiędzy naszą funkcją a szablonem.
Jeśli zrestartujemy teraz serwer developerki i przejdziemy na http://localhost:8000/first-post, to powinniśmy zobaczyć wygenerowaną dynamicznie stronę:
Dodajmy brakujące dane i nadajemy podstawowe style dla naszego template'u:
import React from "react";
import Img from "gatsby-image";
import styles from "./postTemplate.module.scss";
import { graphql } from "gatsby";
export const query = graphql`
query($slug: String!) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
html
frontmatter {
title
date
category
image {
childImageSharp {
fixed(quality: 100, width: 200, height: 200) {
...GatsbyImageSharpFixed_tracedSVG
}
}
}
}
}
}
`;
const PostTemplate = ({ data }) => {
const { frontmatter, html } = data.markdownRemark;
const { image, category, title, date } = frontmatter;
return (
<article className="mx-auto flex flex-col w-full justify-center items-center my-24 max-w-3xl">
<div className="flex w-full justify-start items-start my-24">
<Img
className="object-cover object-center rounded-lg"
fixed={image.childImageSharp.fixed}
alt=""
/>
<div className="flex flex-col m-3 mx-6 justify-start items-start">
<span className="text-blue-600">{category}</span>
<h1 className="text-3xl">{title}</h1>
<span>{date}</span>
</div>
</div>
<div
className={styles.markdown}
dangerouslySetInnerHTML={{ __html: html }}
/>
</article>
);
};
export default PostTemplate;
I gotowe!
Przygotowałem dla Ciebie małe zadanie, w ramach którego utrwalisz sobie zdobytą dotychczas wiedzę. Twoim zadaniem będzie przygotowanie listy artykułów na stronie głównej. Moja implementacja wygląda w następujący sposób:
Oczywiście możesz tutaj eksperymentować, na przykład stworzyć osobną stronę i na niej wyświetlić wszystkie posty, wszystko zależy od Ciebie! Jeśli napotkasz na jakieś problemy lub po prostu nie chcesz wykonywać tego zadanie, zawsze możesz skorzystać z rozwiązania dostępnego w repozytorium na GitHubie.