Задача: Сохранить статью в формате Markdown

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
Войти в полноэкранный режим Выйти из полноэкранного режима

Простой скрапер

Мы собираемся сделать следующее:

  1. Получаем текст URL. Разумеется, это HTML.
  2. Разберите его на узлы DOM.
  3. Определите узел статьи.
  4. Преобразуем узел статьи в 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 (см. Поддержка заголовков).

Оцените статью
devanswers.ru
Добавить комментарий