WordPress рулит, но я бы хотел, чтобы мой контент был и на других платформах. Некоторые платформы, такие как DEV, используют Markdown, но мне кажется, что мне трудно импортировать свои статьи. Поэтому я создал небольшой сниппет приложения для преобразования статьи в Markdown.
Конечный результат
Просто вставьте URL этого блога в эту маленькую программу reppl.it и посмотрите, как она преобразует статью в большую строку Markdown:
Пакеты
Это решение использует Node.js. В NPM есть несколько отличных пакетов для работы:
- node-fetch — для загрузки HTML. В зависимости от версии Node.js, вам может не понадобиться этот пакет для реализации
fetch
. Я использую версию 2, так как не использую ESM. - linkedom — для разбора HTML в работоспособный DOM. Раньше я использовал jsdom, но переключился по соображениям производительности.
- node-html-markdown — для разбора HTML в markdown.
Установите их следующим образом:
npm install [email protected] linkedom node-html-markdown
npm install -D @types/node-fetch
Простой скрапер
Мы собираемся сделать следующее:
- Получаем текст URL. Разумеется, это HTML.
- Разберите его на узлы DOM.
- Определите узел статьи.
- Преобразуем узел статьи в Markdown.
В результате получаются следующие строки кода:
async function scrape(url: string) {
let f = await fetch(url)
let txt = await f.text()
const { document } = parseHTML(txt)
// custom parsing:
// parseCodeFields(document)
// parseEmbeds(document)
let article = (
document.querySelector('article .entry-content') ||
document.querySelector('article .crayons-article__main') ||
document.querySelector('article') ||
document.querySelector('body'))
let html = article?.innerHTML || ""
let content = NodeHtmlMarkdown.translate(html).trim()
// let header = parseHeader(document)
// content = header + content
return content
}
Поддержка языка кода
Теперь мой WordPress генерирует блоки <pre class="lang-ts"><code></code></pre>
. Похоже, что node-html-markdown принимает только <pre><code class="language-ts></code></pre>
. Теперь это легко исправить, добавив некоторую дополнительную обработку перед преобразованием документа в markdown:
function parseCodeFields(document: Document) {
document.querySelectorAll("pre code").forEach(code => {
let lang = [...code.parentElement?.classList || []]
.filter(x => x.startsWith("lang-"))
.find(x => x)
if(!lang) return
lang = lang.replace("lang-", "language-")
code.classList.add(lang)
})
}
Встраивание богатого содержимого
К счастью, dev.to поддерживает жидкие теги для встраивания богатого контента, такого как repl.it и твиты. Давайте разберем наши элементы iframe
в жидкий тег:
function parseEmbeds(document: Document) {
document.querySelectorAll('iframe').forEach(iframe => {
if (!iframe.src) return
const url = new URL(iframe.src)
const type = url.host
const name = url.pathname
const p = document.createElement("p")
const n = document.createTextNode(`{% ${type} ${name} %}`)
p.appendChild(n)
iframe.parentNode?.insertBefore(p, iframe)
})
}
Это не будет работать для каждого встраивания, но это поможет вам начать.
Поддержка заголовков
Для полноты картины нам также необходимо добавить YAML-заголовок с заголовком, тегами и каноническим URL. Это требует некоторого парсинга, но облегчит работу:
function parseHeader(document: Document) {
let header = '---n'
let title = (document.querySelector('h1')?.textContent || '').trim()
if (title) {
header += `title: ${title}n`
}
let tags = [...document.querySelectorAll(".categories a, .tags a")]
.map(a => (a.textContent || '').trim().toLowerCase())
.filter(t => t)
if (tags.length > 0) {
tags.sort()
let t = [... new Set(tags)].join(", ")
header += `tags: [${t}]n`
}
let canonical = document.querySelector('link[rel=canonical]')?.getAttribute("href")
if (canonical) {
header += `canonical_url: ${canonical}n`
}
header += '---nn'
return header;
}
Заключительные мысли
Мне все еще нужно найти лучший способ определения языка фрагментов кода, чтобы не добавлять их вручную. Когда я смотрю на результат, я точно знаю одно: я буду продолжать использовать WordPress для написания своих блогов, поскольку Markdown не делает их более читабельными!
О, и когда вы читаете этот пост на dev.to: он был создан с помощью этого кода (и да, это супер мета 🤓).
Changelog
- 2022-08-31 Первоначальная статья.
- 2022-01-09 Исправлена поддержка языка для полей кода WordPress (см. Поддержка языка кода).
- 2022-03-09 Исправлено встраивание repl.it через парсинг
iframe
(см. Встраивание богатого контента). - 2022-03-09 Добавлена поддержка заголовков.
- 2022-03-09 Добавлена поддержка заголовков YAML (см. Поддержка заголовков).