 d1379c4de8
			
		
	
	
		d1379c4de8
		
			
		
	
	
	
	
		
			
			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
 |