fix: issue #6 post bodies missing for asides

This commit is contained in:
2026-07-05 12:24:30 +02:00
parent fcc574428c
commit 7fa6e6232c
19 changed files with 985 additions and 788 deletions

View File

@@ -54,6 +54,7 @@ defmodule BDS.BoundedAtoms do
:fill_missing_translations,
:find_duplicates,
:generate_sitemap,
:force_render_site,
:validate_site,
:upload_site,
:documentation,

View File

@@ -188,6 +188,7 @@ defmodule BDS.Desktop.MenuBar do
defp item_label(:fill_missing_translations), do: dgettext("ui", "Fill Missing Translations")
defp item_label(:find_duplicates), do: dgettext("ui", "Find Duplicate Posts")
defp item_label(:generate_sitemap), do: dgettext("ui", "Generate Site")
defp item_label(:force_render_site), do: dgettext("ui", "Force Render Site")
defp item_label(:validate_site), do: dgettext("ui", "Validate Site")
defp item_label(:upload_site), do: dgettext("ui", "Upload Site")
defp item_label(:about), do: dgettext("ui", "About")

View File

@@ -239,33 +239,13 @@ defmodule BDS.Desktop.ShellCommands do
end
defp dispatch("generate_sitemap", project, _params) do
group_id = task_group_id("generate_sitemap")
attrs = %{group_id: group_id, group_name: "Render Site"}
queue_render_site(project, "generate_sitemap", "Render Site", [])
end
{:ok, first_task} =
Tasks.submit_task(
section_task_name(:core),
fn report ->
{:ok, _result} = Generation.render_site_section(project.id, :core, on_progress: report)
report.(1.0, section_complete_message(:core))
:ok
end,
attrs
)
Task.start(fn -> run_render_site_sequence(project, group_id, attrs) end)
{:ok,
%{
kind: "task_queued",
action: "generate_sitemap",
title: "Render Site",
message: "Render Site tasks queued",
project_id: project.id,
task_id: first_task.id,
task_group_id: group_id,
panel_tab: "tasks"
}}
# Forced variant of the site render: ignores the stored content hashes and
# rewrites every output file, repairing any drift on disk.
defp dispatch("force_render_site", project, _params) do
queue_render_site(project, "force_render_site", "Force Render Site", force: true)
end
defp dispatch("validate_site", project, _params) do
@@ -490,7 +470,43 @@ defmodule BDS.Desktop.ShellCommands do
end
end
defp run_render_site_sequence(project, group_id, attrs) do
defp queue_render_site(project, action, title, render_opts) do
group_id = task_group_id(action)
attrs = %{group_id: group_id, group_name: title}
{:ok, first_task} =
Tasks.submit_task(
section_task_name(:core),
fn report ->
{:ok, _result} =
Generation.render_site_section(
project.id,
:core,
[on_progress: report] ++ render_opts
)
report.(1.0, section_complete_message(:core))
:ok
end,
attrs
)
Task.start(fn -> run_render_site_sequence(project, group_id, attrs, render_opts) end)
{:ok,
%{
kind: "task_queued",
action: action,
title: title,
message: title <> " tasks queued",
project_id: project.id,
task_id: first_task.id,
task_group_id: group_id,
panel_tab: "tasks"
}}
end
defp run_render_site_sequence(project, group_id, attrs, render_opts) do
remaining_sections = [:single, :category, :tag, :date]
Enum.each(remaining_sections, fn section ->
@@ -499,7 +515,11 @@ defmodule BDS.Desktop.ShellCommands do
section_task_name(section),
fn report ->
{:ok, _result} =
Generation.render_site_section(project.id, section, on_progress: report)
Generation.render_site_section(
project.id,
section,
[on_progress: report] ++ render_opts
)
report.(1.0, section_complete_message(section))
:ok
@@ -515,7 +535,12 @@ defmodule BDS.Desktop.ShellCommands do
Tasks.submit_task(
"Build Search Index",
fn report ->
{:ok, _result} = Generation.build_site_search_index(project.id, on_progress: report)
{:ok, _result} =
Generation.build_site_search_index(
project.id,
[on_progress: report] ++ render_opts
)
report.(1.0, "Build Search Index complete")
:ok
end,

View File

@@ -137,7 +137,9 @@ defmodule BDS.Desktop.ShellLive do
|> MapSet.union(
MapSet.new([:validate_translations, :fill_missing_translations, :find_duplicates])
)
|> MapSet.union(MapSet.new([:generate_sitemap, :validate_site, :upload_site]))
|> MapSet.union(
MapSet.new([:generate_sitemap, :force_render_site, :validate_site, :upload_site])
)
end
embed_templates("shell_live/*")

View File

@@ -99,6 +99,10 @@ defmodule BDS.Generation do
than just the affected ones). The `:core` section also writes the complete
sitemap, feeds, calendar and preview assets. The Pagefind search index is
built separately by `build_site_search_index/2`.
Pass `force: true` to ignore the stored content hashes and rewrite every
output file, repairing any drift between the tracked hashes and what is
actually on disk.
"""
@spec render_site_section(String.t(), section(), generation_opts()) ::
{:ok, map()} | {:error, term()}
@@ -115,7 +119,12 @@ defmodule BDS.Generation do
rendered_url_count =
stream_render(project_id, on_progress, route_total,
[verb: "Generated", gerund: "Generating", refresh_route_timestamps: false],
[
verb: "Generated",
gerund: "Generating",
refresh_route_timestamps: false,
force: Keyword.get(opts, :force, false)
],
fn on_output -> full_section_outputs(plan, render_data, section, on_output) end
)
@@ -143,7 +152,12 @@ defmodule BDS.Generation do
pagefind_outputs = BDS.Generation.Pagefind.build_outputs(plan, html_outputs)
total = max(length(pagefind_outputs), 1)
write_ctx = stream_context(project_id, nil, 0, verb: "Built", gerund: "Building")
write_ctx =
stream_context(project_id, nil, 0,
verb: "Built",
gerund: "Building",
force: Keyword.get(opts, :force, false)
)
pagefind_outputs
|> Enum.with_index(1)
@@ -953,7 +967,9 @@ defmodule BDS.Generation do
%{
project_id: project_id,
project: Projects.get_project!(project_id),
existing_hashes: existing_hash_map(project_id),
# force: pretend nothing was ever generated so every output is written.
existing_hashes:
if(Keyword.get(opts, :force, false), do: %{}, else: existing_hash_map(project_id)),
pending_hashes: :ets.new(:bds_generation_pending_hashes, [:set, :public]),
on_progress: on_progress,
route_total: route_total,

View File

@@ -44,30 +44,18 @@ defmodule BDS.Generation.Renderers do
|> IO.iodata_to_binary()
end
@doc "Render an archive page (category, tag, year) with pagination."
@doc "Render an archive page (category, tag) with pagination."
@spec render_archive_page(map(), String.t(), [map()], String.t() | nil, String.t(), map()) ::
String.t()
def render_archive_page(plan, title, posts, language, kind, pagination) do
fallback = fn -> render_inline_list_page(title, posts, language, kind) end
render_list_output(
render_archive_list_page(
plan,
language,
title,
Enum.map(posts, fn post ->
%{
id: post.id,
slug: post.slug,
title: post.title,
href: "#",
excerpt: post.excerpt,
content: nil,
language: post.language
}
end),
%{kind: kind, name: title},
pagination,
fallback
posts,
language,
kind,
pagination
)
end
@@ -75,12 +63,18 @@ defmodule BDS.Generation.Renderers do
@spec render_date_archive_page(map(), String.t(), map(), [map()], String.t() | nil, map()) ::
String.t()
def render_date_archive_page(plan, label, archive_context, posts, language, pagination) do
fallback = fn -> render_inline_list_page(label, posts, language, "date") end
render_archive_list_page(plan, label, archive_context, posts, language, "date", pagination)
end
# Shared by every archive kind so category/tag pages get the same post
# payloads (loaded bodies, real hrefs) as date archives.
defp render_archive_list_page(plan, title, archive_context, posts, language, kind, pagination) do
fallback = fn -> render_inline_list_page(title, posts, language, kind) end
render_list_output(
plan,
language,
label,
title,
build_list_posts(plan, posts, Paths.route_language(plan.language, language)),
archive_context,
pagination,

View File

@@ -102,19 +102,34 @@ defmodule BDS.Rendering.RenderContext do
@doc """
Return the cached value for `key`, computing and caching it with `fun` on the
first call. Safe to call from concurrently rendering processes; concurrent
first calls may compute twice but always return a consistent value.
first call. Single-flight: concurrent first calls for the same key compute
exactly once — later callers wait for the winner instead of duplicating the
work (template parses, file reads) and its queries.
"""
@spec memoize(t(), term(), (-> value)) :: value when value: term()
def memoize(%__MODULE__{cache: cache}, key, fun) when is_function(fun, 0) do
def memoize(%__MODULE__{cache: cache} = ctx, key, fun) when is_function(fun, 0) do
case :ets.lookup(cache, key) do
[{^key, value}] ->
value
[{^key, value}] -> value
[] -> compute_memoized(ctx, key, fun)
end
end
[] ->
defp compute_memoized(%__MODULE__{cache: cache} = ctx, key, fun) do
lock_key = {:memoize_lock, key}
if :ets.insert_new(cache, {lock_key, self()}) do
try do
value = fun.()
:ets.insert(cache, {key, value})
true = :ets.insert(cache, {key, value})
value
after
:ets.delete(cache, lock_key)
end
else
# Another process is computing this key; poll until its value lands (or
# its lock disappears because it crashed, in which case we take over).
Process.sleep(2)
memoize(ctx, key, fun)
end
end

View File

@@ -27,6 +27,7 @@ defmodule BDS.UI.Commands do
%{id: :publish_selected, accelerator: "CTRL+SHIFT+P"},
%{id: :preview_post, accelerator: "CTRL+SHIFT+V"},
%{id: :generate_sitemap, accelerator: "CTRL+R"},
%{id: :force_render_site, accelerator: "CTRL+SHIFT+R"},
%{id: :validate_site, accelerator: "CTRL+SHIFT+L"},
%{id: :upload_site, accelerator: "CTRL+SHIFT+U"}
]

View File

@@ -63,6 +63,7 @@ defmodule BDS.UI.MenuBar do
%{id: :find_duplicates},
%{separator: true},
%{id: :generate_sitemap},
%{id: :force_render_site},
%{id: :validate_site},
%{id: :upload_site}
]