numerous fixes, blog index, paging, tag index, post show - all liveview
This commit is contained in:
parent
2218a678b1
commit
27a8c22e9f
10 changed files with 178 additions and 177 deletions
|
@ -47,12 +47,21 @@ body {
|
|||
height: 100%;
|
||||
}
|
||||
a {
|
||||
color: $secondary;
|
||||
color: $gray-100;
|
||||
border-bottom: $secondary 2px solid;
|
||||
text-decoration: none;
|
||||
&.navbar-brand {
|
||||
border-bottom: none;
|
||||
}
|
||||
&:visited {
|
||||
color: $info;
|
||||
.post-title & {
|
||||
color: $gray-100;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
color: $primary;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.border-gray-900 {
|
||||
|
@ -94,6 +103,7 @@ a {
|
|||
|
||||
/* social icons */
|
||||
#social-icons .link-light {
|
||||
border-bottom: none;
|
||||
color: $gray-100;
|
||||
&:hover {
|
||||
color: $primary;
|
||||
|
@ -128,9 +138,10 @@ a {
|
|||
.post-title a {
|
||||
color: $gray-100;
|
||||
text-decoration: none;
|
||||
border-bottom: none;
|
||||
&:hover {
|
||||
color: $primary;
|
||||
text-decoration: underline;
|
||||
border-bottom: $secondary 3px solid;
|
||||
}
|
||||
}
|
||||
.post-lede,
|
||||
|
|
|
@ -3,21 +3,29 @@ defmodule Home73k.Blog do
|
|||
|
||||
Application.ensure_all_started(:earmark)
|
||||
|
||||
posts_paths = "#{Home73k.app_blog_content()}/**/*.md" |> Path.wildcard()
|
||||
post_paths = "#{Home73k.app_blog_content()}/**/*.md" |> Path.wildcard()
|
||||
post_paths_hash = :erlang.md5(post_paths)
|
||||
|
||||
posts =
|
||||
for post_path <- posts_paths do
|
||||
for post_path <- post_paths do
|
||||
@external_resource Path.relative_to_cwd(post_path)
|
||||
Post.parse!(post_path)
|
||||
end
|
||||
|
||||
def __mix_recompile__?() do
|
||||
Path.wildcard("#{Home73k.app_blog_content()}/**/*.md") |> :erlang.md5() != unquote(post_paths_hash)
|
||||
end
|
||||
|
||||
@posts Enum.sort_by(posts, & &1.date, {:desc, Date})
|
||||
@post_count length(@posts)
|
||||
|
||||
@tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort()
|
||||
|
||||
def list_posts, do: @posts
|
||||
def list_tags, do: @tags
|
||||
|
||||
def post_count, do: @post_count
|
||||
|
||||
defmodule NotFoundError do
|
||||
defexception [:message, plug_status: 404]
|
||||
end
|
||||
|
@ -29,10 +37,10 @@ defmodule Home73k.Blog do
|
|||
end
|
||||
end
|
||||
|
||||
# def get_posts_by_tag!(tag) do
|
||||
# case Enum.filter(list_posts(), &(tag in &1.tags)) do
|
||||
# [] -> raise NotFoundError, "posts with tag=#{tag} not found"
|
||||
# posts -> posts
|
||||
# end
|
||||
# end
|
||||
def get_posts_by_tag!(tag) do
|
||||
case Enum.filter(list_posts(), &(tag in &1.tags)) do
|
||||
[] -> raise NotFoundError, "posts with tag=#{tag} not found"
|
||||
posts -> posts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,9 +93,6 @@ defmodule Home73kWeb do
|
|||
# Import SVG Icon helper
|
||||
import Home73kWeb.IconHelpers
|
||||
|
||||
# Import Date formatter helper
|
||||
import Home73kWeb.DateHelpers
|
||||
|
||||
import Home73kWeb.ErrorHelpers
|
||||
import Home73kWeb.Gettext
|
||||
alias Home73kWeb.Router.Helpers, as: Routes
|
||||
|
|
|
@ -3,49 +3,96 @@ defmodule Home73kWeb.BlogLive do
|
|||
|
||||
alias Home73k.Blog
|
||||
|
||||
@page_size 7
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
socket
|
||||
|> live_okreply()
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(_params, _url, socket) do
|
||||
socket
|
||||
|> assign(:page_title, "Blog")
|
||||
|> assign(:posts, Blog.list_posts())
|
||||
def handle_params(params, _url, socket) do
|
||||
socket.assigns.live_action
|
||||
|> init_per_live_action(socket, params)
|
||||
|> live_noreply()
|
||||
end
|
||||
|
||||
# @impl true
|
||||
# def handle_event("suggest", %{"q" => query}, socket) do
|
||||
# {:noreply, assign(socket, results: search(query), query: query)}
|
||||
# end
|
||||
defp page_param_as_int(page) do
|
||||
try do
|
||||
String.to_integer(page)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
# @impl true
|
||||
# def handle_event("search", %{"q" => query}, socket) do
|
||||
# case search(query) do
|
||||
# %{^query => vsn} ->
|
||||
# {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
|
||||
defp raise_not_found(msg), do: raise Home73k.Blog.NotFoundError, msg
|
||||
|
||||
# _ ->
|
||||
# {:noreply,
|
||||
# socket
|
||||
# |> put_flash(:error, "No dependencies found matching \"#{query}\"")
|
||||
# |> assign(results: %{}, query: query)}
|
||||
# end
|
||||
# end
|
||||
defp init_per_live_action(:index, socket, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Blog")
|
||||
|> assign(:posts, get_posts_for_page!(1))
|
||||
|> assign(:page_count, get_page_count())
|
||||
|> assign_prev_next(1)
|
||||
end
|
||||
|
||||
# defp search(query) do
|
||||
# if not Home73kWeb.Endpoint.config(:code_reloader) do
|
||||
# raise "action disabled when not in development"
|
||||
# end
|
||||
defp init_per_live_action(:page, socket, %{"page" => page}) do
|
||||
page_int = page_param_as_int(page)
|
||||
page_count = get_page_count()
|
||||
|
||||
# for {app, desc, vsn} <- Application.started_applications(),
|
||||
# app = to_string(app),
|
||||
# String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
|
||||
# into: %{},
|
||||
# do: {app, vsn}
|
||||
# end
|
||||
cond do
|
||||
is_nil(page_int) || page_int <= 1 ->
|
||||
push_patch(socket, to: Routes.blog_path(socket, :index))
|
||||
|
||||
page_int > page_count ->
|
||||
raise_not_found("there are only #{page_count} pages of posts")
|
||||
|
||||
true ->
|
||||
posts = get_posts_for_page!(page_int)
|
||||
|
||||
socket
|
||||
|> assign(:page_title, "Blog \\ Page #{page}")
|
||||
|> assign(:posts, posts)
|
||||
|> assign(:page_count, page_count)
|
||||
|> assign_prev_next(page_int)
|
||||
end
|
||||
end
|
||||
|
||||
defp init_per_live_action(:show, socket, %{"id" => id}) do
|
||||
post = Blog.get_post_by_id!(id)
|
||||
socket
|
||||
|> assign(:page_title, "Blog \\ post.title")
|
||||
|> assign(:posts, [post])
|
||||
|> assign(:page_count, nil)
|
||||
|> assign_prev_next(0)
|
||||
end
|
||||
|
||||
defp init_per_live_action(:tag, socket, %{"tag" => tag}) do
|
||||
socket
|
||||
|> assign(:page_title, "Blog \\ ##{tag}")
|
||||
|> assign(:posts, Blog.get_posts_by_tag!(tag))
|
||||
|> assign(:page_count, get_page_count())
|
||||
|> assign_prev_next(1)
|
||||
end
|
||||
|
||||
|
||||
defp get_posts_for_page!(1), do: Blog.list_posts() |> Enum.take(@page_size)
|
||||
|
||||
defp get_posts_for_page!(page_int) do
|
||||
Blog.list_posts()
|
||||
|> Stream.chunk_every(@page_size)
|
||||
|> Enum.at(page_int - 1)
|
||||
end
|
||||
|
||||
defp get_page_count, do: Integer.floor_div(Blog.post_count(), @page_size) + rem(Blog.post_count(), @page_size)
|
||||
|
||||
defp assign_prev_next(socket, page_int) do
|
||||
socket
|
||||
|> assign(:page_prev, page_int < socket.assigns.page_count && page_int + 1 || nil)
|
||||
|> assign(:page_next, page_int > 1 && page_int - 1 || nil)
|
||||
end
|
||||
|
||||
|
||||
def format_date(date) do
|
||||
Calendar.strftime(date, "%B %-d, %Y")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,24 +2,34 @@
|
|||
|
||||
<div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 pb-2 mb-4 mt-3">
|
||||
|
||||
<%= if is_nil(@posts) do %>
|
||||
|
||||
<div class="post border-bottom border-gray pb-4 mb-3">
|
||||
|
||||
<h2 class="post-title fs-2 fw-600 mb-2">Nothing found.</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
|
||||
<%= for post <- @posts do %>
|
||||
|
||||
<div class="post border-bottom border-gray pb-4 mb-3">
|
||||
|
||||
<h2 class="post-title fs-2 fw-600 mb-2">
|
||||
<%= live_redirect "#{post.title}", to: Routes.post_path(@socket, :show, post) %>
|
||||
<%= live_redirect "#{post.title}", to: Routes.blog_path(@socket, :show, post) %>
|
||||
</h2>
|
||||
|
||||
<div class="post-date font-monospace text-gray-400 <%= if length(post.tags) == 0, do: "mb-3" %>">
|
||||
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= post.date |> format_date() %>
|
||||
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= format_date(post.date) %>
|
||||
by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
|
||||
</div>
|
||||
|
||||
<%= if length(post.tags) > 0 do %>
|
||||
<div class="post-tags fs-smaller mb-3">
|
||||
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
|
||||
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline text-gray-400"] %>
|
||||
<%= for {tag, i} <- Enum.with_index(post.tags) do %>
|
||||
#<%= tag %><%= i < (length(post.tags) - 1) && "," || "" %>
|
||||
<span class="text-gray-400">#</span><%= live_redirect tag, to: Routes.blog_path(@socket, :tag, tag) %><%= i < (length(post.tags) - 1) && "," || "" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -28,14 +38,40 @@
|
|||
<%= raw post.lede %>
|
||||
</div>
|
||||
|
||||
<%= if @live_action == :show do %>
|
||||
<div class="post-body">
|
||||
<%= raw post.body %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p>
|
||||
<%= live_redirect raw("Read more…"), to: Routes.post_path(@socket, :show, post), class: "fs-6" %>
|
||||
<%= live_redirect raw("Read more…"), to: Routes.blog_path(@socket, :show, post), class: "fs-6" %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<%= if @live_action in [:index, :page] do %>
|
||||
<nav class="d-flex justify-content-between" aria-label="Page navigation">
|
||||
<%= if @page_prev do %>
|
||||
<%= live_patch to: Routes.blog_path(@socket, :page, @page_prev) do %>
|
||||
← Older
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="d-block"></div>
|
||||
<% end %>
|
||||
|
||||
<%= if @page_next do %>
|
||||
<%= live_patch to: @page_next == 1 && Routes.blog_path(@socket, :index) || Routes.blog_path(@socket, :page, @page_next) do %>
|
||||
Newer →
|
||||
<% end %>
|
||||
<% end %>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
defmodule Home73kWeb.PostLive do
|
||||
use Home73kWeb, :live_view
|
||||
|
||||
alias Home73k.Blog
|
||||
|
||||
@impl true
|
||||
def mount(%{"id" => id}, session, socket) do
|
||||
# IO.inspect(params, label: "postlive params")
|
||||
IO.inspect(session, label: "postlive session")
|
||||
|
||||
post = Blog.get_post_by_id!(id)
|
||||
|
||||
socket
|
||||
|> assign(:page_title, "Blog \\ #{post.title}")
|
||||
|> assign(:post, post)
|
||||
|> live_okreply()
|
||||
end
|
||||
|
||||
# @impl true
|
||||
# def handle_params(params, _url, socket) do
|
||||
# socket
|
||||
# |> assign(:page_title, "Blog")
|
||||
# |> assign(:posts, Blog.list_posts())
|
||||
# |> live_noreply()
|
||||
# end
|
||||
|
||||
# @impl true
|
||||
# def handle_event("suggest", %{"q" => query}, socket) do
|
||||
# {:noreply, assign(socket, results: search(query), query: query)}
|
||||
# end
|
||||
|
||||
# @impl true
|
||||
# def handle_event("search", %{"q" => query}, socket) do
|
||||
# case search(query) do
|
||||
# %{^query => vsn} ->
|
||||
# {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
|
||||
|
||||
# _ ->
|
||||
# {:noreply,
|
||||
# socket
|
||||
# |> put_flash(:error, "No dependencies found matching \"#{query}\"")
|
||||
# |> assign(results: %{}, query: query)}
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp search(query) do
|
||||
# if not Home73kWeb.Endpoint.config(:code_reloader) do
|
||||
# raise "action disabled when not in development"
|
||||
# end
|
||||
|
||||
# for {app, desc, vsn} <- Application.started_applications(),
|
||||
# app = to_string(app),
|
||||
# String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
|
||||
# into: %{},
|
||||
# do: {app, vsn}
|
||||
# end
|
||||
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
<main class="container d-flex justify-content-center">
|
||||
|
||||
<div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 pb-2 mb-4 mt-3">
|
||||
|
||||
<div class="post border-bottom border-gray pb-4 mb-3">
|
||||
|
||||
<h2 class="post-title fs-2 fw-normal mb-2"><%= raw @post.title %></h2>
|
||||
|
||||
<div class="post-date font-monospace text-gray-400 <%= if length(@post.tags) == 0, do: "mb-3" %>">
|
||||
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= @post.date |> format_date() %>
|
||||
by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
|
||||
</div>
|
||||
|
||||
<%= if length(@post.tags) > 0 do %>
|
||||
<div class="post-tags fs-smaller mb-3">
|
||||
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
|
||||
<%= for {tag, i} <- Enum.with_index(@post.tags) do %>
|
||||
#<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="post-lede">
|
||||
<%= raw @post.lede %>
|
||||
</div>
|
||||
|
||||
<div class="post-body">
|
||||
<%= raw @post.body %>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
|
@ -17,13 +17,17 @@ defmodule Home73kWeb.Router do
|
|||
scope "/", Home73kWeb do
|
||||
pipe_through :browser
|
||||
|
||||
# Pages
|
||||
get "/", HomeController, :index
|
||||
get "/about", HomeController, :about
|
||||
get "/resume", HomeController, :resume
|
||||
get "/folio", HomeController, :folio
|
||||
|
||||
# Blog
|
||||
live "/blog", BlogLive, :index
|
||||
# live "/blog/page/:page", BlogLive, :older
|
||||
live "/blog/:id", PostLive, :show
|
||||
live "/blog/page/:page", BlogLive, :page
|
||||
live "/blog/tag/:tag", BlogLive, :tag
|
||||
live "/blog/:id", BlogLive, :show
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
defmodule Home73kWeb.DateHelpers do
|
||||
@moduledoc """
|
||||
Formatters for dates
|
||||
"""
|
||||
|
||||
def format_date(date) do
|
||||
Calendar.strftime(date, "%B %-d, %Y")
|
||||
end
|
||||
end
|
|
@ -21,10 +21,11 @@ Naturally I ended up writing some alternate lyrics. There's only two; I never go
|
|||
- ["Florida Cargo" (to the tune of "Florida Kilos")](#florida-cargo)
|
||||
- ["The Other Schooner" (to the tune of "The Other Woman")](#other-schooner)
|
||||
|
||||
#### "Florida Cargo" (to the tune of "Florida Kilos")
|
||||
|
||||
<a name="florida-cargo"></a>
|
||||
|
||||
#### "Florida Cargo" (to the tune of "Florida Kilos")
|
||||
|
||||
White shine, royal sailor, doubloons,
|
||||
Don't you see them gleam,
|
||||
They're special, just for you.
|
||||
|
@ -103,10 +104,10 @@ White shine, royal sailor,
|
|||
Gold teeth, royal sailor, yeah,
|
||||
Drink the night away.
|
||||
|
||||
#### "The Other Schooner" (to the tune of "The Other Woman")
|
||||
|
||||
<a name="other-schooner"></a>
|
||||
|
||||
#### "The Other Schooner" (to the tune of "The Other Woman")
|
||||
|
||||
The other schooner has time to trim and furl her sails
|
||||
The other schooner is perfect where her rival fails
|
||||
And she's never seen with caulking in her hull anywhere
|
||||
|
|
Loading…
Reference in a new issue