devel #168
5 changed files with 224 additions and 8 deletions
|
@ -67,7 +67,31 @@ defmodule Chiya.Notes.Note do
|
||||||
end
|
end
|
||||||
|
|
||||||
def note_title(note_content) do
|
def note_title(note_content) do
|
||||||
String.slice(note_content, 0..25)
|
max_length = 25
|
||||||
|
max_words = 7
|
||||||
|
length = String.length(note_content)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
length <= max_length ->
|
||||||
|
note_content
|
||||||
|
|
||||||
|
String.contains?(note_content, ".") ->
|
||||||
|
note_content
|
||||||
|
|> String.split(".")
|
||||||
|
|> List.first()
|
||||||
|
|
||||||
|
true ->
|
||||||
|
note_content
|
||||||
|
|> String.split(" ")
|
||||||
|
|> Enum.reduce_while([], fn word, list ->
|
||||||
|
if Enum.count(list) < max_words do
|
||||||
|
{:cont, list ++ [word]}
|
||||||
|
else
|
||||||
|
{:halt, list}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.join(" ")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|
|
@ -1,13 +1,63 @@
|
||||||
defmodule ChiyaWeb.Markdown do
|
defmodule ChiyaWeb.Markdown do
|
||||||
@options [
|
@moduledoc """
|
||||||
|
Module used for rendering markdown.
|
||||||
|
|
||||||
|
Rendering also collects and replaces internal [references](`Mirage.References`) to other entities like Lists and Tags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Changeset, only: [get_change: 2, put_change: 3]
|
||||||
|
|
||||||
|
defp options() do
|
||||||
|
%Earmark.Options{
|
||||||
|
code_class_prefix: "lang- language-",
|
||||||
footnotes: true,
|
footnotes: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
escape: true,
|
escape: false,
|
||||||
wikilinks: true
|
registered_processors: processors()
|
||||||
]
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders markdown with Earmark.
|
||||||
|
"""
|
||||||
def render(markdown) do
|
def render(markdown) do
|
||||||
markdown
|
markdown
|
||||||
|> Earmark.as_html!(@options)
|
|> ChiyaWeb.References.replace_references()
|
||||||
|
|> Earmark.as_html!(options())
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders markdown to HTML inside a changeset if markdown changed.
|
||||||
|
Markdown field and HTML field can be configured.
|
||||||
|
"""
|
||||||
|
def maybe_render(changeset, markdown_field, html_field) do
|
||||||
|
if markdown = get_change(changeset, markdown_field) do
|
||||||
|
html = render(markdown)
|
||||||
|
put_change(changeset, html_field, html)
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def processors() do
|
||||||
|
heading = fn
|
||||||
|
{_, _, [content], _} = n when is_binary(content) ->
|
||||||
|
slug = Slugger.slugify_downcase(content)
|
||||||
|
Earmark.AstTools.merge_atts_in_node(n, id: slug)
|
||||||
|
|
||||||
|
node ->
|
||||||
|
node
|
||||||
|
end
|
||||||
|
|
||||||
|
[
|
||||||
|
Earmark.TagSpecificProcessors.new([
|
||||||
|
{"h1", heading},
|
||||||
|
{"h2", heading},
|
||||||
|
{"h3", heading},
|
||||||
|
{"h4", heading},
|
||||||
|
{"h5", heading},
|
||||||
|
{"h6", heading}
|
||||||
|
])
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
89
lib/chiya_web/references.ex
Normal file
89
lib/chiya_web/references.ex
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
defmodule ChiyaWeb.References do
|
||||||
|
@moduledoc """
|
||||||
|
Finds and replaces references to entities in a string.
|
||||||
|
|
||||||
|
A reference is an internal link like the following examples:
|
||||||
|
|
||||||
|
* `[[sustainablity]]` -> A note named *Sustainability*
|
||||||
|
* `[[a-long-unfitting-slug|A simple title]]` -> A note named *A long unfitting title*
|
||||||
|
"""
|
||||||
|
|
||||||
|
@reference_regex ~r/\[\[?(?<id>[\d\w-]+)(?:\|(?<title>[\w\d\s'`äöüß]+))?\]\]/
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a list of references in `string`.
|
||||||
|
"""
|
||||||
|
def get_references(nil), do: []
|
||||||
|
|
||||||
|
def get_references(string) do
|
||||||
|
@reference_regex
|
||||||
|
|> Regex.scan(string, capture: :all)
|
||||||
|
|> Enum.map(&map_to_tuple/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Checks each reference returned from `get_references/1` and validates its existence.
|
||||||
|
"""
|
||||||
|
def validate_references(references) do
|
||||||
|
Enum.map(references, fn {placeholder, slug, custom_title} ->
|
||||||
|
note = Chiya.Notes.get_note_by_slug_preloaded(slug)
|
||||||
|
|
||||||
|
valid =
|
||||||
|
case note do
|
||||||
|
nil -> false
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
|
||||||
|
# If a custom title was defined, use it,
|
||||||
|
# otherwise use the note's title
|
||||||
|
title =
|
||||||
|
cond do
|
||||||
|
custom_title != slug ->
|
||||||
|
custom_title
|
||||||
|
|
||||||
|
valid ->
|
||||||
|
note.name
|
||||||
|
|
||||||
|
true ->
|
||||||
|
slug
|
||||||
|
end
|
||||||
|
|
||||||
|
{placeholder, slug, title, valid}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a list of slugs that are referenced in `string`, optionally filtering by `filter_type`.
|
||||||
|
"""
|
||||||
|
def get_reference_ids(string) do
|
||||||
|
string
|
||||||
|
|> get_references()
|
||||||
|
|> Enum.map(fn {_, note_slug, _} -> note_slug end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Finds and replaces references with the matching url in `string`.
|
||||||
|
"""
|
||||||
|
def replace_references(string) do
|
||||||
|
string
|
||||||
|
|> get_references()
|
||||||
|
|> validate_references()
|
||||||
|
|> Enum.reduce(string, fn {placeholder, slug, title, valid}, s ->
|
||||||
|
String.replace(s, placeholder, get_link(slug, title, valid))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_link(slug, title, valid) do
|
||||||
|
"[#{title}](/note/#{slug})#{get_link_class(valid)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_link_class(valid) do
|
||||||
|
if valid, do: "{:.invalid}", else: ""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp map_to_tuple([placeholder, note_slug]),
|
||||||
|
do: {placeholder, note_slug, note_slug}
|
||||||
|
|
||||||
|
defp map_to_tuple([placeholder, note_slug, title]),
|
||||||
|
do: {placeholder, note_slug, title}
|
||||||
|
end
|
28
test/chiya/note_test.exs
Normal file
28
test/chiya/note_test.exs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Chiya.NoteTest do
|
||||||
|
use Chiya.DataCase
|
||||||
|
alias Chiya.Notes.Note
|
||||||
|
|
||||||
|
@content1 "This is a short title"
|
||||||
|
@content2 "This is a title that is a lot longer than the first and does not contain a dot"
|
||||||
|
@content3 "This is a title. It contains dots and should be cut at the first of the dots."
|
||||||
|
@content4 "This used to be some funny title"
|
||||||
|
|
||||||
|
describe "note_title" do
|
||||||
|
test "returns a short text completely" do
|
||||||
|
assert Note.note_title(@content1) == @content1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns a longer text until 7 words " do
|
||||||
|
title = Note.note_title(@content2)
|
||||||
|
assert title == "This is a title that is a"
|
||||||
|
|
||||||
|
title = Note.note_title(@content4)
|
||||||
|
assert title == "This used to be some funny title"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns a longer text until the first dot" do
|
||||||
|
title = Note.note_title(@content3)
|
||||||
|
assert title == "This is a title"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
25
test/chiya_web/markdown_test.exs
Normal file
25
test/chiya_web/markdown_test.exs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule ChiyaWeb.MarkdownTest do
|
||||||
|
use Chiya.DataCase
|
||||||
|
|
||||||
|
alias ChiyaWeb.Markdown
|
||||||
|
|
||||||
|
describe "render/1" do
|
||||||
|
test "renders simple markdown" do
|
||||||
|
html = Markdown.render("# Title")
|
||||||
|
assert html =~ "id=\"title\""
|
||||||
|
assert html =~ "Title"
|
||||||
|
assert html =~ "</h1>"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders a link to a note" do
|
||||||
|
html = Markdown.render("[[foo]]")
|
||||||
|
assert html =~ "/note/foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders a link to a note with custom title" do
|
||||||
|
html = Markdown.render("[[foo|MyFoo]]")
|
||||||
|
assert html =~ "/note/foo"
|
||||||
|
assert html =~ "MyFoo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue