How to import Gatsby posts into Medium with Gist code snippets
Written in October 24, 2021 - 🕒 5 min. readMedium is a great platform to share your blog posts with a wider audience, but it is a bit annoying that to have code snippets you need to create a GitHub Gist (or others) and embed it into the post.
As I write a lot of tutorials in my blog, adding all my code snippets one by one to GitHub Gist takes a lot of time, and I’m very lazy, so let’s automate that.
Code snippets on Gatsby
To add code snippets in Gatsby, you only need to use the markdown triple backtick and the language that you’re using, like the example below.
```javascript
console.log('hello world!');
```
With that, Gatsby will generate an HTML like the one below. This will be important for later.
<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>
The problem
Medium has a super useful tool to import any post from anywhere into a Medium story, but sadly every code snippet will be ignored by it.
Ideally, I could have a special URL for my blog posts where all my code snippets would be replaced by a GitHub Gist URL with that code, and this is exactly what I’m going to do next 😎.
Creating a special URL for Medium
I want to be able to add /medium-import
to the end of my blog posts’ URL and load a special post page with all code snippets replaced by GitHub Gists.
On the gatsby-node.js
file, in the createPages
function, I will create an extra page with the /medium-import
path in the end.
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),
},
});
}
All my blog posts can be accessed via /blog-post-url
and also /blog-post-url/medium-import
now.
Generating a different HTML for Medium
For the generateMediumHTML
function, I will use the querySelectorAll
to find all the HTML nodes with code snippets and replace them with GitHub Gists URLs.
Since all of this code will be executed on Node, I will need jsdom to be able to manipulate the HTML DOM
.
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;
};
All code snippets will be replaced by <div class="gatsby-highlight" data-language="javascript">https://gist.github.com/some-gist-id</div>
.
Using the GitHub Gist API
I will use the GitHub Gist API for two things, to get all my existing Gists to avoid creating the same Gist twice with a GET
request, and to create a new Gist with a POST
request.
Since the code will be executed in Node, I will use node-fetch for the API requests.
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!");',
},
},
}),
});
For the generateGistUrlsForPost
function, I will again use the querySelectorAll
function to get the code via the textContent
property and then send it to GitHub via the Gist API, for that I will need a 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;
};
Rendering the new HTML
In the React component template, I have access to a new attribute called mediumHTML
inside the pageContext
, and that is the new HTML with all code snippets replaced with GitHub Gists.
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
}
}
}
`;
Now I can go into the Medium import tool and import any post via the /blog-post-url/medium-import
.
With all this automation in place, expect to see a lot more Medium posts from me 😊.
Tags:
Related posts
Post a comment
Comments
No comments yet.