- У Medium уже есть огромная аудитория, а это значит, что новые люди узнают о вашем содержании, которые иначе не нашли бы вас. Вам также не нужно бояться того, что Google снизит рейтинг вашего блога в поисковых системах из-за дублирования контента, потому что мы можем установить каноническую ссылку, единственный источник правды, в Medium, чтобы она указывала обратно на оригинальный пост на собственном сайте. Таким образом, размещение ваших сообщений на обеих платформах не является недостатком для SEO.
- Местный редактор среднего размера - отстой. Редактор даже не позволяет напрямую писать в Markdown. Несмотря на то, что Medium поддерживает Markdown (как мы увидим позже), нет переключателя для переключения между представлениями WYSIWYG / Markdown / HTML. Большинство знакомых мне разработчиков предпочитают писать в Markdown вместо того, чтобы вставлять ссылки / изображения через панель инструментов. Используя подход кросс-публикации, мы можем написать Markdown в нашей знакомой среде - для меня это VS Code + gatsbyjs для перезагрузки в реальном времени.
- Публикация на Medium программным способом проще с точки зрения рабочего процесса. Раньше я пробовал использовать функцию Medium Import Story непосредственно в публикации на моем сайте, которая часто нарушала форматирование при чтении HTML, или для импорта Markdown с помощью таких инструментов, как markdowntomedium, которые работают хорошо, но они загрязняют вашу учетную запись GitHub (частными) сущностями и устанавливают неправильный канонический URL-адрес для сущности GitHub. Теперь, с gatsbyjs или любым другим генератором статических сайтов, мой рабочий процесс выглядит так:
- Напишите сообщение в Markdown
- Отправить на GitHub = ›Netlify git-hook запускает и развертывает мой сайт
- Я запускаю свой скрипт кросс-публикации:
npm run crosspost
, который публикует новое сообщение на Medium
Если это вас убедило, вот как настроить кросс-публикацию на Medium.
Настраивать
Все, что вам нужно, это nodejs
версии 8 или выше и сообщение, написанное на Markdown. Сообщение Markdown должно иметь frontmatter, содержащий теги title, slug и medium,. Например, этот пост выглядит так:
---
title: How to cross-post to Medium
slug: how-to-crosspost-to-medium
medium:
- programming
- javascript
- medium
---
I host my blog ...
Создание учетных данных для скриптов
Вам необходимо настроить приложение в Настройках средней учетной записи и создать токен интеграции, чтобы предоставить вашему скрипту права публиковать сообщения от вашего имени.
1. Создайте приложение.
В настройках канала нажмите Управление приложениями, Новое приложение и введите сведения о приложении, как вам удобно:
Name: Cross-Post script
Description:
Callback URLs: http://example.com/callback/medium // We don't need this, but still it's mandatory
Нажмите "Сохранить" и запишите идентификатор клиента и секрет клиента только что созданного приложения.
2. Создайте токен интеграции.
В настройках среды перейдите в Токены интеграции, заполните описание, нажмите Получить токен интеграции и запишите токен.
Скрипт кросс-поста
А вот и самое интересное - мы готовы создать скрипт node.js. Давайте посмотрим на схему того, что должен делать наш скрипт:
- Прочтите файл Markdown, проанализируйте его в абстрактном синтаксическом дереве (MAST) уценки.
- Получите теги title, slug и medium из фронтматера.
- Перепишите все относительные изображения и URL-адреса ссылок на абсолютные URL-адреса, предшествующие нашему веб-сайту.
- Вставить пользовательскую обратную ссылку в нижний колонтитул на наш исходный пост
- Используйте Medium API, чтобы создать сообщение с измененным содержанием сообщения markdown, правильной канонической ссылкой и тегами.
Здесь я рассмотрю основные идеи, полностью функциональный код можно увидеть на GitHub.
Разбор файла уценки
Мы будем использовать remark
с его многочисленными плагинами для анализа файла Markdown. remark
работает, создавая цепочку плагинов. Таким образом, мы начинаем с содержимого файла, а выходные данные каждого плагина затем передаются в качестве входных данных для следующего плагина в цепочке. Мы будем использовать плагин parse
для анализа файла уценки, затем обработаем возвращенный MAST для анализа узла yaml frontmatter
и, наконец, снова воспользуемся stringify
для воссоздания содержимого уценки из MAST.
var vfile = require('to-vfile')
const frontmatterPlugin = require('remark-frontmatter')
const Remark = require(`remark`)
const parse = require('remark-parse')
const stringify = require('remark-stringify')
const transformPostFromPath = async (filePath) => {
try {
return new Promise((resolve, reject) => {
new Remark()
.data(`settings`, {
commonmark: true,
footnotes: true,
pedantic: true,
})
// creates the MAST from markdown
.use(parse)
// to create a markdown yaml node in the MAST
.use(frontmatterPlugin)
// converts the MAST back to markdown
.use(stringify)
// apply it to the file
.process(vfile.readSync(filePath), function(err, vfile) {
if (err) return reject(err)
const returnValue = {
// this will be the markdown content
content: String(vfile),
}
return resolve(returnValue)
})
})
} catch (ex) {
console.log(ex)
}
}
Мы следуем этой схеме добавления новых настраиваемых плагинов замечаний в цепочку для остальных шагов, которые нам нужно сделать.
Получение главного
Чтобы прочитать переднюю часть и преобразовать ее в объект javascript, мы напишем наш первый remark
плагин. Он проходит проверку MAST для yaml
узлов и создает на их основе объект javascript.
Плагины Remark - это просто функции, которые сами возвращают
transformer
функцию.
const yaml = require('js-yaml')
const Remark = require(`remark`)
const parse = require('remark-parse')
const frontmatterPlugin = require('remark-frontmatter')
const stringify = require('remark-stringify')
const visit = require('unist-util-visit')
async function getFrontmatter(filePath) {
let frontmatter
function frontmatterToJs() {
return function transformer(tree) {
visit(tree, `yaml`, node => {
frontmatter = yaml.load(node.value)
})
}
}
return new Promise((resolve, reject) => {
new Remark()
.data(`settings`, {
commonmark: true,
footnotes: true,
pedantic: true,
})
.use(parse)
.use(frontmatterPlugin)
.use(frontmatterToJs)
.process(vfile.readSync(filePath), function(err) {
if (err) return reject(err)
if (!frontmatter)
return reject(new Error('No frontmatter found in markdown-AST'))
console.log(`Found frontmatter ...`)
return resolve(frontmatter)
})
})
}
Затем наша transformPostFromPath
функция может использовать frontmatter
для извлечения slug
:
const url = require('url')
const transformPostFromPath = async (filePath, transformerPlugin) => {
try {
const frontmatter = await getFrontmatter(filePath)
const siteUrl = `https://cmichel.io`
const { slug } = frontmatter
const postUrl = url.resolve(siteUrl, `/${slug}`)
return new Promise((resolve, reject) => {
new Remark()
// ...
const returnValue = {
content: String(vfile),
frontmatter,
postUrl,
siteUrl,
slug,
}
// ...
}
}
}
Преобразование относительных URL-адресов в абсолютные URL-адреса
Затем нам нужно написать плагин, который преобразует все относительные URL-адреса изображений и ссылок в абсолютные URL-адреса. Причина в том, что изображение, размещенное в нашем собственном блоге, встроено как
![Alt text](./cats.png)
не будет работать в Medium. Поэтому мы будем использовать slug
, чтобы переписать MAST для image
s и link
s, чтобы иметь такую структуру:
![Alt text](https://cmichel.io/how-to-crosspost-to-medium/cats.png)
Код плагина довольно прост, мы снова используем шаблон посетитель, чтобы пройти через MAST и переписать свойство url
этих узлов.
const url = require('url') const visit = require('unist-util-visit') function urlIsRelative(url) { // catches http(s)://example.io but also //example.io const isAbsolute = new RegExp('^([a-z]+://|//)', 'i') return !isAbsolute.test(url) } function joinUrls(siteUrl, slug, relativeUrl) { const siteUrlWithSlug = url.resolve(siteUrl, `/${slug}`) return url.resolve(siteUrlWithSlug, `${relativeUrl}`) } function absoluteUrls(options) { const { siteUrl, slug } = options return transformer function replaceUrl(node) { if (!node.url || !urlIsRelative(node.url)) return const absoluteUrl = joinUrls(siteUrl, slug, node.url) console.log(`\tRewriting link "${node.url}" to "${absoluteUrl}" ...`) node.url = absoluteUrl } function transformer(tree) { console.log(`Rewriting image links ...`) visit(tree, 'image', replaceUrl) console.log(`Rewriting anchor links ...`) visit(tree, 'link', replaceUrl) } }
// use the absoluteUrl plugin const transformPostFromPath = async (filePath, transformerPlugin) => { // ... new Remark() .data(`settings`, { commonmark: true, footnotes: true, pedantic: true, }) .use(parse) .use(frontmatterPlugin) // pass the siteUrl, slug as options .use(absoluteUrls, { siteUrl, slug, postUrl, frontmatter, }) .use(stringify) // ... }
Внедрение пользовательского нижнего колонтитула
Давайте добавим нижний колонтитул к сообщению со средней уценкой, ссылаясь на изначально опубликованное сообщение. Мы просто напишем плагин, который добавляет divider
и paragraph
к дочерним элементам MAST.
const url = require('url')
const createHorizontalRule = () => ({
type: `thematicBreak`,
})
const createReferenceToOriginalPost = postUrl => ({
type: `paragraph`,
children: [
{
type: `text`,
value: `Originally published at `,
},
{
type: 'link',
url: postUrl,
children: [
{
type: 'text',
value: 'cmichel.io',
},
],
},
],
})
const createClapImage = siteUrl => ({
type: `image`,
title: null,
alt: 'Medium Clap',
url: url.resolve(siteUrl, '/images/medium_clap.gif'),
})
function appendFooter(options) {
const { siteUrl, postUrl } = options
return transformer
function transformer(tree) {
tree.children = [
...tree.children,
createHorizontalRule(),
createReferenceToOriginalPost(postUrl),
createClapImage(siteUrl),
]
}
}
// use the plugin like this:
.use(appendFooter, {
siteUrl,
slug,
postUrl,
frontmatter,
})
Использование medium-sdk для публикации преобразованного сообщения
Теперь нам просто нужно вызвать функцию transformPostFromPath
, которая последовательно выполняет все наши remark
плагины и возвращает новый пост как markdown вместе с frontmatter
.
const transformedPost = await transformPostFromPath(path)
const response = await client.createPost(transformedPost)
Реализация среднего клиента выглядит так:
require('dotenv').config()
const medium = require('medium-sdk')
const mediumClient = new medium.MediumClient({
clientId: process.env.MEDIUM_CLIENT_ID,
clientSecret: process.env.MEDIUM_CLIENT_SECRET,
})
mediumClient.setAccessToken(process.env.MEDIUM_ACCESS_TOKEN)
const client = {
createPost({ content, frontmatter, postUrl }) {
return new Promise((resolve, reject) => {
mediumClient.getUser(function(err, user) {
mediumClient.createPost(
{
userId: user.id,
// markdown post
content,
title: frontmatter.title,
canonicalUrl: postUrl,
// tags for medium read out of frontmatter
tags: frontmatter.medium,
// format is Markdown
contentFormat: medium.PostContentFormat.MARKDOWN,
publishStatus: medium.PostPublishStatus.DRAFT,
},
function(err, post) {
if (err) return reject(err)
return resolve(post)
}
)
})
})
},
}
module.exports = client
Вам необходимо установить правильные учетные данные, упомянутые в разделе настройки в .env
файле.
Опять же, полный рабочий код можно увидеть на GitHub. После вызова этой функции у вас должна появиться новая история в вашем аккаунте Medium в виде черновика. 🎉
Улучшения
Написание этих сценариев автоматического пост-генератора вызывает привыкание. Вот еще несколько вещей, которые вы можете сделать:
- Напишите плагин-реплику, который заменяет
code
узлов на суть Codepen / GitHub, чтобы включить подсветку синтаксиса в Medium. - Добавьте подпись / изображение в нижний колонтитул, рекламируя свои продукты.
- Кросс-пост на другие платформы. Следующий пост будет о кросс-постинге в steemit.
Первоначально опубликовано на cmichel.io