196 lines
6.2 KiB
Elixir
196 lines
6.2 KiB
Elixir
defmodule BDS.Rendering.Metadata do
|
|
@moduledoc false
|
|
|
|
import Ecto.Query
|
|
|
|
alias BDS.I18n
|
|
alias BDS.Menu
|
|
alias BDS.Metadata, as: ProjectMetadata
|
|
alias BDS.Persistence
|
|
alias BDS.PreviewAssets
|
|
alias BDS.Rendering.LinksAndLanguages
|
|
alias BDS.Repo
|
|
alias BDS.Posts.Post
|
|
alias BDS.Posts.Translation
|
|
alias BDS.Tags.Tag
|
|
|
|
@menu_item_path_segments %{
|
|
page: [],
|
|
category_archive: ["category"]
|
|
}
|
|
|
|
@spec project_metadata(String.t()) :: map()
|
|
def project_metadata(project_id) do
|
|
{:ok, metadata} = ProjectMetadata.get_project_metadata(project_id)
|
|
metadata
|
|
end
|
|
|
|
@doc """
|
|
Resolve the document/page title (the `<title>` and the home-page heading).
|
|
|
|
Mirrors the old app's `resolvePageTitle`: the blog description wins, then the
|
|
blog name. The archive-specific label (category/tag/date) is carried
|
|
separately in `archive_context`, so every page's document title stays the
|
|
blog title.
|
|
"""
|
|
@spec blog_page_title(String.t() | nil, String.t() | nil) :: String.t()
|
|
def blog_page_title(description, name) do
|
|
cond do
|
|
is_binary(description) and String.trim(description) != "" -> String.trim(description)
|
|
is_binary(name) and String.trim(name) != "" -> String.trim(name)
|
|
true -> ""
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Resolve a category's descriptive display name for archive headings.
|
|
|
|
Mirrors the old app: the category's configured `title` wins, falling back to
|
|
the raw category key when no title is set.
|
|
"""
|
|
@spec category_display_name(map() | nil, String.t()) :: String.t()
|
|
def category_display_name(category_settings, category) do
|
|
title =
|
|
category_settings
|
|
|> category_settings_for(category)
|
|
|> category_title()
|
|
|
|
if is_binary(title) and String.trim(title) != "", do: String.trim(title), else: category
|
|
end
|
|
|
|
defp category_settings_for(category_settings, category) when is_map(category_settings),
|
|
do: Map.get(category_settings, category)
|
|
|
|
defp category_settings_for(_category_settings, _category), do: nil
|
|
|
|
defp category_title(settings) when is_map(settings),
|
|
do: BDS.MapUtils.attr(settings, :title)
|
|
|
|
defp category_title(_settings), do: nil
|
|
|
|
@spec menu_items(String.t()) :: [map()]
|
|
def menu_items(project_id) do
|
|
{:ok, %{items: items}} = Menu.get_menu(project_id)
|
|
template_menu_items(items)
|
|
end
|
|
|
|
@spec menu_items_from_raw([map()]) :: [map()]
|
|
def menu_items_from_raw(items) when is_list(items) do
|
|
template_menu_items(items)
|
|
end
|
|
|
|
defp template_menu_items(items), do: Enum.map(items, &to_template_menu_item/1)
|
|
|
|
defp to_template_menu_item(item) do
|
|
kind = Map.get(item, :kind)
|
|
children = template_menu_items(Map.get(item, :children, []))
|
|
|
|
%{
|
|
title: Map.get(item, :label, ""),
|
|
href: menu_item_href(item),
|
|
has_children: children != [],
|
|
children: children,
|
|
kind: kind
|
|
}
|
|
end
|
|
|
|
defp menu_item_href(%{kind: :home}), do: "/"
|
|
|
|
defp menu_item_href(%{kind: kind, slug: slug}) when is_binary(slug) and slug != "" do
|
|
case Map.get(@menu_item_path_segments, kind) do
|
|
nil -> "#"
|
|
path_segments -> "/" <> Path.join(path_segments ++ [URI.encode(slug)]) <> "/"
|
|
end
|
|
end
|
|
|
|
defp menu_item_href(_item), do: "#"
|
|
|
|
@spec blog_languages(map(), String.t()) :: [map()]
|
|
def blog_languages(metadata, current_language) do
|
|
([metadata.main_language] ++ (metadata.blog_languages || []))
|
|
|> Enum.reject(&(&1 in [nil, ""]))
|
|
|> Enum.uniq()
|
|
|> Enum.map(fn language ->
|
|
normalized = I18n.normalize_language(language)
|
|
|
|
href_prefix =
|
|
LinksAndLanguages.language_prefix(normalized, metadata.main_language || current_language)
|
|
|
|
%{
|
|
code: normalized,
|
|
flag: I18n.flag(normalized),
|
|
href: href_for_language(href_prefix),
|
|
href_prefix: href_prefix,
|
|
is_current: normalized == I18n.normalize_language(current_language)
|
|
}
|
|
end)
|
|
end
|
|
|
|
@spec tag_color_by_name(String.t()) :: %{String.t() => String.t() | nil}
|
|
def tag_color_by_name(project_id) do
|
|
Repo.all(from tag in Tag, where: tag.project_id == ^project_id, select: {tag.name, tag.color})
|
|
|> Enum.into(%{}, fn {name, color} -> {name, color} end)
|
|
end
|
|
|
|
@spec alternate_links(Post.t() | nil, String.t(), String.t()) :: [map()]
|
|
def alternate_links(nil, _project_id, _main_language), do: []
|
|
|
|
def alternate_links(%Post{} = post, project_id, main_language) do
|
|
translations =
|
|
Repo.all(
|
|
from translation in Translation,
|
|
where:
|
|
translation.project_id == ^project_id and
|
|
translation.translation_for == ^post.id and
|
|
translation.status == :published,
|
|
order_by: [asc: translation.language]
|
|
)
|
|
|
|
alternate_links_from(post, translations, main_language)
|
|
end
|
|
|
|
@spec alternate_links_from(Post.t() | nil, [Translation.t()], String.t()) :: [map()]
|
|
def alternate_links_from(nil, _translations, _main_language), do: []
|
|
|
|
def alternate_links_from(%Post{} = post, translations, main_language) do
|
|
[
|
|
%{
|
|
href: LinksAndLanguages.post_path(post, nil),
|
|
hreflang: LinksAndLanguages.normalize_language(post.language, main_language)
|
|
}
|
|
] ++
|
|
Enum.map(translations, fn translation ->
|
|
%{
|
|
href: LinksAndLanguages.post_path(post, translation.language, main_language),
|
|
hreflang: translation.language
|
|
}
|
|
end)
|
|
end
|
|
|
|
@spec backlinks([map()]) :: [map()]
|
|
def backlinks(incoming_links) do
|
|
Enum.map(incoming_links, fn link ->
|
|
%{path: link.href, display_slug: link.display_slug, title: link.title}
|
|
end)
|
|
end
|
|
|
|
@spec default_pico_stylesheet_href(String.t() | nil) :: String.t()
|
|
def default_pico_stylesheet_href(theme), do: PreviewAssets.stylesheet_href(theme)
|
|
|
|
@spec href_for_language(String.t()) :: String.t()
|
|
def href_for_language(""), do: "/"
|
|
def href_for_language(prefix), do: String.trim_trailing(prefix, "/") <> "/"
|
|
|
|
@spec calendar_initial_year(map() | nil) :: integer() | nil
|
|
def calendar_initial_year(%{created_at: created_at}) when is_integer(created_at),
|
|
do: Persistence.from_unix_ms!(created_at).year
|
|
|
|
def calendar_initial_year(_post), do: nil
|
|
|
|
@spec calendar_initial_month(map() | nil) :: integer() | nil
|
|
def calendar_initial_month(%{created_at: created_at}) when is_integer(created_at),
|
|
do: Persistence.from_unix_ms!(created_at).month
|
|
|
|
def calendar_initial_month(_post), do: nil
|
|
end
|