Создание движка для блога с помощью Phoenix и Elixir – Часть 6. Поддержка Markdown

в 23:04, , рубрики: earmark, Elixir, Elixir/Phoenix, Erlang/OTP, phoenix, ruby on rails, wunsh, блог, вунш, Тестирование веб-сервисов

Создание движка для блога с помощью Phoenix и Elixir - Часть 6. Поддержка Markdown - 1

От переводчика: «Elixir и Phoenix — прекрасный пример того, куда движется современная веб-разработка. Уже сейчас эти инструменты предоставляют качественный доступ к технологиям реального времени для веб-приложений. Сайты с повышенной интерактивностью, многопользовательские браузерные игры, микросервисы — те направления, в которых данные технологии сослужат хорошую службу. Далее представлен перевод серии из 11 статей, подробно описывающих аспекты разработки на фреймворке Феникс казалось бы такой тривиальной вещи, как блоговый движок. Но не спешите кукситься, будет действительно интересно, особенно если статьи побудят вас обратить внимание на Эликсир либо стать его последователями.

В этой части мы подключим библиотеку Earmark для добавления возможности использования разметки Markdown.

На данный момент наше приложение основано на:

  • Elixir: v1.3.1
  • Phoenix: v1.2.0
  • Ecto: v2.0.2
  • ExMachina : v1.0.2
  • Earmark : v1.0.1

Где мы остановились

Сейчас в нашей блоговой платформе есть роли, пользователи и посты. Также исправлены некоторые баги, обновлены зависимости и подготовлены начальные данные. Мы получили набор базовой функциональности, который по-настоящему неплох и стабилен. Если вы что-то пропустили либо присоединились только сейчас, то можете взять код из ветки 11092015 в репозитории.

Реализация поддержки Markdown

Вероятно, нам следует дать людям возможность использовать разметку при написании постов. Например, Markdown — язык разметки, который великолепно подходит для ведения блогов, и при этом без проблем поддерживается языком Elixir. Так что давайте включим Markdown через стороннюю библиотеку под названием Earmark. Начнём с добавления новой зависимости внутрь функции deps в файле mix.exs:

{:earmark, "~> 1.0.1"}

А затем выполним команду mix do deps.get, compile, чтобы убедиться в корректности подключения библиотеки. Если всё прошло гладко, мы можем перейти к работе над шаблонами и представлениями.

Обновление представлений

Начнём с предоставления удобного способа для конвертирования текста постов в Markdown. Нам нужно избежать добавления Earmark.to_html(data) по всему проекту, так как не хочется выискивать и переписывать этот код, если мы примем решение использовать другие возможности или настройки Earmark. Для этого мы напишем функцию в файле web/views/post_view.ex, которая станет единой точкой, если потребуется что-то изменить. Откройте этот файл и добавьте следующую функцию:

def markdown(body) do
  body
  |> Earmark.to_html
  |> raw
end

У нас есть исходный текст body, который мы передаём функции to_html из библиотеки Earmark. Так как мы ожидаем получить на выходе HTML, пригодный для размещения в посте, то используем функцию raw. Я знаю, что вы подумали: «Ой, вроде бы функция raw крайне небезопасна!». Ответ будет — «Да, всё так и есть». Проблема в том, что нам нужно получить на выходе сырой html, чтобы суметь его отрендерить. При этом не хочется вырезать некоторые потенциально опасные теги каждый раз, когда мы выводим пост. Так что давайте откроем файл web/models/post.ex и добавим немного кода для решения задачи:

def changeset(struct, params \ %{}) do
  struct
  |> cast(params, [:title, :body])
  |> validate_required([:title, :body])
  |> strip_unsafe_body(params)
end

defp strip_unsafe_body(model, %{"body" => nil}) do
  model
end

defp strip_unsafe_body(model, %{"body" => body}) do
  {:safe, clean_body} = Phoenix.HTML.html_escape(body)
  model |> put_change(:body, clean_body)
end

defp strip_unsafe_body(model, _) do
  model
end

Мы написали приличное количество кода. Теперь давайте обсудим, что он делает. Первым делом мы внесли изменение в функцию changeset. Добавили новый вызов strip_unsafe_body(model, …). Этот вызов, используя сопоставления с образцом, выберет одну из трёх функций. Первая — включает параметр body, равный nil. Вторая — параметр body, который нам нужно очистить. И третья — для контрольного отлова остальных вызовов.

Мы используем функцию put_change для замены текста body, очищенной с помощью функции html_escape из Phoenix.HTML версией. Эта функция принимает текст и возвращает кортеж {:safe, cleaned_up_body} в случае успеха.

Написания кода без тестов огорчает меня, особенно для такого функционала, как здесь. Давайте же протестируем его!

