- Blog ➔
- Programação ↴
Como importar posts com sintaxe de código colorida no Medium usando o Gatsby
Escrito em 24 de outubro de 2021 - 🕒 5 min. de leituraMedium é uma ótima plataforma para compartilhar posts com um público mais amplo, mas é um pouco irritante que, para ter códigos, você precisa criar um Gist do GitHub (ou outros) e embuti-los ao post.
Como eu escrevo muitos tutoriais de programação no meu blog, adicionar todos os meus códigos um por um ao Gist do GitHub leva muito tempo, e como eu sou muito preguiçoso, vou automatizar isso.
Code snippets no Gatsby
Para adicionar códigos no Gatsby, basta usar o backtick triplo e a linguagem que você está usando, como o exemplo abaixo.
```javascript
console.log('hello world!');
```
Com isso, Gatsby irá gerar um HTML como o abaixo. Isso será importante para mais tarde.
<div class="gatsby-highlight" data-language="javascript">
<pre class="language-javascript">
<code class="language-javascript">
<!-- the span element is not exactly like this, this is just an example -->
<span>console.log('hello world!');</span>
</code>
</pre>
</div>
O problema
O Medium tem uma ferramenta super útil para importar qualquer post de qualquer lugar para um post no Medium, mas infelizmente todos os códigos serão ignorados por esse importe.
Seria perfeito ter uma URL especial para os meus post onde todos os meus códigos fossem substituídos por URLs de Gists do GitHub com aquele código, e isso é exatamente o que farei a seguir 😎.
Criando uma URL especial para o Medium
Eu quero ser capaz de adicionar /medium-import
no final da URL dos meus posts e carregar uma página especial com todos os códigos substituídos por Gists do GitHub.
No arquivo gatsby-node.js
, na função createPages
, eu irei criar uma página adicional com /medium-import
no final da URL.
const posts = postsResult.data.allMarkdownRemark.edges;
for (const post of posts) {
createPage({
path: `${post.node.fields.path}/medium-import`,
component: path.resolve('./src/templates/MediumPost.jsx'),
context: {
mediumHTML: await generateMediumHTML(post.node.html, post.node.frontmatter.title),
},
});
}
Todos os meus posts podem ser acessados via /blog-post-url
e também /blog-post-url/medium-import
agora.
Gerando um HTML diferente para o Medium
Para a função generateMediumHTML
, eu vou usar o querySelectorAll
para encontrar todos os Nodes do HTML com códigos e substituí-los por URLs de Gists do GitHub.
Como todo esse código será executado no Node, eu irei precisar do jsdom para poder manipular o DOM
do HTML.
const jsdom = require('jsdom');
const generateMediumHTML = async (htmlString, postTitle) => {
const gistUrls = await generateGistUrlsForPost(htmlString, postTitle);
const dom = new jsdom.JSDOM(htmlString);
const result = dom.window.document.querySelectorAll('.gatsby-highlight');
result.forEach((element, index) => {
element.textContent = gistUrls[index];
});
return dom.window.document.body.innerHTML;
};
Todos os códigos serão substituídos por <div class="gatsby-highlight" data-language="javascript">https://gist.github.com/some-gist-id</div>
.
Usando a API do Gist
Eu vou usar a API do Gist para duas coisas, para obter todos os meus Gists existentes para evitar a criação do mesmo Gist duas vezes com um request de GET
, e para criar um novo Gist com um request de POST
.
Como o código será executado no Node, usarei o node-fetch para todas as solicitações da API.
const gistAccessToken = process.env.GITHUB_ACCESS_TOKEN;
// Get all existing gists under my github username
const response = await nodeFetch('https://api.github.com/gists', {
method: 'GET',
headers: {
Authorization: `token ${gistAccessToken}`,
'Content-type': 'application/json',
},
});
const gistAccessToken = process.env.GITHUB_ACCESS_TOKEN;
// create a new gist
const response = await nodeFetch('https://api.github.com/gists', {
method: 'POST',
headers: {
Authorization: `token ${gistAccessToken}`,
'Content-type': 'application/json',
},
body: JSON.stringify({
description: 'Code for blog post',
public: true,
files: {
['file-name.js']: {
content: 'console.log("hello world!");',
},
},
}),
});
Para a função generateGistUrlsForPost
, eu irei usar novamente a função querySelectorAll
para obter o código por meio da propriedade textContent
e, em seguida, enviá-lo ao GitHub por meio da API do Gist, para isso irei precisar de um GitHub Personal Access Token.
const generateGistUrlsForPost = async (htmlString, postTitle) => {
const gistAccessToken = process.env.GITHUB_ACCESS_TOKEN;
const dom = new jsdom.JSDOM(htmlString);
const result = dom.window.document.querySelectorAll('.gatsby-highlight > pre > code');
const slug = convertToKebabCase(postTitle);
// Get all existing gists under my github username
const response = await nodeFetch('https://api.github.com/gists', {
method: 'GET',
headers: {
Authorization: `token ${gistAccessToken}`,
'Content-type': 'application/json',
},
});
const responseData = await response.json();
const files = responseData.map((data) => Object.keys(data.files));
const fileNames = files.flat();
const gistUrls = [];
let index = 1;
for (const element of result) {
const code = element.textContent;
const extension = element.getAttribute('data-language');
const fileName = `${slug}-script-${index}.${extension}`;
// if the gist for the file already exists, then don't create a new one
if (fileNames.includes(fileName)) {
const existingGist = responseData.find(
(data) => Object.keys(data.files).includes(fileName)
);
gistUrls.push(existingGist.html_url);
} else {
const res = await nodeFetch('https://api.github.com/gists', {
method: 'POST',
headers: {
Authorization: `token ${gistAccessToken}`,
'Content-type': 'application/json',
},
body: JSON.stringify({
description: `Code for post "${postTitle}"`,
public: true,
files: {
[fileName]: {
content: code,
},
},
}),
});
const data = await res.json();
gistUrls.push(data.html_url);
}
index += 1;
}
return gistUrls;
};
Renderizando o novo HTML
No template do componente React, eu tenho acesso a um novo atributo chamado mediumHTML
dentro do pageContext
, que é o novo HTML com todos os códigos substituídos por URLs do Gist.
import React from 'react';
import { graphql } from 'gatsby';
const MediumPostTemplate = ({ data, pageContext }) => {
const { markdownRemark } = data;
const { title } = markdownRemark.frontmatter;
const { mediumHTML } = pageContext;
return (
<article>
<header>{title}</header>
<section
dangerouslySetInnerHTML={{ __html: mediumHTML }}
/>
</article>
);
};
export default MediumPostTemplate;
export const pageQuery = graphql`
query MediumPostBySlug($slug: String!, $categoryImage: String) {
markdownRemark(fields: { slug: { eq: $slug } }) {
frontmatter {
title
}
}
}
`;
Agora posso usar a ferramenta de importação do Medium para importar qualquer post com a URL /blog-post-url/medium-import
.
Com toda essa automação pronta, espere ver muito mais posts meus no Medium 😊.
Tags:
Posts relacionados
Publicar um comentário
Comentários
Nenhum comentário.