It moves bbcode to bbcode_pleroma as the former is owned by kaniini and transfering ownership wasn't done in a timely manner. Closes: https://git.pleroma.social/pleroma/pleroma/issues/1374 Closes: https://git.pleroma.social/pleroma/pleroma/issues/1375
		
			
				
	
	
		
			256 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
# Pleroma: A lightweight social networking server
 | 
						|
# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
 | 
						|
# SPDX-License-Identifier: AGPL-3.0-only
 | 
						|
#
 | 
						|
# This file is derived from Earmark, under the following copyright:
 | 
						|
# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
 | 
						|
# SPDX-License-Identifier: Apache-2.0
 | 
						|
# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
 | 
						|
defmodule Pleroma.EarmarkRenderer do
 | 
						|
  @moduledoc false
 | 
						|
 | 
						|
  alias Earmark.Block
 | 
						|
  alias Earmark.Context
 | 
						|
  alias Earmark.HtmlRenderer
 | 
						|
  alias Earmark.Options
 | 
						|
 | 
						|
  import Earmark.Inline, only: [convert: 3]
 | 
						|
  import Earmark.Helpers.HtmlHelpers
 | 
						|
  import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
 | 
						|
  import Earmark.Context, only: [append: 2, set_value: 2]
 | 
						|
  import Earmark.Options, only: [get_mapper: 1]
 | 
						|
 | 
						|
  @doc false
 | 
						|
  def render(blocks, %Context{options: %Options{}} = context) do
 | 
						|
    messages = get_messages(context)
 | 
						|
 | 
						|
    {contexts, html} =
 | 
						|
      get_mapper(context.options).(
 | 
						|
        blocks,
 | 
						|
        &render_block(&1, put_in(context.options.messages, []))
 | 
						|
      )
 | 
						|
      |> Enum.unzip()
 | 
						|
 | 
						|
    all_messages =
 | 
						|
      contexts
 | 
						|
      |> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
 | 
						|
 | 
						|
    {put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
 | 
						|
  end
 | 
						|
 | 
						|
  #############
 | 
						|
  # Paragraph #
 | 
						|
  #############
 | 
						|
  defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
 | 
						|
    lines = convert(lines, lnb, context)
 | 
						|
    add_attrs(lines, "<p>#{lines.value}</p>", attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  ########
 | 
						|
  # Html #
 | 
						|
  ########
 | 
						|
  defp render_block(%Block.Html{html: html}, context) do
 | 
						|
    {context, html}
 | 
						|
  end
 | 
						|
 | 
						|
  defp render_block(%Block.HtmlComment{lines: lines}, context) do
 | 
						|
    {context, lines}
 | 
						|
  end
 | 
						|
 | 
						|
  defp render_block(%Block.HtmlOneline{html: html}, context) do
 | 
						|
    {context, html}
 | 
						|
  end
 | 
						|
 | 
						|
  #########
 | 
						|
  # Ruler #
 | 
						|
  #########
 | 
						|
  defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
 | 
						|
    add_attrs(context, "<hr />", attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  ###########
 | 
						|
  # Heading #
 | 
						|
  ###########
 | 
						|
  defp render_block(
 | 
						|
         %Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
 | 
						|
         context
 | 
						|
       ) do
 | 
						|
    converted = convert(content, lnb, context)
 | 
						|
    html = "<h#{level}>#{converted.value}</h#{level}>"
 | 
						|
    add_attrs(converted, html, attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  ##############
 | 
						|
  # Blockquote #
 | 
						|
  ##############
 | 
						|
 | 
						|
  defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
 | 
						|
    {context1, body} = render(blocks, context)
 | 
						|
    html = "<blockquote>#{body}</blockquote>"
 | 
						|
    add_attrs(context1, html, attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  #########
 | 
						|
  # Table #
 | 
						|
  #########
 | 
						|
 | 
						|
  defp render_block(
 | 
						|
         %Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
 | 
						|
         context
 | 
						|
       ) do
 | 
						|
    {context1, html} = add_attrs(context, "<table>", attrs, [], lnb)
 | 
						|
    context2 = set_value(context1, html)
 | 
						|
 | 
						|
    context3 =
 | 
						|
      if header do
 | 
						|
        append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>")
 | 
						|
      else
 | 
						|
        # Maybe an error, needed append(context, html)
 | 
						|
        context2
 | 
						|
      end
 | 
						|
 | 
						|
    context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>")
 | 
						|
 | 
						|
    {context4, [context4.value, "</table>"]}
 | 
						|
  end
 | 
						|
 | 
						|
  ########
 | 
						|
  # Code #
 | 
						|
  ########
 | 
						|
 | 
						|
  defp render_block(
 | 
						|
         %Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
 | 
						|
         %Context{options: options} = context
 | 
						|
       ) do
 | 
						|
    class =
 | 
						|
      if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
 | 
						|
 | 
						|
    tag = ~s[<pre><code#{class}>]
 | 
						|
    lines = options.render_code.(block)
 | 
						|
    html = ~s[#{tag}#{lines}</code></pre>]
 | 
						|
    add_attrs(context, html, attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  #########
 | 
						|
  # Lists #
 | 
						|
  #########
 | 
						|
 | 
						|
  defp render_block(
 | 
						|
         %Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
 | 
						|
         context
 | 
						|
       ) do
 | 
						|
    {context1, content} = render(items, context)
 | 
						|
    html = "<#{type}#{start}>#{content}</#{type}>"
 | 
						|
    add_attrs(context1, html, attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  # format a single paragraph list item, and remove the para tags
 | 
						|
  defp render_block(
 | 
						|
         %Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
 | 
						|
         context
 | 
						|
       )
 | 
						|
       when length(blocks) == 1 do
 | 
						|
    {context1, content} = render(blocks, context)
 | 
						|
    content = Regex.replace(~r{</?p>}, content, "")
 | 
						|
    html = "<li>#{content}</li>"
 | 
						|
    add_attrs(context1, html, attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  # format a spaced list item
 | 
						|
  defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
 | 
						|
    {context1, content} = render(blocks, context)
 | 
						|
    html = "<li>#{content}</li>"
 | 
						|
    add_attrs(context1, html, attrs, [], lnb)
 | 
						|
  end
 | 
						|
 | 
						|
  ##################
 | 
						|
  # Footnote Block #
 | 
						|
  ##################
 | 
						|
 | 
						|
  defp render_block(%Block.FnList{blocks: footnotes}, context) do
 | 
						|
    items =
 | 
						|
      Enum.map(footnotes, fn note ->
 | 
						|
        blocks = append_footnote_link(note)
 | 
						|
        %Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
 | 
						|
      end)
 | 
						|
 | 
						|
    {context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
 | 
						|
    {context1, Enum.join([~s[<div class="footnotes">], "<hr />", html, "</div>"])}
 | 
						|
  end
 | 
						|
 | 
						|
  #######################################
 | 
						|
  # Isolated IALs are rendered as paras #
 | 
						|
  #######################################
 | 
						|
 | 
						|
  defp render_block(%Block.Ial{verbatim: verbatim}, context) do
 | 
						|
    {context, "<p>{:#{verbatim}}</p>"}
 | 
						|
  end
 | 
						|
 | 
						|
  ####################
 | 
						|
  # IDDef is ignored #
 | 
						|
  ####################
 | 
						|
 | 
						|
  defp render_block(%Block.IdDef{}, context), do: {context, ""}
 | 
						|
 | 
						|
  #####################################
 | 
						|
  # And here are the inline renderers #
 | 
						|
  #####################################
 | 
						|
 | 
						|
  defdelegate br, to: HtmlRenderer
 | 
						|
  defdelegate codespan(text), to: HtmlRenderer
 | 
						|
  defdelegate em(text), to: HtmlRenderer
 | 
						|
  defdelegate strong(text), to: HtmlRenderer
 | 
						|
  defdelegate strikethrough(text), to: HtmlRenderer
 | 
						|
 | 
						|
  defdelegate link(url, text), to: HtmlRenderer
 | 
						|
  defdelegate link(url, text, title), to: HtmlRenderer
 | 
						|
 | 
						|
  defdelegate image(path, alt, title), to: HtmlRenderer
 | 
						|
 | 
						|
  defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
 | 
						|
 | 
						|
  # Table rows
 | 
						|
  defp add_trs(context, rows, tag, aligns, lnb) do
 | 
						|
    numbered_rows =
 | 
						|
      rows
 | 
						|
      |> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
 | 
						|
 | 
						|
    numbered_rows
 | 
						|
    |> Enum.reduce(context, fn {row, lnb}, ctx ->
 | 
						|
      append(add_tds(append(ctx, "<tr>"), row, tag, aligns, lnb), "</tr>")
 | 
						|
    end)
 | 
						|
  end
 | 
						|
 | 
						|
  defp add_tds(context, row, tag, aligns, lnb) do
 | 
						|
    Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
 | 
						|
  end
 | 
						|
 | 
						|
  defp add_td_fn(row, tag, aligns, lnb) do
 | 
						|
    fn n, ctx ->
 | 
						|
      style =
 | 
						|
        case Enum.at(aligns, n - 1, :default) do
 | 
						|
          :default -> ""
 | 
						|
          align -> " style=\"text-align: #{align}\""
 | 
						|
        end
 | 
						|
 | 
						|
      col = Enum.at(row, n - 1)
 | 
						|
      converted = convert(col, lnb, set_messages(ctx, []))
 | 
						|
      append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}</#{tag}>")
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  ###############################
 | 
						|
  # Append Footnote Return Link #
 | 
						|
  ###############################
 | 
						|
 | 
						|
  defdelegate append_footnote_link(note), to: HtmlRenderer
 | 
						|
  defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
 | 
						|
 | 
						|
  defdelegate render_code(lines), to: HtmlRenderer
 | 
						|
 | 
						|
  defp code_classes(language, prefix) do
 | 
						|
    ["" | String.split(prefix || "")]
 | 
						|
    |> Enum.map(fn pfx -> "#{pfx}#{language}" end)
 | 
						|
    |> Enum.join(" ")
 | 
						|
  end
 | 
						|
end
 |