starting to get public pages working

This commit is contained in:
sloane 2025-03-31 09:54:31 -04:00
parent f8859e8070
commit a7a270101d
Signed by: sloanelybutsurely
SSH key fingerprint: SHA256:8SBnwhl+RY3oEyQxy1a9wByPzxWM0x+/Ejc+sIlY5qQ
11 changed files with 209 additions and 22 deletions

View file

@ -31,6 +31,20 @@ defmodule Core.Posts do
|> Core.Repo.all()
end
def get_published_recent_posts(kind) do
Post.Query.recent_posts(kind, @recent_posts_count)
|> Post.Query.published()
|> Core.Repo.all()
end
def publish_date(%Schema.Post{published_at: nil}), do: nil
def publish_date(%Schema.Post{published_at: published_at}) do
published_at
|> DateTime.shift_zone!("America/New_York")
|> DateTime.to_date()
end
def change_post(%Schema.Post{} = post \\ %Schema.Post{}, attrs) do
Post.content_changeset(post, attrs)
end

View file

@ -110,6 +110,13 @@ defmodule Core.Posts.Post do
|> limit(^count)
end
def recent_posts(query \\ base(), kind, count) do
query
|> where_kind(kind)
|> default_order()
|> limit(^count)
end
def where_publish_date_and_slug(query \\ current(), publish_date, slug) do
where(
query,

View file

@ -112,4 +112,85 @@ defmodule Web.CoreComponents do
</button>
"""
end
attr :format, :string, required: true
attr :value, :any, default: nil
attr :formatter, :atom, default: :default
attr :timezone, :string, default: "America/New_York"
attr :global, :global
def timex(%{value: nil} = assigns) do
~H"""
<time datetime="">--</time>
"""
end
def timex(%{value: value, timezone: timezone} = assigns) do
assigns =
assign_new(assigns, :local_value, fn ->
case value do
%DateTime{} = datetime ->
datetime
%NaiveDateTime{} = naive ->
naive
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.shift_zone!(timezone)
end
end)
~H"""
<time
datetime={Timex.format!(@local_value, "{ISO:Extended}")}
title={Timex.format!(@local_value, "{Mshort} {D}, {YYYY}, {h12}:{m} {AM} {Zabbr}")}
{@global}
>
{Timex.format!(@local_value, @format, timex_formatter(@formatter))}
</time>
"""
end
defp timex_formatter(formatter) do
Module.concat(Timex.Format.DateTime.Formatters, :string.titlecase("#{formatter}"))
end
attr :id, :string, required: true
attr :posts, :any, required: true
slot :inner_block, required: true
def post_list(assigns) do
~H"""
<ol id={@id} phx-update={if is_struct(@posts, Phoenix.LiveView.LiveStream), do: "phx-update"}>
<li
:for={{dom_id, item} <- normalize_posts(@posts)}
id={dom_id}
class="flex flex-row justify-between"
>
<span>{render_slot(@inner_block, item)}</span>
<span>
<%= if item.deleted_at do %>
deleted
<% else %>
<%= if item.published_at do %>
<%= case item.kind do %>
<% :blog -> %>
<.timex value={item.published_at} format="{YYYY}-{0M}-{0D}" />
<% :status -> %>
<.timex value={item.published_at} format="{relative}" formatter={:relative} />
<% end %>
<% else %>
draft
<% end %>
<% end %>
</span>
</li>
</ol>
"""
end
defp normalize_posts(%Phoenix.LiveView.LiveStream{} = stream), do: stream
defp normalize_posts(posts) when is_list(posts),
do: Enum.with_index(posts, &{"#{&1.kind}-#{&2}", &1})
end

View file

@ -6,8 +6,8 @@
</.link>
<nav>
<ul class="flex flex-row gap-x-2">
<li><.link href="#">writing</.link></li>
<li><.link href="#">microblog</.link></li>
<li><.link href={~p"/blog"}>writing</.link></li>
<li><.link href={~p"/microblog"}>microblog</.link></li>
</ul>
</nav>
</section>

View file

@ -22,6 +22,12 @@ defmodule Web.PostController do
|> render_post(status)
end
def index(%{assigns: %{kind: kind}} = conn, _params) when kind in ~w[blog status]a do
posts = Core.Posts.get_published_recent_posts(kind)
render(conn, :index, posts: posts)
end
defp render_post(conn, %Schema.Post{kind: :blog} = blog) do
render(conn, :show_blog, blog: blog)
end

View file

@ -2,4 +2,10 @@ defmodule Web.PostHTML do
use Web, :html
embed_templates "post_html/*"
def blog_path(%Schema.Post{} = blog) do
if date = Core.Posts.publish_date(blog) do
~p"/blog/#{date.year}/#{date.month}/#{date.day}/#{blog.slug}"
end
end
end

View file

@ -0,0 +1,8 @@
<.post_list :let={post} id="recent-posts" posts={@posts}>
<%= case post.kind do %>
<% :blog -> %>
<.link navigate={blog_path(post)}>{post.title}</.link>
<% :status -> %>
{post.body}
<% end %>
</.post_list>

View file

@ -23,7 +23,7 @@ defmodule Web.AdminDashboardLive do
<h2 class="font-bold text-xl">recent statuses</h2>
<.link navigate={~p"/admin/posts/new?kind=status"}>new status</.link>
</header>
<.post_list :let={status} id="recent-statuses" stream={@streams.statuses}>
<.post_list :let={status} id="recent-statuses" posts={@streams.statuses}>
<.link navigate={~p"/admin/posts/#{status}"}>{status.body}</.link>
</.post_list>
</section>
@ -33,26 +33,11 @@ defmodule Web.AdminDashboardLive do
<h2 class="font-bold text-xl">recent blogs</h2>
<.link navigate={~p"/admin/posts/new?kind=blog"}>new blog</.link>
</header>
<.post_list :let={blog} id="recent-blogs" stream={@streams.blogs}>
<.post_list :let={blog} id="recent-blogs" posts={@streams.blogs}>
<.link navigate={~p"/admin/posts/#{blog}"}>{blog.title}</.link>
</.post_list>
</section>
</div>
"""
end
attr :id, :string, required: true
attr :stream, :any, required: true
slot :inner_block, required: true
def post_list(assigns) do
~H"""
<ol id={@id} phx-update="stream">
<li :for={{dom_id, item} <- @stream} id={dom_id}>
{render_slot(@inner_block, item)}
</li>
</ol>
"""
end
end

View file

@ -9,7 +9,12 @@ defmodule Web.AdminPostLive do
post = Core.Posts.get!(post_id)
form = Core.Posts.change_post(post, %{}) |> to_form()
socket = assign(socket, post: post, form: form)
socket =
assign(socket,
post: post,
form: form,
page_title: page_title(post, socket.assigns.live_action)
)
{:noreply, socket}
end
@ -19,7 +24,12 @@ defmodule Web.AdminPostLive do
post = %Schema.Post{kind: String.to_existing_atom(kind)}
form = Core.Posts.change_post(post, %{}) |> to_form()
socket = assign(socket, post: post, form: form)
socket =
assign(socket,
post: post,
form: form,
page_title: page_title(post, socket.assigns.live_action)
)
{:noreply, socket}
end
@ -54,6 +64,58 @@ defmodule Web.AdminPostLive do
{:noreply, socket}
end
def handle_event("publish", _, %{assigns: %{post: post}} = socket) do
socket =
case Core.Posts.publish_post(post) do
{:ok, post} ->
assign(socket, post: post)
_ ->
socket
end
{:noreply, socket}
end
def handle_event("unpublish", _, %{assigns: %{post: post}} = socket) do
socket =
case Core.Posts.unpublish_post(post) do
{:ok, post} ->
assign(socket, post: post)
_ ->
socket
end
{:noreply, socket}
end
def handle_event("delete", _, %{assigns: %{post: post}} = socket) do
socket =
case Core.Posts.delete_post(post) do
{:ok, post} ->
assign(socket, post: post)
_ ->
socket
end
{:noreply, socket}
end
def handle_event("undelete", _, %{assigns: %{post: post}} = socket) do
socket =
case Core.Posts.undelete_post(post) do
{:ok, post} ->
assign(socket, post: post)
_ ->
socket
end
{:noreply, socket}
end
def render(assigns) do
~H"""
<main>
@ -73,6 +135,21 @@ defmodule Web.AdminPostLive do
<.button type="submit">save</.button>
</.form>
<%= if @live_action == :edit do %>
<div>
<%= if @post.published_at do %>
<.button phx-click="unpublish">unpublish</.button>
<% else %>
<.button phx-click="publish">publish</.button>
<% end %>
<%= if @post.deleted_at do %>
<.button phx-click="undelete">undelete</.button>
<% else %>
<.button phx-click="delete">delete</.button>
<% end %>
</div>
<% end %>
</main>
"""
end

View file

@ -45,7 +45,9 @@ defmodule Web.Router do
delete "/admin/users/log_out", UserSessionController, :delete
get "/:year/:month/:day/:slug", PostController, :show
get "/blog", PostController, :index, assigns: %{kind: :blog}
get "/blog/:year/:month/:day/:slug", PostController, :show
get "/microblog", PostController, :index, assigns: %{kind: :status}
get "/status/:status_id", PostController, :show
# live_session :current_user, on_mount: [{Web.UserAuth, :mount_current_user}] do

View file

@ -60,6 +60,7 @@ defmodule SlaonelyButSurely.MixProject do
{:boundary, "~> 0.10.4"},
{:tzdata, "~> 1.1"},
{:slugify, "~> 1.3"},
{:timex, "~> 3.7"},
# Added dev and/or test dependencies
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},