Написание тестов

Так как теперь мы зависим от этого кода, нужно должным образом его протестировать.

defmodule Pxblog.PostTest do
  use Pxblog.ModelCase

  alias Pxblog.Post

  @valid_attrs %{body: "some content", title: "some content"}
  @invalid_attrs %{}

  test "changeset with valid attributes" do
    changeset = Post.changeset(%Post{}, @valid_attrs)
    assert changeset.valid?
  end

  test "changeset with invalid attributes" do
    changeset = Post.changeset(%Post{}, @invalid_attrs)
    refute changeset.valid?
  end

  test "when the body includes a script tag" do
    changeset = Post.changeset(%Post{}, %{@valid_attrs | body: "Hello <script type='javascript'>alert('foo');</script>"})
    refute String.match? get_change(changeset, :body), ~r{<script>}
  end

  test "when the body includes an iframe tag" do
    changeset = Post.changeset(%Post{}, %{@valid_attrs | body: "Hello <iframe src='https://google.com'></iframe>"})
    refute String.match? get_change(changeset, :body), ~r{<iframe>}
  end

  test "body includes no stripped tags" do
    changeset = Post.changeset(%Post{}, @valid_attrs)
    assert get_change(changeset, :body) == @valid_attrs[:body]
  end
end

Для начала нужно изменить объявление @valid_attrs. Мы получаем данные из контроллера в виде словаря со строковыми ключами (не атомами). Если мы ничего не изменим, то тесты упадут.

Затем импортируем get_change/2 из Ecto.Changeset, чтобы доставать изменённое значение из ревизии. Наконец, мы пишем несколько тестов:

  1. Когда в теле поста есть тег script.
  2. Когда в теле поста есть тег iframe.
  3. Когда в теле поста нет недопустимых тегов.

Перезапустим тесты. Они должны быть зелёными. Нам осталось написать ещё один тест, немного базового UI и всё будет готово!

Написание тестов для хелпера Markdown

Нам также нужно добавить небольшой тест на вспомогательную функцию для Markdown, которую мы написали в модуле Post View. Хорошая новость — этот тест очень легко написать.

Для этого создайте файл test/view/post_view_test.exs и заполните его следующим содержимым:

defmodule Pxblog.PostViewTest do
  use Pxblog.ConnCase, async: true

  test "converts markdown to html" do
    {:safe, result} = Pxblog.PostView.markdown("**bold me**")
    assert String.contains? result, "<strong>bold me</strong>"
  end

  test "leaves text with no markdown alone" do
    {:safe, result} = Pxblog.PostView.markdown("leave me alone")
    assert String.contains? result, "leave me alone"
  end
end

Теперь запустите тесты, убедитесь, что они зелёные, и с правкой тестов будет покончено.

Обновление пользовательского интерфейса

Прямо сейчас у нас есть возможность добавлять полноценные посты, но UI не позволяет написать ничего больше одной строчки. Мы изменим стандартную форму, чтобы работать с текстовой областью вместо текстового поля, а также подключим классный редактор.

Откройте файл web/templates/post/form.html.eex и добавьте параметр id со значением "body-editor". Окончательно, строчка должна выглядеть следующим образом:

<%= textarea f, :body, class: "form-control", id: "body-editor" %>

Наконец, добавьте следующий код вниз нашего шаблона формы (файл web/templates/post/form.html.eex):

<link rel="stylesheet" href="//cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<script src="//cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
<script>var simplemde = new SimpleMDE();</script>

После обновления странички, вы должны увидеть очень аккуратный интерфейс для написания постов. Начинает становиться похоже на настоящую блоговую платформу!

Создание движка для блога с помощью Phoenix и Elixir - Часть 6. Поддержка Markdown - 2
Наконец, откройте файл web/templates/post/show.html.eex и замените строчку с выводом тела поста на функцию markdown(body), которую мы определили в представлении.

<%= markdown(@post.body) %>

Заключение

Теперь у нас есть улучшенное средство для редактирования/форматирования постов и редактор, выглядящий гораздо, гораздо приятнее! Возможно, вам захочется немного поправить шаблон, например, добавить возможность писать комментарии. Но комментарии — отличный способ для применения каналов, так что давайте отложим это дело до следующей статьи.

Законченная версия кода из этой части лежит в ветке add_markdown_support нашего репозитория.

Другие части:

  1. Вступление
  2. Авторизация
  3. Добавляем роли
  4. Обрабатываем роли в контроллерах
  5. Подключаем ExMachina
  6. Поддержка Markdown
  7. Скоро...

Не забывайте подписываться на нашу рассылку, в которой мы дважды в неделю выкладываем интересные статьи по Эликсиру на русском языке.

Успехов в изучении!

Автор: jarosluv

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js