Static blog with front page summary
Goal: have a personal blog, and try out another point in the 'modular app design with elixir' space. Designing OTP systems with elixir had some interesting ideas.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
defmodule FirehoseWeb.BlogLive.Index do
|
||||
@moduledoc """
|
||||
Example LiveView for the blog index page.
|
||||
|
||||
Copy this into your host app and customize the layout/styling.
|
||||
Replace `Firehose.EngineeringBlog` with your actual blog module.
|
||||
"""
|
||||
use MyAppWeb, :live_view
|
||||
|
||||
import Blogex.Components
|
||||
|
||||
@impl true
|
||||
def mount(%{"blog" => blog_id} = _params, _session, socket) do
|
||||
blog = Blogex.get_blog!(String.to_existing_atom(blog_id))
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:blog, blog)
|
||||
|> assign(:page_title, blog.title())}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _uri, socket) do
|
||||
page = String.to_integer(params["page"] || "1")
|
||||
tag = params["tag"]
|
||||
blog = socket.assigns.blog
|
||||
|
||||
posts =
|
||||
if tag do
|
||||
blog.posts_by_tag(tag)
|
||||
else
|
||||
blog.all_posts()
|
||||
end
|
||||
|
||||
per_page = 10
|
||||
total = length(posts)
|
||||
total_pages = max(ceil(total / per_page), 1)
|
||||
|
||||
entries =
|
||||
posts
|
||||
|> Enum.drop((page - 1) * per_page)
|
||||
|> Enum.take(per_page)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:posts, entries)
|
||||
|> assign(:tags, blog.all_tags())
|
||||
|> assign(:current_tag, tag)
|
||||
|> assign(:page, page)
|
||||
|> assign(:total_pages, total_pages)
|
||||
|> assign(:base_path, blog.base_path())}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="max-w-3xl mx-auto py-8 px-4">
|
||||
<h1 class="text-3xl font-bold mb-2">{@blog.title()}</h1>
|
||||
<p class="text-gray-600 mb-8">{@blog.description()}</p>
|
||||
|
||||
<.tag_list tags={@tags} base_path={@base_path} current_tag={@current_tag} />
|
||||
|
||||
<div class="mt-8">
|
||||
<.post_index posts={@posts} base_path={@base_path} />
|
||||
</div>
|
||||
|
||||
<.pagination page={@page} total_pages={@total_pages} base_path={@base_path} />
|
||||
|
||||
<div class="mt-8 text-sm text-gray-500">
|
||||
<a href={"#{@base_path}/feed.xml"} class="hover:underline">RSS Feed</a>
|
||||
·
|
||||
<a href={"#{@base_path}/atom.xml"} class="hover:underline">Atom Feed</a>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
defmodule FirehoseWeb.BlogLive.Show do
|
||||
@moduledoc """
|
||||
Example LiveView for showing a single blog post.
|
||||
|
||||
Copy this into your host app and customize the layout/styling.
|
||||
"""
|
||||
use MyAppWeb, :live_view
|
||||
|
||||
import Blogex.Components
|
||||
|
||||
@impl true
|
||||
def mount(%{"blog" => blog_id} = _params, _session, socket) do
|
||||
blog = Blogex.get_blog!(String.to_existing_atom(blog_id))
|
||||
{:ok, assign(socket, :blog, blog)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"slug" => slug}, _uri, socket) do
|
||||
blog = socket.assigns.blog
|
||||
post = blog.get_post!(slug)
|
||||
|
||||
meta = Blogex.SEO.meta_tags(post, FirehoseWeb.Endpoint.url(), blog)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:post, post)
|
||||
|> assign(:meta, meta)
|
||||
|> assign(:page_title, post.title)
|
||||
|> assign(:base_path, blog.base_path())}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="max-w-3xl mx-auto py-8 px-4">
|
||||
<nav class="mb-8">
|
||||
<a href={@base_path} class="text-blue-600 hover:underline">
|
||||
← Back to {@blog.title()}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<.post_show post={@post} />
|
||||
|
||||
<footer class="mt-12 pt-8 border-t border-gray-200">
|
||||
<h3 class="text-lg font-semibold mb-4">Tags</h3>
|
||||
<.tag_list tags={@post.tags} base_path={@base_path} />
|
||||
</footer>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
defmodule FirehoseWeb.Router do
|
||||
@moduledoc """
|
||||
Example router showing how to integrate Blogex.
|
||||
|
||||
You have two options for mounting blogs:
|
||||
|
||||
## Option A: Plug Router (JSON API / feeds only)
|
||||
|
||||
Uses `Blogex.Router` directly — great for headless / API usage:
|
||||
|
||||
scope "/blog" do
|
||||
pipe_through :browser
|
||||
forward "/engineering", Blogex.Router, blog: Firehose.EngineeringBlog
|
||||
forward "/releases", Blogex.Router, blog: Firehose.ReleaseNotes
|
||||
end
|
||||
|
||||
## Option B: LiveView (full UI, recommended)
|
||||
|
||||
Uses your own LiveViews with Blogex components — full control over layout:
|
||||
|
||||
scope "/blog", MyAppWeb do
|
||||
pipe_through :browser
|
||||
|
||||
live "/engineering", BlogLive.Index, :index,
|
||||
metadata: %{blog: :engineering}
|
||||
|
||||
live "/engineering/:slug", BlogLive.Show, :show,
|
||||
metadata: %{blog: :engineering}
|
||||
|
||||
live "/releases", BlogLive.Index, :index,
|
||||
metadata: %{blog: :release_notes}
|
||||
|
||||
live "/releases/:slug", BlogLive.Show, :show,
|
||||
metadata: %{blog: :release_notes}
|
||||
end
|
||||
|
||||
# Still mount the Plug router for feeds
|
||||
scope "/blog" do
|
||||
forward "/engineering", Blogex.Router, blog: Firehose.EngineeringBlog
|
||||
forward "/releases", Blogex.Router, blog: Firehose.ReleaseNotes
|
||||
end
|
||||
|
||||
## Option C: Mixed
|
||||
|
||||
Use LiveView for the HTML pages but let Blogex.Router handle
|
||||
feeds and the JSON API. Just make sure the LiveView routes are
|
||||
defined first so they take priority.
|
||||
"""
|
||||
|
||||
use Phoenix.Router
|
||||
|
||||
import Phoenix.LiveView.Router
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug :fetch_live_flash
|
||||
plug :put_root_layout, html: {FirehoseWeb.Layouts, :root}
|
||||
plug :protect_from_forgery
|
||||
plug :put_secure_browser_headers
|
||||
end
|
||||
|
||||
# -- Option B: LiveView routes (recommended) --
|
||||
|
||||
scope "/blog", MyAppWeb do
|
||||
pipe_through :browser
|
||||
|
||||
# Engineering blog
|
||||
live "/engineering", BlogLive.Index, :index
|
||||
live "/engineering/tag/:tag", BlogLive.Index, :tag
|
||||
live "/engineering/:slug", BlogLive.Show, :show
|
||||
|
||||
# Release notes
|
||||
live "/releases", BlogLive.Index, :index
|
||||
live "/releases/tag/:tag", BlogLive.Index, :tag
|
||||
live "/releases/:slug", BlogLive.Show, :show
|
||||
end
|
||||
|
||||
# Feeds (served by Blogex.Router as Plug)
|
||||
scope "/blog" do
|
||||
forward "/engineering", Blogex.Router, blog: Firehose.EngineeringBlog
|
||||
forward "/releases", Blogex.Router, blog: Firehose.ReleaseNotes
|
||||
end
|
||||
|
||||
# Sitemap
|
||||
scope "/" do
|
||||
get "/sitemap.xml", FirehoseWeb.SitemapController, :index
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
defmodule FirehoseWeb.SitemapController do
|
||||
@moduledoc """
|
||||
Example controller for serving the blog sitemap.
|
||||
"""
|
||||
use MyAppWeb, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
xml = Blogex.SEO.sitemap(Blogex.blogs(), FirehoseWeb.Endpoint.url())
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, xml)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user