diff --git a/lib/core.ex b/lib/core.ex index 3071bda..3ce5c5e 100644 --- a/lib/core.ex +++ b/lib/core.ex @@ -1,4 +1,4 @@ defmodule Core do @moduledoc false - use Boundary, deps: [Schema], exports: [Accounts, Posts, Author] + use Boundary, deps: [Schema], exports: [Accounts, Posts, Author, DateTime] end diff --git a/lib/core/date_time.ex b/lib/core/date_time.ex new file mode 100644 index 0000000..6ee4ef1 --- /dev/null +++ b/lib/core/date_time.ex @@ -0,0 +1,9 @@ +defmodule Core.DateTime do + @local_time_zone "America/New_York" + + def to_local_time(nil), do: nil + + def to_local_time(date_time) do + DateTime.shift_zone!(date_time, @local_time_zone) + end +end diff --git a/lib/core/posts.ex b/lib/core/posts.ex index 832fd0a..cd5e84a 100644 --- a/lib/core/posts.ex +++ b/lib/core/posts.ex @@ -50,11 +50,15 @@ defmodule Core.Posts do |> Flop.validate_and_run(params, for: Schema.Post) end - def publish_date(%Schema.Post{published_at: nil}), do: nil + def publish_date_time(%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") + def publish_date_time(%Schema.Post{published_at: published_at}) do + Core.DateTime.to_local_time(published_at) + end + + def publish_date(%Schema.Post{} = post) do + post + |> publish_date_time() |> DateTime.to_date() end diff --git a/lib/core/posts/post.ex b/lib/core/posts/post.ex index 38b51bd..45cb7e1 100644 --- a/lib/core/posts/post.ex +++ b/lib/core/posts/post.ex @@ -6,7 +6,7 @@ defmodule Core.Posts.Post do def content_changeset(%Schema.Post{} = post, attrs) do changeset = post - |> cast(attrs, [:tid, :kind, :slug, :title, :body]) + |> cast(attrs, [:tid, :kind, :slug, :title, :body, :deleted_at, :published_at]) |> validate_required([:kind], message: "must have a kind") |> validate_required([:body], message: "must have a body") @@ -40,7 +40,9 @@ defmodule Core.Posts.Post do changeset = change(post) if is_nil(get_field(changeset, :published_at)) do - put_change(changeset, :published_at, published_at) + changeset + |> put_change(:deleted_at, nil) + |> put_change(:published_at, published_at) else changeset end @@ -50,7 +52,9 @@ defmodule Core.Posts.Post do changeset = change(post) if is_nil(get_field(changeset, :deleted_at)) do - put_change(changeset, :deleted_at, deleted_at) + changeset + |> put_change(:published_at, nil) + |> put_change(:deleted_at, deleted_at) else changeset end @@ -81,7 +85,12 @@ defmodule Core.Posts.Post do end def default_order(query \\ base()) do - order_by(query, [posts: p], desc: :inserted_at, desc: :updated_at) + order_by(query, [posts: p], + desc: :published_at, + desc: :inserted_at, + desc: :updated_at, + desc: :id + ) end def where_kind(query \\ base(), kind) do diff --git a/lib/schema/post.ex b/lib/schema/post.ex index 033c38c..94ed07f 100644 --- a/lib/schema/post.ex +++ b/lib/schema/post.ex @@ -5,11 +5,11 @@ defmodule Schema.Post do @derive { Flop.Schema, filterable: [], - sortable: [:published_at, :id], + sortable: [:published_at, :inserted_at, :updated_at, :id], pagination_types: [:first, :last], default_order: %{ - order_by: [:published_at, :id], - order_directions: [:desc, :desc] + order_by: [:published_at, :inserted_at, :updated_at, :id], + order_directions: [:desc, :desc, :desc, :desc] }, default_pagination_type: :first, default_limit: 20, diff --git a/lib/web/components/core_components.ex b/lib/web/components/core_components.ex index 8d1b43a..5c2d348 100644 --- a/lib/web/components/core_components.ex +++ b/lib/web/components/core_components.ex @@ -10,7 +10,8 @@ defmodule Web.CoreComponents do attr :name, :any attr :label, :string, default: nil attr :value, :any - attr :type, :string, default: "text", values: ~w[text password textarea] + attr :class, :string, default: nil + attr :type, :string, default: "text", values: ~w[text password textarea datetime-local] attr :field, FormField attr :errors, :list, default: [] attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required] @@ -36,9 +37,9 @@ defmodule Web.CoreComponents do def input(%{type: "textarea"} = assigns) do ~H""" - <div> + <div class={["flex flex-col", @class]}> <.label for={@id}>{@label}</.label> - <textarea id={@id} name={@name}>{Phoenix.HTML.Form.normalize_value(@type, @value)}</textarea> + <textarea id={@id} name={@name} class="h-80">{Phoenix.HTML.Form.normalize_value(@type, @value)}</textarea> <.error :for={error <- @errors}>{error}</.error> </div> """ @@ -46,7 +47,7 @@ defmodule Web.CoreComponents do def input(assigns) do ~H""" - <div> + <div class="flex flex-col"> <.label for={@id}>{@label}</.label> <input id={@id} diff --git a/lib/web/controllers/blog_html/index.html.heex b/lib/web/controllers/blog_html/index.html.heex index 75e183b..feca9d7 100644 --- a/lib/web/controllers/blog_html/index.html.heex +++ b/lib/web/controllers/blog_html/index.html.heex @@ -16,7 +16,7 @@ > <h3 class="p-name u-url">{blog.title}</h3> <div class="text-gray-500 dt-published"> - <.timex value={blog.published_at} format="{Mfull} {D}" /> + <.timex value={Core.Posts.publish_date_time(blog)} format="{Mfull} {D}" /> </div> </.link> </article> diff --git a/lib/web/controllers/blog_html/show.html.heex b/lib/web/controllers/blog_html/show.html.heex index 3955b33..2ef6229 100644 --- a/lib/web/controllers/blog_html/show.html.heex +++ b/lib/web/controllers/blog_html/show.html.heex @@ -3,10 +3,18 @@ <header class="mb-4"> <h1 class="p-name text-2xl font-bold mb-2">{@blog.title}</h1> <%= if @blog.published_at do %> - <div class="text-sm text-gray-500 flex items-center"> + <div class="text-sm text-gray-500 flex items-center gap-x-2"> <span class="p-author h-card">{Core.Author.get(:name)}</span> - <span class="mx-2">·</span> - <.timex value={@blog.published_at} format="{Mfull} {D}, {YYYY}" class="dt-published" /> + <span>·</span> + <.timex + value={Core.Posts.publish_date_time(@blog)} + format="{Mfull} {D}, {YYYY}" + class="dt-published" + /> + <%= if @current_user do %> + <span>·</span> + <.link navigate={~p"/admin/posts/#{@blog}"}>edit</.link> + <% end %> </div> <% end %> </header> diff --git a/lib/web/controllers/page_html/home.html.heex b/lib/web/controllers/page_html/home.html.heex index 76dd544..abf61e3 100644 --- a/lib/web/controllers/page_html/home.html.heex +++ b/lib/web/controllers/page_html/home.html.heex @@ -18,7 +18,7 @@ > <h3 class="p-name u-url">{blog.title}</h3> <div class="text-gray-500 dt-published"> - <.timex value={blog.published_at} format="{Mfull} {D}" /> + <.timex value={Core.Posts.publish_date_time(blog)} format="{Mfull} {D}" /> </div> </.link> </article> diff --git a/lib/web/controllers/status_html.ex b/lib/web/controllers/status_html.ex index 627cc8f..fb49a00 100644 --- a/lib/web/controllers/status_html.ex +++ b/lib/web/controllers/status_html.ex @@ -49,7 +49,7 @@ defmodule Web.StatusHTML do <span class="mx-2 text-sm text-gray-500">·</span> <div class="text-sm text-gray-500"> <.timex - value={@status.published_at} + value={Core.Posts.publish_date_time(@status)} format="{relative}" formatter={:relative} class="dt-published" diff --git a/lib/web/controllers/status_html/show.html.heex b/lib/web/controllers/status_html/show.html.heex index 093462c..bcd4982 100644 --- a/lib/web/controllers/status_html/show.html.heex +++ b/lib/web/controllers/status_html/show.html.heex @@ -10,7 +10,7 @@ </div> <article class="h-entry p-4 border border-gray-200"> - <header class="flex items-center mb-3"> + <header class="flex mb-3 justify-between"> <div class="h-card flex items-center gap-3 p-author"> <%= if Core.Author.get(:avatar_url) do %> <img @@ -34,6 +34,10 @@ </p> </div> </div> + + <%= if @current_user do %> + <.link class="text-sm text-gray-500" navigate={~p"/admin/posts/#{@status}"}>edit</.link> + <% end %> </header> <div class="e-content my-4"> @@ -43,7 +47,7 @@ <footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500"> <%= if @status.published_at do %> <.timex - value={@status.published_at} + value={Core.Posts.publish_date_time(@status)} format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}" class="dt-published" /> diff --git a/lib/web/live/admin_post_live.ex b/lib/web/live/admin_post_live.ex index 7970e80..54d0366 100644 --- a/lib/web/live/admin_post_live.ex +++ b/lib/web/live/admin_post_live.ex @@ -53,7 +53,13 @@ defmodule Web.AdminPostLive do socket = case Core.Posts.create_or_update_post(post, attrs) do {:ok, post} -> + form = + post + |> Core.Posts.change_post(%{}) + |> to_form() + socket + |> assign(post: post, form: form) |> put_flash(:info, "post saved") |> push_patch(to: ~p"/admin/posts/#{post}", replace: true) @@ -68,7 +74,12 @@ defmodule Web.AdminPostLive do socket = case Core.Posts.publish_post(post) do {:ok, post} -> - assign(socket, post: post) + form = + post + |> Core.Posts.change_post(%{}) + |> to_form() + + assign(socket, post: post, form: form) _ -> socket @@ -81,7 +92,12 @@ defmodule Web.AdminPostLive do socket = case Core.Posts.unpublish_post(post) do {:ok, post} -> - assign(socket, post: post) + form = + post + |> Core.Posts.change_post(%{}) + |> to_form() + + assign(socket, post: post, form: form) _ -> socket @@ -94,7 +110,12 @@ defmodule Web.AdminPostLive do socket = case Core.Posts.delete_post(post) do {:ok, post} -> - assign(socket, post: post) + form = + post + |> Core.Posts.change_post(%{}) + |> to_form() + + assign(socket, post: post, form: form) _ -> socket @@ -107,7 +128,12 @@ defmodule Web.AdminPostLive do socket = case Core.Posts.undelete_post(post) do {:ok, post} -> - assign(socket, post: post) + form = + post + |> Core.Posts.change_post(%{}) + |> to_form() + + assign(socket, post: post, form: form) _ -> socket diff --git a/lib/web/live/admin_post_live.html.heex b/lib/web/live/admin_post_live.html.heex index 0fcf196..b8144bc 100644 --- a/lib/web/live/admin_post_live.html.heex +++ b/lib/web/live/admin_post_live.html.heex @@ -1,33 +1,41 @@ -<main> - <header> - <h1>{page_title(@post, @live_action)}</h1> - </header> - - <.form for={@form} phx-change="validate" phx-submit="save"> - <.input :if={@post.kind == :blog} type="text" field={@form[:title]} /> +<.form + for={@form} + phx-change="validate" + phx-submit="save" + class="flex flex-col md:flex-row p-4 gap-4" +> + <div class="flex-grow flex flex-col gap-y-4"> + <.input :if={@post.kind == :blog} type="text" label="title" field={@form[:title]} /> + <.input type="textarea" field={@form[:body]} label="body" class="md:flex-grow" /> + </div> + <div class="md:w-80 flex flex-col gap-y-2"> + <.button type="submit" class="self-end">save</.button> + <.link + :if={@post.published_at} + class="self-end" + navigate={Web.Paths.public_post_path(@post)} + target="_blank" + > + view + </.link> + <.input type="datetime-local" label="published (utc)" field={@form[:published_at]} /> + <%= if @post.published_at do %> + <.button phx-click="unpublish" class="self-end">unpublish</.button> + <% else %> + <.button phx-click="publish" class="self-end">publish</.button> + <% end %> + <.input type="datetime-local" label="deleted (utc)" field={@form[:deleted_at]} /> + <%= if @post.deleted_at do %> + <.button phx-click="undelete" class="self-end">undelete</.button> + <% else %> + <.button phx-click="delete" class="self-end">delete</.button> + <% end %> <.input :if={@post.kind == :blog} + label="slug" type="text" field={@form[:slug]} disabled={not update_slug?(@post)} /> - <.input type="textarea" field={@form[:body]} /> - - <.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> + </div> +</.form>