139 lines
4.2 KiB
Elixir
139 lines
4.2 KiB
Elixir
defmodule Blogex.Blog do
|
|
@moduledoc """
|
|
Macro to define a blog context backed by NimblePublisher.
|
|
|
|
## Usage
|
|
|
|
In your host application, define one module per blog:
|
|
|
|
defmodule Firehose.EngineeringBlog do
|
|
use Blogex.Blog,
|
|
blog_id: :engineering,
|
|
app: :firehose,
|
|
from: "priv/blog/engineering/**/*.md",
|
|
title: "Engineering Blog",
|
|
description: "Deep dives into our tech stack",
|
|
base_path: "/blog/engineering"
|
|
end
|
|
|
|
defmodule Firehose.ReleaseNotes do
|
|
use Blogex.Blog,
|
|
blog_id: :release_notes,
|
|
app: :firehose,
|
|
from: "priv/blog/release-notes/**/*.md",
|
|
title: "Release Notes",
|
|
description: "What's new in Firehose",
|
|
base_path: "/blog/releases"
|
|
end
|
|
|
|
Each module compiles all markdown posts at build time and exposes
|
|
query functions like `all_posts/0`, `get_post!/1`, `posts_by_tag/1`, etc.
|
|
"""
|
|
|
|
defmacro __using__(opts) do
|
|
blog_id = Keyword.fetch!(opts, :blog_id)
|
|
app = Keyword.fetch!(opts, :app)
|
|
from = Keyword.fetch!(opts, :from)
|
|
title = Keyword.fetch!(opts, :title)
|
|
description = Keyword.get(opts, :description, "")
|
|
base_path = Keyword.fetch!(opts, :base_path)
|
|
highlighters = Keyword.get(opts, :highlighters, [:makeup_elixir, :makeup_erlang])
|
|
|
|
quote do
|
|
alias Blogex.Post
|
|
|
|
use NimblePublisher,
|
|
build: Post,
|
|
from: Application.app_dir(unquote(app), unquote(from)),
|
|
as: :posts,
|
|
highlighters: unquote(highlighters)
|
|
|
|
# Inject the blog_id into each post and sort by descending date
|
|
@posts @posts
|
|
|> Enum.map(&Map.put(&1, :blog, unquote(blog_id)))
|
|
|> Enum.sort_by(& &1.date, {:desc, Date})
|
|
|
|
# Collect all unique tags
|
|
@tags @posts |> Enum.flat_map(& &1.tags) |> Enum.uniq() |> Enum.sort()
|
|
|
|
@blog_id unquote(blog_id)
|
|
@blog_title unquote(title)
|
|
@blog_description unquote(description)
|
|
@blog_base_path unquote(base_path)
|
|
|
|
@doc "Returns the blog identifier atom."
|
|
def blog_id, do: @blog_id
|
|
|
|
@doc "Returns the blog title."
|
|
def title, do: @blog_title
|
|
|
|
@doc "Returns the blog description."
|
|
def description, do: @blog_description
|
|
|
|
@doc "Returns the base URL path for this blog."
|
|
def base_path, do: @blog_base_path
|
|
|
|
@doc "Returns all compiled posts regardless of published status or date."
|
|
def unfiltered_posts, do: @posts
|
|
|
|
@doc "Returns all visible posts, newest first. Drafts are included in dev/test."
|
|
def all_posts do
|
|
today = Date.utc_today()
|
|
|
|
if Blogex.show_drafts?() do
|
|
Enum.filter(@posts, &(not Date.after?(&1.date, today)))
|
|
else
|
|
Enum.filter(@posts, &(&1.published and not Date.after?(&1.date, today)))
|
|
end
|
|
end
|
|
|
|
@doc "Returns the N most recent published posts."
|
|
def recent_posts(n \\ 5), do: Enum.take(all_posts(), n)
|
|
|
|
@doc "Returns all unique tags across all published posts."
|
|
def all_tags do
|
|
all_posts()
|
|
|> Enum.flat_map(& &1.tags)
|
|
|> Enum.uniq()
|
|
|> Enum.sort()
|
|
end
|
|
|
|
@doc "Returns all published posts matching the given tag."
|
|
def posts_by_tag(tag) do
|
|
Enum.filter(all_posts(), fn post -> tag in post.tags end)
|
|
end
|
|
|
|
@doc "Returns a single post by slug/id, or raises."
|
|
def get_post!(id) do
|
|
Enum.find(unfiltered_posts(), &(&1.id == id)) ||
|
|
raise Blogex.NotFoundError, "post #{inspect(id)} not found in #{@blog_id}"
|
|
end
|
|
|
|
@doc "Returns a single post by slug/id, or nil."
|
|
def get_post(id) do
|
|
Enum.find(unfiltered_posts(), &(&1.id == id))
|
|
end
|
|
|
|
@doc "Returns paginated posts. Page is 1-indexed."
|
|
def paginate(page \\ 1, per_page \\ 10) do
|
|
posts = all_posts()
|
|
total = length(posts)
|
|
total_pages = max(ceil(total / per_page), 1)
|
|
|
|
entries =
|
|
posts
|
|
|> Enum.drop((page - 1) * per_page)
|
|
|> Enum.take(per_page)
|
|
|
|
%{
|
|
entries: entries,
|
|
page: page,
|
|
per_page: per_page,
|
|
total_entries: total,
|
|
total_pages: total_pages
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|