From a7a270101d63150b43a14882b89da1296bd597e7 Mon Sep 17 00:00:00 2001 From: sloane <git@sloanelybutsurely.com> Date: Mon, 31 Mar 2025 09:54:31 -0400 Subject: [PATCH] starting to get public pages working --- lib/core/posts.ex | 14 ++++ lib/core/posts/post.ex | 7 ++ lib/web/components/core_components.ex | 81 +++++++++++++++++++ lib/web/components/layouts/app.html.heex | 4 +- lib/web/controllers/post_controller.ex | 6 ++ lib/web/controllers/post_html.ex | 6 ++ lib/web/controllers/post_html/index.html.heex | 8 ++ lib/web/live/admin_dashboard_live.ex | 19 +---- lib/web/live/admin_post_live.ex | 81 ++++++++++++++++++- lib/web/router.ex | 4 +- mix.exs | 1 + 11 files changed, 209 insertions(+), 22 deletions(-) create mode 100644 lib/web/controllers/post_html/index.html.heex diff --git a/lib/core/posts.ex b/lib/core/posts.ex index 5380144..5a00dc6 100644 --- a/lib/core/posts.ex +++ b/lib/core/posts.ex @@ -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 diff --git a/lib/core/posts/post.ex b/lib/core/posts/post.ex index 2a3b3e3..38b51bd 100644 --- a/lib/core/posts/post.ex +++ b/lib/core/posts/post.ex @@ -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, diff --git a/lib/web/components/core_components.ex b/lib/web/components/core_components.ex index 77b5ebf..ad9d967 100644 --- a/lib/web/components/core_components.ex +++ b/lib/web/components/core_components.ex @@ -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 diff --git a/lib/web/components/layouts/app.html.heex b/lib/web/components/layouts/app.html.heex index b5e9694..2ca9351 100644 --- a/lib/web/components/layouts/app.html.heex +++ b/lib/web/components/layouts/app.html.heex @@ -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> diff --git a/lib/web/controllers/post_controller.ex b/lib/web/controllers/post_controller.ex index 5d28516..9765928 100644 --- a/lib/web/controllers/post_controller.ex +++ b/lib/web/controllers/post_controller.ex @@ -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 diff --git a/lib/web/controllers/post_html.ex b/lib/web/controllers/post_html.ex index 0fcc3f9..0183532 100644 --- a/lib/web/controllers/post_html.ex +++ b/lib/web/controllers/post_html.ex @@ -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 diff --git a/lib/web/controllers/post_html/index.html.heex b/lib/web/controllers/post_html/index.html.heex new file mode 100644 index 0000000..8e61040 --- /dev/null +++ b/lib/web/controllers/post_html/index.html.heex @@ -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> diff --git a/lib/web/live/admin_dashboard_live.ex b/lib/web/live/admin_dashboard_live.ex index be54573..8c20527 100644 --- a/lib/web/live/admin_dashboard_live.ex +++ b/lib/web/live/admin_dashboard_live.ex @@ -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 diff --git a/lib/web/live/admin_post_live.ex b/lib/web/live/admin_post_live.ex index 99175f9..41dcc1c 100644 --- a/lib/web/live/admin_post_live.ex +++ b/lib/web/live/admin_post_live.ex @@ -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 diff --git a/lib/web/router.ex b/lib/web/router.ex index a4b51b4..1d23a76 100644 --- a/lib/web/router.ex +++ b/lib/web/router.ex @@ -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 diff --git a/mix.exs b/mix.exs index c9524f2..e0285f1 100644 --- a/mix.exs +++ b/mix.exs @@ -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},