hand written admin improvements pt. 1
This commit is contained in:
parent
4f3e1d3a8d
commit
1675efb514
12 changed files with 203 additions and 117 deletions
|
@ -37,6 +37,12 @@ defmodule Core.Posts do
|
|||
|> Core.Repo.all()
|
||||
end
|
||||
|
||||
def list_posts(kind, params \\ %{}) do
|
||||
Post.Query.base()
|
||||
|> Post.Query.where_kind(kind)
|
||||
|> Flop.validate_and_run(params, for: Schema.Post)
|
||||
end
|
||||
|
||||
def list_published_posts(kind, params \\ %{}) do
|
||||
Post.Query.base()
|
||||
|> Post.Query.published()
|
||||
|
|
|
@ -154,46 +154,6 @@ defmodule Web.CoreComponents 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})
|
||||
|
||||
@doc """
|
||||
Renders markdown content as HTML.
|
||||
|
||||
|
@ -251,4 +211,37 @@ defmodule Web.CoreComponents do
|
|||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :id, :string, required: true
|
||||
attr :stream, Phoenix.LiveView.LiveStream, required: true
|
||||
attr :class, :string, default: nil
|
||||
attr :rest, :global
|
||||
|
||||
slot :col do
|
||||
attr :label, :string
|
||||
attr :class, :string
|
||||
end
|
||||
|
||||
def table(assigns) do
|
||||
~H"""
|
||||
<table id={@id} class={["border-collapse", @class]} {@rest}>
|
||||
<thead>
|
||||
<tr>
|
||||
<%= for col <- @col do %>
|
||||
<th class="border p-2">{col[:label]}</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id={"#{@id}-stream"} phx-update="stream">
|
||||
<%= for {dom_id, item} <- @stream do %>
|
||||
<tr id={dom_id}>
|
||||
<%= for col <- @col do %>
|
||||
<td class={["border p-2", col[:class]]}>{render_slot(col, item)}</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<section :if={not is_nil(@current_user)} class="ml-2">
|
||||
<nav>
|
||||
<ul class="flex flex-row gap-x-2">
|
||||
<li><.link navigate={~p"/admin"}>admin</.link></li>
|
||||
<li><.link navigate={~p"/admin/writing"}>admin</.link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
|
|
|
@ -2,10 +2,4 @@ defmodule Web.BlogHTML do
|
|||
use Web, :html
|
||||
|
||||
embed_templates "blog_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
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<li>
|
||||
<article class="h-entry">
|
||||
<.link
|
||||
navigate={blog_path(blog)}
|
||||
navigate={Web.Paths.public_blog_path(blog)}
|
||||
class="flex justify-between items-center hover:bg-gray-50 -mx-2 px-2 py-1 rounded"
|
||||
>
|
||||
<h3 class="p-name u-url">{blog.title}</h3>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<li>
|
||||
<article class="h-entry">
|
||||
<.link
|
||||
navigate={Web.BlogHTML.blog_path(blog)}
|
||||
navigate={Web.Paths.public_blog_path(blog)}
|
||||
class="flex justify-between items-center hover:bg-gray-50 -mx-2 px-2 py-1 rounded"
|
||||
>
|
||||
<h3 class="p-name u-url">{blog.title}</h3>
|
||||
|
|
|
@ -2,42 +2,64 @@ defmodule Web.AdminDashboardLive do
|
|||
use Web, :live_view
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
statuses = Core.Posts.get_all_recent_statuses()
|
||||
blogs = Core.Posts.get_all_recent_blogs()
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> stream(:statuses, statuses)
|
||||
|> stream(:blogs, blogs)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
def handle_params(params, _uri, socket) do
|
||||
kind = socket.assigns.live_action
|
||||
|
||||
{:ok, {posts, meta}} = Core.Posts.list_posts(kind, params)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(
|
||||
kind: kind,
|
||||
meta: meta
|
||||
)
|
||||
|> stream(:posts, posts, reset: true)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
attr :post, Schema.Post, required: true
|
||||
|
||||
defp post_status(%{post: %{published_at: nil, deleted_at: nil}} = assigns) do
|
||||
~H"""
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<h1 class="font-bold text-2xl">dashboard</h1>
|
||||
draft
|
||||
"""
|
||||
end
|
||||
|
||||
<section>
|
||||
<header class="flex flex-row justify-between">
|
||||
<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" posts={@streams.statuses}>
|
||||
<.link navigate={~p"/admin/posts/#{status}"}>{status.body}</.link>
|
||||
</.post_list>
|
||||
</section>
|
||||
defp post_status(%{post: %{published_at: _, deleted_at: nil}} = assigns) do
|
||||
~H"""
|
||||
published
|
||||
"""
|
||||
end
|
||||
|
||||
<section>
|
||||
<header class="flex flex-row justify-between">
|
||||
<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" posts={@streams.blogs}>
|
||||
<.link navigate={~p"/admin/posts/#{blog}"}>{blog.title}</.link>
|
||||
</.post_list>
|
||||
</section>
|
||||
defp post_status(assigns) do
|
||||
~H"""
|
||||
deleted
|
||||
"""
|
||||
end
|
||||
|
||||
attr :post, Schema.Post, required: true
|
||||
|
||||
defp post_actions(assigns) do
|
||||
~H"""
|
||||
<div class="flex flex-row gap-x-1">
|
||||
<.link navigate={~p"/admin/posts/#{@post}"}>edit</.link>
|
||||
<.link
|
||||
:if={@post.published_at && is_nil(@post.deleted_at)}
|
||||
navigate={Web.Paths.public_post_path(@post)}
|
||||
>
|
||||
view
|
||||
</.link>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp build_path(:blog, meta),
|
||||
do: Flop.Phoenix.build_path(~p"/admin/writing", meta, for: Schema.Post)
|
||||
|
||||
defp build_path(:status, meta),
|
||||
do: Flop.Phoenix.build_path(~p"/admin/microblog", meta, for: Schema.Post)
|
||||
end
|
||||
|
|
59
lib/web/live/admin_dashboard_live.html.heex
Normal file
59
lib/web/live/admin_dashboard_live.html.heex
Normal file
|
@ -0,0 +1,59 @@
|
|||
<div class="flex flex-col py-4 px-6">
|
||||
<header class="mb-4">
|
||||
<nav>
|
||||
<ul class="flex flex-row gap-x-4">
|
||||
<li>
|
||||
<.link class={[@kind == :blog && "underline"]} patch={~p"/admin/writing"}>
|
||||
writing
|
||||
</.link>
|
||||
</li>
|
||||
<li>
|
||||
<.link class={[@kind == :status && "underline"]} patch={~p"/admin/microblog"}>
|
||||
microblog
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-col">
|
||||
<.link class="mb-4" navigate={~p"/admin/posts/new?kind=#{@kind}"}>new {@kind}</.link>
|
||||
<%= case @kind do %>
|
||||
<% :blog -> %>
|
||||
<.table id="blog-posts" stream={@streams.posts}>
|
||||
<:col :let={blog} label="title">{blog.title}</:col>
|
||||
<:col :let={blog} label="status">
|
||||
<.post_status post={blog} />
|
||||
</:col>
|
||||
<:col :let={blog}>
|
||||
<.post_actions post={blog} />
|
||||
</:col>
|
||||
</.table>
|
||||
<% :status -> %>
|
||||
<.table id="status-posts" stream={@streams.posts}>
|
||||
<:col :let={status} label="content">
|
||||
{status.body}
|
||||
</:col>
|
||||
<:col :let={status} label="status">
|
||||
<.post_status post={status} />
|
||||
</:col>
|
||||
<:col :let={status}>
|
||||
<.post_actions post={status} />
|
||||
</:col>
|
||||
</.table>
|
||||
<% end %>
|
||||
<footer class="flex flex-row justify-between mt-2">
|
||||
<%= if @meta.has_previous_page? do %>
|
||||
<.link patch={build_path(@kind, Flop.to_previous_cursor(@meta))}>prev</.link>
|
||||
<% else %>
|
||||
<div />
|
||||
<% end %>
|
||||
|
||||
<%= if @meta.has_next_page? do %>
|
||||
<.link patch={build_path(@kind, Flop.to_next_cursor(@meta))}>next</.link>
|
||||
<% else %>
|
||||
<div />
|
||||
<% end %>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
|
@ -116,44 +116,6 @@ defmodule Web.AdminPostLive do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<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]} />
|
||||
<.input
|
||||
:if={@post.kind == :blog}
|
||||
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>
|
||||
"""
|
||||
end
|
||||
|
||||
defp page_title(%Schema.Post{kind: :blog}, :new), do: "new blog"
|
||||
defp page_title(%Schema.Post{kind: :status}, :new), do: "new status"
|
||||
defp page_title(%Schema.Post{kind: :blog}, :edit), do: "edit blog"
|
||||
|
|
33
lib/web/live/admin_post_live.html.heex
Normal file
33
lib/web/live/admin_post_live.html.heex
Normal file
|
@ -0,0 +1,33 @@
|
|||
<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]} />
|
||||
<.input
|
||||
:if={@post.kind == :blog}
|
||||
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>
|
16
lib/web/paths.ex
Normal file
16
lib/web/paths.ex
Normal file
|
@ -0,0 +1,16 @@
|
|||
defmodule Web.Paths do
|
||||
use Web, :html
|
||||
|
||||
def public_post_path(%Schema.Post{kind: :status} = status), do: public_status_path(status)
|
||||
def public_post_path(%Schema.Post{kind: :blog} = blog), do: public_blog_path(blog)
|
||||
|
||||
def public_status_path(%Schema.Post{kind: :status} = status) do
|
||||
~p"/status/#{status}"
|
||||
end
|
||||
|
||||
def public_blog_path(%Schema.Post{kind: :blog} = blog) do
|
||||
if date = Core.Posts.publish_date(blog) do
|
||||
~p"/blog/#{date.year}/#{date.month}/#{date.day}/#{blog.slug}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,7 +31,8 @@ defmodule Web.Router do
|
|||
live_session :require_authenticated_user, on_mount: [{Web.UserAuth, :ensure_authenticated}] do
|
||||
live "/users/settings", UserSettingsLive, :edit
|
||||
|
||||
live "/", AdminDashboardLive
|
||||
live "/writing", AdminDashboardLive, :blog
|
||||
live "/microblog", AdminDashboardLive, :status
|
||||
|
||||
live "/posts/new", AdminPostLive, :new
|
||||
live "/posts/:post_id", AdminPostLive, :edit
|
||||
|
|
Loading…
Reference in a new issue