diff --git a/lib/philomena/markdown.ex b/lib/philomena/markdown.ex index fbff2a48..cc609f94 100644 --- a/lib/philomena/markdown.ex +++ b/lib/philomena/markdown.ex @@ -2,8 +2,8 @@ defmodule Philomena.Markdown do @markdown_chars ~r/[\*_\[\]\(\)\^`\%\\~<>#\|]/ # When your NIF is loaded, it will override this function. - def to_html(text), do: Philomena.Native.markdown_to_html(text) - def to_html_unsafe(text), do: Philomena.Native.markdown_to_html_unsafe(text) + def to_html(text, replacements), do: Philomena.Native.markdown_to_html(text, replacements) + def to_html_unsafe(text, replacements), do: Philomena.Native.markdown_to_html_unsafe(text, replacements) def escape_markdown(text) do @markdown_chars diff --git a/lib/philomena/native.ex b/lib/philomena/native.ex index 4d9f5ebf..04f700ab 100644 --- a/lib/philomena/native.ex +++ b/lib/philomena/native.ex @@ -2,8 +2,8 @@ defmodule Philomena.Native do use Rustler, otp_app: :philomena # Markdown - def markdown_to_html(_text), do: :erlang.nif_error(:nif_not_loaded) - def markdown_to_html_unsafe(_text), do: :erlang.nif_error(:nif_not_loaded) + def markdown_to_html(_text, _replacements), do: :erlang.nif_error(:nif_not_loaded) + def markdown_to_html_unsafe(_text, _replacements), do: :erlang.nif_error(:nif_not_loaded) # Camo def camo_image_url(_uri), do: :erlang.nif_error(:nif_not_loaded) diff --git a/lib/philomena_web/markdown_renderer.ex b/lib/philomena_web/markdown_renderer.ex index 72e7376a..4ec85c6f 100644 --- a/lib/philomena_web/markdown_renderer.ex +++ b/lib/philomena_web/markdown_renderer.ex @@ -1,7 +1,96 @@ defmodule PhilomenaWeb.MarkdownRenderer do alias Philomena.Markdown + alias Philomena.Images.Image + alias Philomena.Repo + import Phoenix.HTML + import Phoenix.HTML.Link + import Ecto.Query - def render(text, _conn) do - Markdown.to_html(text) + @image_view Module.concat(["PhilomenaWeb.ImageView"]) + + def render(text, conn) do + images = find_images(text) + representations = render_representations(images, conn) + + Markdown.to_html(text, representations) + end + + defp find_images(text) do + Regex.scan(~r/>>(\d+)([tsp])?/, text, capture: :all_but_first) + |> Enum.map(fn matches -> [Enum.at(matches, 0) |> String.to_integer(), Enum.at(matches, 1) || ""] end) + |> Enum.filter(fn m -> Enum.at(m, 0) < 2_147_483_647 end) + end + + defp load_images(images) do + ids = Enum.map(images, fn m -> Enum.at(m, 0) end) + + Image + |> where([i], i.id in ^ids) + |> preload(tags: :aliases) + |> Repo.all() + |> Map.new(&{&1.id, &1}) + end + + defp link_suffix(image) do + cond do + not is_nil(image.duplicate_id) -> + " (merged)" + + image.hidden_from_users -> + " (deleted)" + + true -> + "" + end + end + + defp render_representations(images, conn) do + loaded_images = load_images(images) + + images + |> Enum.map(fn group -> + img = loaded_images[Enum.at(group, 0)] + text = "#{Enum.at(group, 0)}#{Enum.at(group, 1)}" + + rendered = cond do + img != nil -> + case group do + [_id, "p"] when not img.hidden_from_users -> + Phoenix.View.render(@image_view, "_image_target.html", + image: img, + size: :medium, + conn: conn + ) + |> safe_to_string() + [_id, "t"] when not img.hidden_from_users -> + Phoenix.View.render(@image_view, "_image_target.html", + image: img, + size: :small, + conn: conn + ) + |> safe_to_string() + [_id, "s"] when not img.hidden_from_users -> + Phoenix.View.render(@image_view, "_image_target.html", + image: img, + size: :thumb_small, + conn: conn + ) + |> safe_to_string() + [_id, ""] -> + link(">>#{img.id}#{link_suffix(img)}", to: "/images/#{img.id}") + |> safe_to_string() + [_id, suffix] when suffix in ["t", "s", "p"] -> + link(">>#{img.id}#{suffix}#{link_suffix(img)}", to: "/images/#{img.id}") + |> safe_to_string() + [id, suffix] -> # This condition should never trigger, but let's leave it here just in case. + ">>#{id}#{suffix}" + end + true -> + ">>#{text}" + end + + [text, rendered] + end) + |> Map.new(fn [id, html] -> {id, html} end) end end diff --git a/native/philomena/Cargo.lock b/native/philomena/Cargo.lock index 21599c7f..ab588a5e 100644 --- a/native/philomena/Cargo.lock +++ b/native/philomena/Cargo.lock @@ -87,9 +87,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" [[package]] name = "byte-tools" @@ -143,7 +143,7 @@ dependencies = [ [[package]] name = "comrak" version = "0.12.1" -source = "git+https://github.com/philomena-dev/comrak?branch=main#8160b121f3ccfa5b284c03eedb71add59c3fbb9f" +source = "git+https://github.com/philomena-dev/comrak?branch=main#3ecac2425999185fee33991dec6efbe812d82196" dependencies = [ "clap", "entities", @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] @@ -327,9 +327,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" [[package]] name = "line-wrap" @@ -785,9 +785,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -853,9 +853,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -863,9 +863,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", @@ -878,9 +878,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -888,9 +888,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", @@ -901,15 +901,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/native/philomena/src/lib.rs b/native/philomena/src/lib.rs index 6cc393f6..ada6eb4b 100644 --- a/native/philomena/src/lib.rs +++ b/native/philomena/src/lib.rs @@ -1,4 +1,5 @@ use jemallocator::Jemalloc; +use rustler::Term; mod camo; mod markdown; @@ -14,13 +15,13 @@ rustler::init! { // Markdown NIF wrappers. #[rustler::nif(schedule = "DirtyCpu")] -fn markdown_to_html(input: String) -> String { - markdown::to_html(input) +fn markdown_to_html<'a>(input: String, reps: Term<'a>) -> String { + markdown::to_html(input, reps) } #[rustler::nif(schedule = "DirtyCpu")] -fn markdown_to_html_unsafe(input: String) -> String { - markdown::to_html_unsafe(input) +fn markdown_to_html_unsafe<'a>(input: String, reps: Term<'a>) -> String { + markdown::to_html_unsafe(input, reps) } // Camo NIF wrappers. diff --git a/native/philomena/src/markdown.rs b/native/philomena/src/markdown.rs index af74785a..ff2cf0d2 100644 --- a/native/philomena/src/markdown.rs +++ b/native/philomena/src/markdown.rs @@ -1,5 +1,7 @@ use comrak::ComrakOptions; use crate::camo; +use rustler::{MapIterator, Term}; +use std::collections::HashMap; fn common_options() -> ComrakOptions { let mut options = ComrakOptions::default(); @@ -13,21 +15,34 @@ fn common_options() -> ComrakOptions { options.render.hardbreaks = true; options.render.github_pre_lang = true; - options.extension.camoifier = Some(|s| camo::image_url(s).unwrap_or_else(|| String::from(""))); + options.extension.camoifier = Some(|s| camo::image_url(s).unwrap_or(String::from(""))); options } -pub fn to_html(input: String) -> String { +fn map_to_hashmap<'a>(map: Term<'a>) -> Option> { + Some(MapIterator::new(map)?.map(|(key, value)| { + let key: String = key.decode().unwrap_or_else(|_| String::from("")); + let value: String = value.decode().unwrap_or_else(|_| String::from("")); + + (key, value) + }).collect()) +} + +pub fn to_html<'a>(input: String, reps: Term<'a>) -> String { let mut options = common_options(); options.render.escape = true; + options.extension.philomena_replacements = map_to_hashmap(reps); + comrak::markdown_to_html(&input, &options) } -pub fn to_html_unsafe(input: String) -> String { +pub fn to_html_unsafe<'a>(input: String, reps: Term<'a>) -> String { let mut options = common_options(); options.render.unsafe_ = true; + options.extension.philomena_replacements = map_to_hashmap(reps); + comrak::markdown_to_html(&input, &options) }