fix: issue #14 wrong sorting in category and tag archives
This commit is contained in:
@@ -175,6 +175,7 @@ defmodule BDS.Generation.Data do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|> sort_generation_index_posts()
|
||||||
end
|
end
|
||||||
|
|
||||||
## --- internals -----------------------------------------------------------
|
## --- internals -----------------------------------------------------------
|
||||||
@@ -398,4 +399,17 @@ defmodule BDS.Generation.Data do
|
|||||||
defp append_generation_index(index, field, key, post) do
|
defp append_generation_index(index, field, key, post) do
|
||||||
update_in(index[field], fn grouped -> Map.update(grouped, key, [post], &[post | &1]) end)
|
update_in(index[field], fn grouped -> Map.update(grouped, key, [post], &[post | &1]) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Archive listings (categories, tags, dates) must render newest first. The
|
||||||
|
# index is built by prepending, so each grouped list is sorted explicitly by
|
||||||
|
# creation date (descending) to guarantee a stable newest-first order.
|
||||||
|
defp sort_generation_index_posts(index) do
|
||||||
|
Map.new(index, fn {field, grouped} ->
|
||||||
|
{field, Map.new(grouped, fn {key, posts} -> {key, sort_index_posts_desc(posts)} end)}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sort_index_posts_desc(posts) do
|
||||||
|
Enum.sort_by(posts, &{-(&1.created_at || 0), -(&1.published_at || 0), to_string(&1.slug)})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ defmodule BDS.Rendering.ListArchive do
|
|||||||
|> DateTime.to_date()
|
|> DateTime.to_date()
|
||||||
|> Date.to_iso8601())
|
|> Date.to_iso8601())
|
||||||
)
|
)
|
||||||
|> Enum.sort_by(fn {label, _posts} -> label end)
|
|> Enum.sort_by(fn {label, _posts} -> label end, :desc)
|
||||||
|
|
||||||
last_index = length(grouped_blocks) - 1
|
last_index = length(grouped_blocks) - 1
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ defmodule BDS.Rendering.ListArchive do
|
|||||||
date_label: date_label,
|
date_label: date_label,
|
||||||
show_date_marker: true,
|
show_date_marker: true,
|
||||||
show_separator: index < last_index,
|
show_separator: index < last_index,
|
||||||
posts: Enum.sort_by(grouped_posts, &Map.get(&1, :created_at))
|
posts: Enum.sort_by(grouped_posts, &Map.get(&1, :created_at), :desc)
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|> case do
|
|> case do
|
||||||
|
|||||||
39
test/bds/generation/archive_ordering_test.exs
Normal file
39
test/bds/generation/archive_ordering_test.exs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
defmodule BDS.Generation.ArchiveOrderingTest do
|
||||||
|
@moduledoc """
|
||||||
|
Category, tag and date archive listings must render newest first. The
|
||||||
|
generation post index is built by prepending, so this guards against the
|
||||||
|
regression where grouped archive lists came out in an unstable/oldest-first
|
||||||
|
order.
|
||||||
|
"""
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias BDS.Generation.Data
|
||||||
|
|
||||||
|
defp post(slug, created_at, categories, tags) do
|
||||||
|
%{
|
||||||
|
id: slug,
|
||||||
|
slug: slug,
|
||||||
|
created_at: created_at,
|
||||||
|
published_at: created_at,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "categories, tags and dates are ordered newest first regardless of input order" do
|
||||||
|
older = post("older", 1_700_000_000_000, ["article"], ["elixir"])
|
||||||
|
newest = post("newest", 1_700_000_200_000, ["article"], ["elixir"])
|
||||||
|
middle = post("middle", 1_700_000_100_000, ["article"], ["elixir"])
|
||||||
|
|
||||||
|
# Deliberately unordered input.
|
||||||
|
index = Data.build_generation_post_index([older, newest, middle])
|
||||||
|
|
||||||
|
expected = ["newest", "middle", "older"]
|
||||||
|
|
||||||
|
assert Enum.map(index.posts_by_category["article"], & &1.slug) == expected
|
||||||
|
assert Enum.map(index.posts_by_tag["elixir"], & &1.slug) == expected
|
||||||
|
|
||||||
|
{year, _month, _day} = BDS.Generation.Paths.local_date_parts!(newest.created_at)
|
||||||
|
assert Enum.map(index.posts_by_year[year], & &1.slug) == expected
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -246,7 +246,7 @@ defmodule BDS.RenderingTest do
|
|||||||
title: "Render Day Blocks",
|
title: "Render Day Blocks",
|
||||||
kind: :list,
|
kind: :list,
|
||||||
content:
|
content:
|
||||||
"range={{ min_date }}-{{ max_date }}|heading={{ show_archive_range_heading }}|blocks={% for block in day_blocks %}[{{ block.date_label }}:{{ block.posts.size }}:{{ block.show_date_marker }}]{% endfor %}|archive={{ archive_context.kind }}:{{ archive_context.year }}:{{ archive_context.month }}"
|
"range={{ min_date }}-{{ max_date }}|heading={{ show_archive_range_heading }}|blocks={% for block in day_blocks %}[{{ block.date_label }}:{% for p in block.posts %}{{ p.slug }};{% endfor %}:{{ block.show_date_marker }}]{% endfor %}|archive={{ archive_context.kind }}:{{ archive_context.year }}:{{ archive_context.month }}"
|
||||||
})
|
})
|
||||||
|
|
||||||
assert {:ok, published_list_template} = BDS.Templates.publish_template(list_template.id)
|
assert {:ok, published_list_template} = BDS.Templates.publish_template(list_template.id)
|
||||||
@@ -289,6 +289,19 @@ defmodule BDS.RenderingTest do
|
|||||||
tags: [],
|
tags: [],
|
||||||
categories: [],
|
categories: [],
|
||||||
href: "/2024/04/01/second/"
|
href: "/2024/04/01/second/"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
id: "second-b",
|
||||||
|
slug: "second-b",
|
||||||
|
title: "Second B",
|
||||||
|
excerpt: "two-b",
|
||||||
|
language: "en",
|
||||||
|
created_at: second_day + 3_600,
|
||||||
|
updated_at: second_day + 3_600,
|
||||||
|
published_at: second_day + 3_600,
|
||||||
|
tags: [],
|
||||||
|
categories: [],
|
||||||
|
href: "/2024/04/01/second-b/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -311,9 +324,9 @@ defmodule BDS.RenderingTest do
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert rendered =~ "heading=true"
|
assert rendered =~ "heading=true"
|
||||||
assert rendered =~ "blocks=[2024-03-31:1:true][2024-04-01:1:true]"
|
assert rendered =~ "blocks=[2024-04-01:second-b;second;:true][2024-03-31:first;:true]"
|
||||||
assert rendered =~ "archive=date:2024:4"
|
assert rendered =~ "archive=date:2024:4"
|
||||||
assert rendered =~ "range=1711843200-1711929600"
|
assert rendered =~ "range=1711843200-1711933200"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "render_post_page falls back to bundled starter template when the published default template file is missing",
|
test "render_post_page falls back to bundled starter template when the published default template file is missing",
|
||||||
|
|||||||
38
tmp_preview_check.exs
Normal file
38
tmp_preview_check.exs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
Application.ensure_all_started(:ecto_sql)
|
||||||
|
Application.ensure_all_started(:exqlite)
|
||||||
|
{:ok, _} = BDS.Repo.start_link()
|
||||||
|
|
||||||
|
project_id = "aceac6d3-8f3f-4a9c-a2b4-517b59b20f44"
|
||||||
|
|
||||||
|
# 1) Replicate the preview data path exactly.
|
||||||
|
import Ecto.Query
|
||||||
|
alias BDS.Posts.Post
|
||||||
|
|
||||||
|
posts =
|
||||||
|
BDS.Repo.all(
|
||||||
|
from p in Post,
|
||||||
|
where: p.project_id == ^project_id and p.status in [:published, :draft],
|
||||||
|
order_by: [desc: p.created_at, desc: p.published_at, asc: p.slug]
|
||||||
|
)
|
||||||
|
|
||||||
|
picture = Enum.filter(posts, fn p -> "picture" in (p.categories || []) end)
|
||||||
|
|
||||||
|
IO.puts("== preview data path, first 5 (expect newest) ==")
|
||||||
|
picture |> Enum.take(5) |> Enum.each(fn p -> IO.puts(" #{p.slug} #{p.created_at} #{p.language}") end)
|
||||||
|
|
||||||
|
# 2) Call the actual router render and extract the first post links.
|
||||||
|
case BDS.Preview.Router.render_route(project_id, "/category/picture") do
|
||||||
|
{:ok, %{body: body}} ->
|
||||||
|
body = IO.iodata_to_binary(body)
|
||||||
|
|
||||||
|
links =
|
||||||
|
Regex.scan(~r{href="[^"]*/20\d\d/\d\d/[^"]*"}, body)
|
||||||
|
|> Enum.map(&hd/1)
|
||||||
|
|> Enum.take(6)
|
||||||
|
|
||||||
|
IO.puts("== render_route /category/picture, first link hrefs ==")
|
||||||
|
Enum.each(links, &IO.puts(" #{&1}"))
|
||||||
|
|
||||||
|
other ->
|
||||||
|
IO.inspect(other, label: "render_route returned")
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user