Add What's New changelog page
- ChangelogParser: parses priv/static/changelog.txt into structured entries - ChangelogLive: timeline view with DaisyUI styling - Route at /changelog in authenticated scope - What's New link in org dropdown menu
This commit is contained in:
parent
f64a1690fd
commit
608bad0cf6
4 changed files with 93 additions and 0 deletions
48
lib/towerops_web/changelog_parser.ex
Normal file
48
lib/towerops_web/changelog_parser.ex
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
defmodule ToweropsWeb.ChangelogParser do
|
||||
@moduledoc "Parses priv/static/changelog.txt into structured entries."
|
||||
|
||||
@doc "Returns a list of %{date: Date.t(), title: String.t(), items: [String.t()]}"
|
||||
def parse do
|
||||
path = Application.app_dir(:towerops, "priv/static/changelog.txt")
|
||||
|
||||
case File.read(path) do
|
||||
{:ok, content} -> parse_content(content)
|
||||
{:error, _} -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_content(content) do
|
||||
content
|
||||
|> String.split("\n")
|
||||
|> Enum.reduce([], fn line, acc ->
|
||||
cond do
|
||||
# Date header with title: "2026-02-13 — Title"
|
||||
Regex.match?(~r/^\d{4}-\d{2}-\d{2}\s*[—–-]\s*.+/, line) ->
|
||||
[date_str, title] = String.split(line, ~r/\s*[—–-]\s*/, parts: 2)
|
||||
entry = %{date: Date.from_iso8601!(String.trim(date_str)), title: String.trim(title), items: []}
|
||||
[entry | acc]
|
||||
|
||||
# Date header without title: "2026-02-13"
|
||||
Regex.match?(~r/^\d{4}-\d{2}-\d{2}\s*$/, line) ->
|
||||
date = Date.from_iso8601!(String.trim(line))
|
||||
entry = %{date: date, title: nil, items: []}
|
||||
[entry | acc]
|
||||
|
||||
# Bullet point
|
||||
String.starts_with?(String.trim(line), "* ") ->
|
||||
case acc do
|
||||
[current | rest] ->
|
||||
item = String.trim(line) |> String.trim_leading("* ")
|
||||
[%{current | items: current.items ++ [item]} | rest]
|
||||
|
||||
[] ->
|
||||
acc
|
||||
end
|
||||
|
||||
true ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|> Enum.reverse()
|
||||
end
|
||||
end
|
||||
|
|
@ -366,6 +366,14 @@ defmodule ToweropsWeb.Layouts do
|
|||
>
|
||||
My Data
|
||||
</.link>
|
||||
<.link
|
||||
role="menuitem"
|
||||
navigate={~p"/changelog"}
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-hidden dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
phx-click={JS.hide(to: "#org-menu")}
|
||||
>
|
||||
🆕 What's New
|
||||
</.link>
|
||||
<.link
|
||||
role="menuitem"
|
||||
href={~p"/users/log-out"}
|
||||
|
|
|
|||
34
lib/towerops_web/live/changelog_live.ex
Normal file
34
lib/towerops_web/live/changelog_live.ex
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
defmodule ToweropsWeb.ChangelogLive do
|
||||
use ToweropsWeb, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
entries = ToweropsWeb.ChangelogParser.parse()
|
||||
{:ok, assign(socket, entries: entries, page_title: "What's New", active_page: "changelog")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="mx-auto max-w-3xl px-4 py-8">
|
||||
<h1 class="text-3xl font-bold mb-2">What's New</h1>
|
||||
<p class="text-base-content/60 mb-8">Latest updates and improvements to TowerOps.</p>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div :for={entry <- @entries} class="relative pl-8 border-l-2 border-primary/20">
|
||||
<div class="absolute -left-[9px] top-1 w-4 h-4 rounded-full bg-primary border-2 border-base-100" />
|
||||
<div class="mb-1">
|
||||
<span class="badge badge-ghost badge-sm font-mono">
|
||||
{Calendar.strftime(entry.date, "%b %d, %Y")}
|
||||
</span>
|
||||
</div>
|
||||
<h2 :if={entry.title} class="text-lg font-semibold mb-2">{entry.title}</h2>
|
||||
<ul :if={entry.items != []} class="list-disc list-inside space-y-1 text-base-content/80 text-sm">
|
||||
<li :for={item <- entry.items}>{item}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
@ -371,6 +371,9 @@ defmodule ToweropsWeb.Router do
|
|||
# Activity feed
|
||||
live "/activity", ActivityFeedLive, :index
|
||||
|
||||
# Changelog
|
||||
live "/changelog", ChangelogLive, :index
|
||||
|
||||
# Network map route
|
||||
live "/network-map", NetworkMapLive, :index
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue