Skip to content

Commit

Permalink
Merge pull request #74 from felipelincoln/dev
Browse files Browse the repository at this point in the history
Release v1.1.0
  • Loading branch information
felipelincoln committed Jul 4, 2021
2 parents 2bec0cc + 1b003e5 commit 28dcb2c
Show file tree
Hide file tree
Showing 22 changed files with 542 additions and 241 deletions.
1 change: 1 addition & 0 deletions .env.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GITHUB_API_TOKEN=
4 changes: 3 additions & 1 deletion .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ jobs:
docker build --target build --build-arg MIX_ENV=test --cache-from docker.pkg.github.com/felipelincoln/branchpage/dev-cache:latest --tag branchpage:latest .
- name: Start container
run: docker-compose run -d -e MIX_ENV=test -e CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} -v /branchpage/deps -v /branchpage/_build --name branchpage web /bin/sh
run: |
cp .env.dev .env
docker-compose run -d -e MIX_ENV=test -e CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} -v /branchpage/deps -v /branchpage/_build --name branchpage web /bin/sh
- name: Code format
run: docker exec branchpage mix format --check-formatted
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
![](https://i.ibb.co/ZWgjbS5/Screenshot-from-2021-06-05-11-55-08.png)

## Features
* Tottally free. :money_with_wings:
* Totally free. :money_with_wings:
* Blog can be created in seconds :fast_forward:
* You content is always safe on github :octocat:
* Readers can contribute to your posts :hammer:
Expand Down
58 changes: 32 additions & 26 deletions apps/publishing/lib/publishing/manage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ defmodule Publishing.Manage do

import Ecto.Query

def count_blogs do
Repo.aggregate(Blog, :count, :id)
end

def load_blog!(username) do
db_blog =
Blog
Expand Down Expand Up @@ -68,18 +64,7 @@ defmodule Publishing.Manage do
:fail
end

content = %{
title: article.title,
body: article.body,
preview: article.preview
}

{:ok, _} =
db_article
|> Article.changeset(content)
|> Repo.update()

Map.merge(db_article, content)
%{db_article | body: article.body}
rescue
_error ->
reraise Publishing.PageNotFound, __STACKTRACE__
Expand Down Expand Up @@ -119,12 +104,21 @@ defmodule Publishing.Manage do
{:ok, integration} <- Integration.service(url),
{:ok, username} <- integration.get_username(url),
{:ok, content} <- integration.get_content(url) do
title = Markdown.get_heading(content)
preview = Markdown.get_preview(content)
html = Markdown.get_body(content)
title = Markdown.get_title(content)
description = Markdown.get_description(content)
cover = Markdown.get_cover(content)
html = Markdown.parse(content)
blog = %Blog{username: username}

{:ok, %Article{body: html, preview: preview, title: title, url: url, blog: blog}}
{:ok,
%Article{
body: html,
title: title,
description: description,
cover: cover,
url: url,
blog: blog
}}
else
{:error, :scheme} ->
{:error, "Invalid scheme. Use http or https"}
Expand All @@ -147,13 +141,25 @@ defmodule Publishing.Manage do
end

defp get_platform(url) do
platform_url =
url
|> URI.parse()
|> Map.merge(%{path: "/"})
|> URI.to_string()
url
|> URI.parse()
|> Map.merge(%{path: "/"})
|> URI.to_string()
|> upsert_platform!()
end

defp upsert_platform!(name) do
platform = Repo.get_by(Platform, name: name)

Repo.one(from Platform, where: [name: ^platform_url])
case platform do
nil ->
%Platform{}
|> Platform.changeset(%{name: name})
|> Repo.insert!()

item ->
item
end
end

defp upsert_blog(%Article{} = article) do
Expand Down
26 changes: 22 additions & 4 deletions apps/publishing/lib/publishing/manage/article.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ defmodule Publishing.Manage.Article do
import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
@optional_fields ~w(title preview url blog_id)a
@required_fields ~w()a
@optional_fields ~w(cover blog_id)a
@required_fields ~w(title description url)a

schema "article" do
field :title, :string
field :url, :string
field :preview, :string
field :description, :string
field :cover, :string
field :body, :string, virtual: true

belongs_to :blog, Publishing.Manage.Blog, type: :binary_id
Expand All @@ -24,6 +25,7 @@ defmodule Publishing.Manage.Article do
|> cast(attrs, @optional_fields ++ @required_fields)
|> validate_required(@required_fields)
|> validate_length(:title, max: 255)
|> validate_length(:description, max: 255)
|> assoc_constraint(:blog)
|> unique_constraint(:url)
end
Expand All @@ -32,6 +34,22 @@ defmodule Publishing.Manage.Article do
Prints a message relative to the first error in the `changeset`.
"""
def get_error(%Ecto.Changeset{errors: [{:url, _reason} | _tail]}) do
"This article has already been published!"
"This article has already been published."
end

def get_error(%Ecto.Changeset{errors: [{field, reason} | _tail]}) do
{msg, opts} = reason

error_msg =
Enum.reduce(opts, msg, fn {key, value}, msg ->
String.replace(msg, "%{#{key}}", to_string(value))
end)

field_str =
field
|> to_string()
|> String.capitalize()

field_str <> " " <> error_msg
end
end
128 changes: 82 additions & 46 deletions apps/publishing/lib/publishing/markdown.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,126 @@ defmodule Publishing.Markdown do
Module for handling raw markdown texts.
"""

@preview_length Application.compile_env!(:publishing, :markdown)[:preview_length]
@heading_length Application.compile_env!(:publishing, :markdown)[:heading_length]
@heading_default Application.compile_env!(:publishing, :markdown)[:heading_default]
@heading_tags ["h1", "h2", "h3", "h4", "h5", "h6"]
@heading_default "Untitled"
@description_default ""
@cover_default ""

@doc """
Transform markdown into HMTL performing additional mutations.
## Features
* Removes the first `#` heading
* Add `language-none` to inline and code blocks.
Example:
iex> get_body("# title")
""
iex> parse("# title")
"<h1>\\ntitle</h1>\\n"
iex> get_body("## title")
iex> parse("## title")
"<h2>\\ntitle</h2>\\n"
iex> get_body("`some code`")
iex> parse("`some code`")
"<p>\\n<code class=\\"language-none\\">some code</code></p>\\n"
iex> get_body("```\\nsome code\\n```")
iex> parse("```\\nsome code\\n```")
"<pre><code class=\\"language-none\\">some code</code></pre>\\n"
iex> parse("```elixir\\nsome code\\n```")
"<pre><code class=\\"elixir language-elixir\\">some code</code></pre>\\n"
"""
@spec get_body(String.t()) :: list
def get_body(markdown) do
@spec parse(String.t()) :: list
def parse(markdown) do
markdown
|> to_ast()
|> remove_heading()
|> add_code_class()
|> Earmark.Transform.transform()
end

@doc """
Returns the markdown's main title or the given `default` (optional).
Returns the markdown's title or first subtitle or the given `default` (optional).
Examples:
iex> get_heading("# Hello World!\\nLorem ipsum...")
iex> get_title("# Hello World!\\nLorem ipsum...")
"Hello World!"
iex> get_title("## Hello World!\\nLorem ipsum...")
"Hello World!"
iex> get_heading("Lorem ipsum dolor sit amet...", "Untitled")
iex> get_title("Lorem ipsum dolor sit amet...", "Untitled")
"Untitled"
"""
@spec get_title(String.t()) :: String.t()
def get_title(markdown, default \\ @heading_default) when is_binary(markdown) do
get_content(markdown, default, &title_tags/1)
end

@doc """
Returns the markdown's first paragraph or the given `default` (optional).
Examples:
iex> get_description("# Hello World!\\nLorem ipsum...")
"Lorem ipsum..."
iex> get_description("## Hello World!", "Untitled")
"Untitled"
"""
@spec get_heading(String.t()) :: String.t()
def get_heading(markdown, default \\ @heading_default) when is_binary(markdown) do
with {:ok, ast, _} <- EarmarkParser.as_ast(markdown),
[{"h1", _, [title], _} | _tail] when is_binary(title) <- ast do
title
|> String.slice(0, @heading_length)
|> String.trim()
else
_ -> default
end
@spec get_description(String.t()) :: String.t()
def get_description(markdown, default \\ @description_default) when is_binary(markdown) do
get_content(markdown, default, &paragraph_tag/1)
end

def get_preview(markdown) do
title_size =
"# #{get_heading(markdown)}\n"
|> byte_size

preview_length = @preview_length + title_size

if byte_size(markdown) > preview_length do
markdown
|> String.slice(0, preview_length)
|> String.trim()
|> Kernel.<>(" ...")
|> get_body()
else
markdown
|> String.trim()
|> get_body()
end
@doc """
Returns the markdown's first image or the given `default` (optional).
Examples:
iex> get_cover("# Hello World!\\n![](image.png)")
"image.png"
iex> get_cover("## Hello World!", "img.png")
"img.png"
"""
def get_cover(markdown, default \\ @cover_default) when is_binary(markdown) do
get_content(markdown, default, &image_tag/1)
end

defp get_content(markdown, default, tag_function) do
markdown
|> to_ast()
|> Enum.find(tag_function)
|> tag_content_or_default(default)
end

defp title_tags({tag, _, [content], _}) when tag in @heading_tags and is_binary(content),
do: true

defp title_tags(_), do: false

defp paragraph_tag({"p", _, [content], _}) when is_binary(content), do: true
defp paragraph_tag(_), do: false

defp image_tag({"img", _, _, _}), do: true
defp image_tag({"p", _, [{"img", _, _, _}], _}), do: true
defp image_tag(_), do: false

defp tag_content_or_default({"p", _, [{"img", _, _, _} = img], _}, default) do
tag_content_or_default(img, default)
end

defp tag_content_or_default({"img", attrs, _, _}, _default) do
%{"src" => src} = Map.new(attrs)

src
end

defp tag_content_or_default({_, _, [content], _}, _default), do: content
defp tag_content_or_default(_, default), do: default

defp to_ast(markdown) do
{:ok, ast, _} = EarmarkParser.as_ast(markdown, code_class_prefix: "language-")

ast
end

defp remove_heading([{"h1", _, [_title], _} | tail]), do: tail
defp remove_heading(ast), do: ast

defp add_code_class(ast) do
Earmark.Transform.map_ast(ast, fn
{"code", [], [content], %{}} ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Publishing.Repo.Migrations.AlterArticleTableRenamePreviewColumn do
use Ecto.Migration

def change do
rename table(:article), :preview, to: :description

alter table(:article) do
modify :description, :string
add :cover, :text
end
end
end
Loading

0 comments on commit 28dcb2c

Please sign in to comment.