592 lines
22 KiB
Elixir
592 lines
22 KiB
Elixir
defmodule BDS.Desktop.ShellLive.PostEditor do
|
|
@moduledoc false
|
|
|
|
use Phoenix.Component
|
|
|
|
import Ecto.Query
|
|
import Phoenix.HTML
|
|
|
|
alias BDS.Desktop.ShellData
|
|
alias BDS.{I18n, Metadata, PostLinks, Posts, Repo, Tags, Templates}
|
|
alias BDS.Media.Media
|
|
alias BDS.Posts.{Post, Translation}
|
|
alias BDS.UI.Workbench
|
|
|
|
embed_templates "post_editor_html/*"
|
|
|
|
def assign_socket(socket) do
|
|
assigns = Map.put(socket.assigns, :project_metadata, project_metadata(socket.assigns.projects.active_project_id))
|
|
assign(socket, :post_editor, build(assigns))
|
|
end
|
|
|
|
def update(socket, params, reload) do
|
|
case socket.assigns.current_tab do
|
|
%{type: :post, id: post_id} ->
|
|
case Repo.get(Post, post_id) do
|
|
nil ->
|
|
socket
|
|
|
|
%Post{} = post ->
|
|
metadata = project_metadata(post.project_id)
|
|
canonical_language = canonical_language(post, metadata)
|
|
current_language = Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
|
requested_language = normalize_language(Map.get(params, "language"), current_language)
|
|
|
|
next_language =
|
|
if current_language == canonical_language do
|
|
requested_language
|
|
else
|
|
current_language
|
|
end
|
|
|
|
draft = normalize_params(params, current_language, next_language)
|
|
workbench = Workbench.mark_dirty(socket.assigns.workbench, :post, post_id)
|
|
|
|
socket
|
|
|> assign(:workbench, workbench)
|
|
|> assign(:post_editor_drafts, put_nested_map(socket.assigns.post_editor_drafts, post_id, next_language, draft))
|
|
|> assign(:post_editor_active_languages, Map.put(socket.assigns.post_editor_active_languages, post_id, next_language))
|
|
|> assign(:post_editor_save_states, Map.put(socket.assigns.post_editor_save_states, post_id, :dirty))
|
|
|> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:post, post_id}, %{title: draft["title"], subtitle: Atom.to_string(post.status || :draft)}))
|
|
|> maybe_drop_old_language_draft(post_id, current_language, next_language)
|
|
|> reload.(workbench)
|
|
end
|
|
|
|
_other ->
|
|
socket
|
|
end
|
|
end
|
|
|
|
def persist_socket(socket, post_id, action, reload, append_output) do
|
|
case Repo.get(Post, post_id) do
|
|
nil ->
|
|
socket
|
|
|
|
%Post{} = post ->
|
|
metadata = project_metadata(post.project_id)
|
|
canonical_language = canonical_language(post, metadata)
|
|
active_language = Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
|
draft = current_draft(socket.assigns, post, metadata, active_language)
|
|
|
|
case persist(post, draft, active_language, metadata, action) do
|
|
{:ok, record} ->
|
|
workbench = Workbench.clear_dirty(socket.assigns.workbench, :post, post_id)
|
|
normalized_form = persisted_form(Repo.get!(Post, post_id), metadata, active_language)
|
|
|
|
socket
|
|
|> assign(:workbench, workbench)
|
|
|> assign(:post_editor_drafts, put_nested_map(socket.assigns.post_editor_drafts, post_id, active_language, normalized_form))
|
|
|> assign(:post_editor_save_states, Map.put(socket.assigns.post_editor_save_states, post_id, save_state_for_action(action)))
|
|
|> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:post, post_id}, %{title: record_title(record, Repo.get!(Post, post_id)), subtitle: Atom.to_string(record_status(record))}))
|
|
|> reload.(workbench)
|
|
|
|
{:error, reason} ->
|
|
socket
|
|
|> append_output.(translated("Post"), inspect(reason), nil, "error")
|
|
|> reload.(socket.assigns.workbench)
|
|
end
|
|
end
|
|
end
|
|
|
|
def discard_socket(socket, post_id, reload, append_output) do
|
|
case Repo.get(Post, post_id) do
|
|
nil ->
|
|
socket
|
|
|
|
%Post{} = post ->
|
|
metadata = project_metadata(post.project_id)
|
|
canonical_language = canonical_language(post, metadata)
|
|
active_language = Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
|
|
|
case discard(post, active_language, metadata) do
|
|
{:ok, restored_post} ->
|
|
workbench = Workbench.clear_dirty(socket.assigns.workbench, :post, post_id)
|
|
|
|
socket
|
|
|> assign(:workbench, workbench)
|
|
|> assign(:post_editor_drafts, delete_nested_map(socket.assigns.post_editor_drafts, post_id, active_language))
|
|
|> assign(:post_editor_save_states, Map.put(socket.assigns.post_editor_save_states, post_id, :discarded))
|
|
|> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:post, post_id}, %{title: restored_post.title || restored_post.slug || restored_post.id, subtitle: Atom.to_string(restored_post.status || :draft)}))
|
|
|> reload.(workbench)
|
|
|
|
{:error, reason} ->
|
|
socket
|
|
|> append_output.(translated("Post"), inspect(reason), nil, "error")
|
|
|> reload.(socket.assigns.workbench)
|
|
end
|
|
end
|
|
end
|
|
|
|
def delete_socket(socket, post_id, reload, append_output) do
|
|
case Posts.delete_post(post_id) do
|
|
{:ok, :deleted} ->
|
|
workbench = Workbench.close_tab(socket.assigns.workbench, :post, post_id)
|
|
|
|
socket
|
|
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:post, post_id}))
|
|
|> assign(:post_editor_drafts, Map.delete(socket.assigns.post_editor_drafts, post_id))
|
|
|> assign(:post_editor_active_languages, Map.delete(socket.assigns.post_editor_active_languages, post_id))
|
|
|> assign(:post_editor_modes, Map.delete(socket.assigns.post_editor_modes, post_id))
|
|
|> assign(:post_editor_expanded, Map.delete(socket.assigns.post_editor_expanded, post_id))
|
|
|> assign(:post_editor_save_states, Map.delete(socket.assigns.post_editor_save_states, post_id))
|
|
|> reload.(workbench)
|
|
|
|
{:error, reason} ->
|
|
socket
|
|
|> append_output.(translated("Post"), inspect(reason), nil, "error")
|
|
|> reload.(socket.assigns.workbench)
|
|
end
|
|
end
|
|
|
|
def set_mode(socket, post_id, mode, reload) do
|
|
workbench = socket.assigns.workbench
|
|
|
|
socket
|
|
|> assign(:post_editor_modes, Map.put(socket.assigns.post_editor_modes, post_id, normalize_mode(mode)))
|
|
|> reload.(workbench)
|
|
end
|
|
|
|
def toggle_section(socket, post_id, section, reload) when section in [:metadata, :excerpt] do
|
|
workbench = socket.assigns.workbench
|
|
|
|
socket
|
|
|> assign(:post_editor_expanded, Map.put(socket.assigns.post_editor_expanded, post_id, toggled_sections(socket.assigns.post_editor_expanded, post_id, section)))
|
|
|> reload.(workbench)
|
|
end
|
|
|
|
def select_language(socket, post_id, language, reload) do
|
|
workbench = socket.assigns.workbench
|
|
|
|
socket
|
|
|> assign(:post_editor_active_languages, Map.put(socket.assigns.post_editor_active_languages, post_id, normalize_language(language, language)))
|
|
|> reload.(workbench)
|
|
end
|
|
|
|
def build(%{current_tab: %{type: :post, id: post_id}} = assigns) do
|
|
case Repo.get(Post, post_id) do
|
|
nil ->
|
|
nil
|
|
|
|
%Post{} = post ->
|
|
metadata = assigned_project_metadata(assigns)
|
|
canonical_language = canonical_language(post, metadata)
|
|
active_language = Map.get(assigns.post_editor_active_languages, post.id, canonical_language)
|
|
translations = translations(post.id)
|
|
persisted_form = persisted_form(post, metadata, active_language, translations)
|
|
|
|
form =
|
|
assigns.post_editor_drafts
|
|
|> Map.get(post.id, %{})
|
|
|> Map.get(active_language, persisted_form)
|
|
|
|
expanded =
|
|
Map.get(assigns.post_editor_expanded, post.id, %{
|
|
metadata: blank?(post.title),
|
|
excerpt: not blank?(post.excerpt)
|
|
})
|
|
|
|
current_translation = Map.get(translations, active_language)
|
|
|
|
%{
|
|
id: post.id,
|
|
display_title: display_title(form["title"], post.slug, post.id),
|
|
subtitle: active_language_subtitle(active_language, canonical_language),
|
|
slug: post.slug || post.id,
|
|
status: current_status(post.status, active_language, canonical_language, current_translation),
|
|
dirty?: Workbench.dirty?(assigns.workbench, :post, post.id),
|
|
save_state: Map.get(assigns.post_editor_save_states, post.id, :idle),
|
|
metadata_expanded: Map.get(expanded, :metadata, false),
|
|
excerpt_expanded: Map.get(expanded, :excerpt, false),
|
|
mode: Map.get(assigns.post_editor_modes, post.id, :markdown),
|
|
editing_canonical?: editing_canonical_language?(translations, active_language, canonical_language),
|
|
languages: languages(metadata),
|
|
form: form,
|
|
template_options: template_options(post.project_id),
|
|
tag_options: Enum.map(Tags.list_tags(post.project_id), & &1.name),
|
|
category_options: metadata.categories || [],
|
|
translation_flags: translation_flags(post, canonical_language, active_language, translations),
|
|
linked_media: linked_media(post.id),
|
|
post_links: post_links(post.id),
|
|
footer: footer(post, current_translation, active_language, canonical_language)
|
|
}
|
|
end
|
|
end
|
|
|
|
def build(_assigns), do: nil
|
|
|
|
def normalize_mode(mode) when mode in [:visual, :markdown, :preview], do: mode
|
|
def normalize_mode("visual"), do: :visual
|
|
def normalize_mode("preview"), do: :preview
|
|
def normalize_mode(_mode), do: :markdown
|
|
|
|
def normalize_language(value, fallback) do
|
|
case value |> to_string() |> String.trim() do
|
|
"" -> fallback
|
|
normalized -> String.downcase(normalized)
|
|
end
|
|
end
|
|
|
|
def normalize_params(params, current_language, next_language) do
|
|
%{
|
|
"title" => Map.get(params, "title", ""),
|
|
"excerpt" => Map.get(params, "excerpt", ""),
|
|
"content" => Map.get(params, "content", ""),
|
|
"tags" => Map.get(params, "tags", ""),
|
|
"categories" => Map.get(params, "categories", ""),
|
|
"author" => Map.get(params, "author", ""),
|
|
"language" => if(current_language == next_language, do: normalize_language(Map.get(params, "language"), current_language), else: next_language),
|
|
"do_not_translate" => truthy?(Map.get(params, "do_not_translate")),
|
|
"template_slug" => Map.get(params, "template_slug", "")
|
|
}
|
|
end
|
|
|
|
def current_draft(assigns, %Post{} = post, metadata, active_language) do
|
|
persisted = persisted_form(post, metadata, active_language)
|
|
|
|
assigns.post_editor_drafts
|
|
|> Map.get(post.id, %{})
|
|
|> Map.get(active_language, persisted)
|
|
end
|
|
|
|
def persisted_form(%Post{} = post, metadata, active_language) do
|
|
persisted_form(post, metadata, active_language, translations(post.id))
|
|
end
|
|
|
|
def persist(%Post{} = post, draft, active_language, metadata, action) do
|
|
canonical_language = canonical_language(post, metadata)
|
|
translations = translations(post.id)
|
|
|
|
result =
|
|
if editing_canonical_language?(translations, active_language, canonical_language) do
|
|
post
|
|
|> save_canonical_draft(draft)
|
|
|> maybe_publish_post(post.id, action)
|
|
else
|
|
post.id
|
|
|> save_translation_draft(active_language, draft)
|
|
|> maybe_publish_translation(post.id, active_language, action)
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
def discard(%Post{} = post, active_language, metadata) do
|
|
canonical_language = canonical_language(post, metadata)
|
|
current_translations = translations(post.id)
|
|
|
|
cond do
|
|
not editing_canonical_language?(current_translations, active_language, canonical_language) ->
|
|
{:ok, post}
|
|
|
|
post.file_path not in [nil, ""] and post.status == :draft ->
|
|
Posts.discard_post_changes(post.id)
|
|
|
|
true ->
|
|
{:ok, post}
|
|
end
|
|
end
|
|
|
|
def save_state_for_action(:publish), do: :published
|
|
def save_state_for_action(_action), do: :saved
|
|
|
|
def record_title(%Translation{title: title}, post), do: blank_to_nil(title) || post.title || post.slug || post.id
|
|
def record_title(%Post{title: title, slug: slug, id: id}, _post), do: blank_to_nil(title) || blank_to_nil(slug) || id
|
|
|
|
def record_status(%Translation{status: status}), do: status || :draft
|
|
def record_status(%Post{status: status}), do: status || :draft
|
|
|
|
def editing_canonical_language?(translations, active_language, canonical_language) do
|
|
active_language == canonical_language or not Map.has_key?(translations, active_language)
|
|
end
|
|
|
|
def post_status_label(status), do: ShellData.dashboard_status_label(status)
|
|
|
|
def post_editor_save_state_label(:dirty), do: translated("Unsaved")
|
|
def post_editor_save_state_label(:saved), do: translated("Saved")
|
|
def post_editor_save_state_label(:published), do: translated("Published")
|
|
def post_editor_save_state_label(:discarded), do: translated("Reverted")
|
|
def post_editor_save_state_label(_state), do: translated("Idle")
|
|
|
|
def post_editor_mode_label(:visual), do: translated("Visual")
|
|
def post_editor_mode_label(:markdown), do: translated("Markdown")
|
|
def post_editor_mode_label(:preview), do: translated("Preview")
|
|
|
|
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
|
|
|
def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
|
|
|
|
def project_metadata(project_id) do
|
|
{:ok, metadata} = Metadata.get_project_metadata(project_id)
|
|
metadata
|
|
rescue
|
|
_error -> %{main_language: "en", blog_languages: []}
|
|
end
|
|
|
|
defp editor_toolbar(assigns) do
|
|
~H"""
|
|
<%= if Enum.any?(@toolbar_buttons) do %>
|
|
<div class="editor-toolbar">
|
|
<%= for button <- @toolbar_buttons do %>
|
|
<button
|
|
class={["editor-toolbar-button", if(button.destructive, do: "is-destructive")]}
|
|
data-testid="editor-toolbar-overlay-button"
|
|
type="button"
|
|
phx-click="open_overlay"
|
|
phx-value-kind={button.kind}
|
|
>
|
|
<%= translated(button.label) %>
|
|
</button>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
"""
|
|
end
|
|
|
|
defp assigned_project_metadata(assigns), do: Map.get(assigns, :project_metadata, %{})
|
|
|
|
defp current_status(post_status, active_language, canonical_language, current_translation) do
|
|
if active_language == canonical_language, do: post_status, else: translation_status(current_translation)
|
|
end
|
|
|
|
defp persisted_form(post, metadata, active_language, translations) do
|
|
canonical_language = canonical_language(post, metadata)
|
|
translation = Map.get(translations, active_language)
|
|
|
|
if active_language == canonical_language do
|
|
%{
|
|
"title" => post.title || "",
|
|
"excerpt" => post.excerpt || "",
|
|
"content" => post.content || "",
|
|
"tags" => Enum.join(post.tags || [], ", "),
|
|
"categories" => Enum.join(post.categories || [], ", "),
|
|
"author" => post.author || metadata.default_author || "",
|
|
"language" => canonical_language,
|
|
"do_not_translate" => post.do_not_translate || false,
|
|
"template_slug" => post.template_slug || ""
|
|
}
|
|
else
|
|
%{
|
|
"title" => translation && translation.title || "",
|
|
"excerpt" => translation && translation.excerpt || "",
|
|
"content" => translation && translation.content || "",
|
|
"tags" => Enum.join(post.tags || [], ", "),
|
|
"categories" => Enum.join(post.categories || [], ", "),
|
|
"author" => post.author || metadata.default_author || "",
|
|
"language" => active_language,
|
|
"do_not_translate" => post.do_not_translate || false,
|
|
"template_slug" => post.template_slug || ""
|
|
}
|
|
end
|
|
end
|
|
|
|
defp canonical_language(post, metadata) do
|
|
normalize_language(post.language, metadata.main_language || "en")
|
|
end
|
|
|
|
defp truthy?(value) when value in [true, "true", "on", 1, "1"], do: true
|
|
defp truthy?(_value), do: false
|
|
|
|
defp blank?(value), do: blank_to_nil(value) == nil
|
|
|
|
defp blank_to_nil(value) do
|
|
value
|
|
|> to_string()
|
|
|> String.trim()
|
|
|> case do
|
|
"" -> nil
|
|
trimmed -> trimmed
|
|
end
|
|
end
|
|
|
|
defp csv_to_list(value) do
|
|
value
|
|
|> to_string()
|
|
|> String.split(",")
|
|
|> Enum.map(&String.trim/1)
|
|
|> Enum.reject(&(&1 == ""))
|
|
end
|
|
|
|
defp translations(post_id) do
|
|
{:ok, translations} = Posts.list_post_translations(post_id)
|
|
Map.new(translations, fn translation -> {translation.language, translation} end)
|
|
end
|
|
|
|
defp languages(metadata) do
|
|
(([metadata.main_language || "en"] ++ (metadata.blog_languages || [])) ++ Enum.map(I18n.supported_languages(), & &1.code))
|
|
|> Enum.reject(&is_nil/1)
|
|
|> Enum.uniq()
|
|
end
|
|
|
|
defp translation_status(nil), do: :draft
|
|
defp translation_status(%Translation{status: status}) when not is_nil(status), do: status
|
|
defp translation_status(_translation), do: :draft
|
|
|
|
defp template_options(project_id) do
|
|
Repo.all(
|
|
from template in Templates.Template,
|
|
where: template.project_id == ^project_id,
|
|
order_by: [asc: template.title, asc: template.slug],
|
|
select: %{slug: template.slug, title: fragment("COALESCE(?, ?)", template.title, template.slug)}
|
|
)
|
|
rescue
|
|
_error -> []
|
|
end
|
|
|
|
defp linked_media(post_id) do
|
|
case Repo.query("SELECT media_id, sort_order FROM post_media WHERE post_id = ? ORDER BY sort_order ASC, media_id ASC", [post_id]) do
|
|
{:ok, %{rows: rows}} ->
|
|
Enum.map(rows, fn [media_id, sort_order] ->
|
|
case Repo.get(Media, media_id) do
|
|
%Media{} = media ->
|
|
%{
|
|
media_id: media.id,
|
|
has_thumbnail: String.starts_with?(to_string(media.mime_type || ""), "image/"),
|
|
name: media.title || media.original_name || media.id,
|
|
sort_order: sort_order || 0
|
|
}
|
|
|
|
_other ->
|
|
nil
|
|
end
|
|
end)
|
|
|> Enum.reject(&is_nil/1)
|
|
|
|
_other ->
|
|
[]
|
|
end
|
|
rescue
|
|
_error -> []
|
|
end
|
|
|
|
defp post_links(post_id) do
|
|
%{
|
|
backlinks: related_posts(PostLinks.list_incoming_links(post_id), :source_post_id),
|
|
outlinks: related_posts(PostLinks.list_outgoing_links(post_id), :target_post_id)
|
|
}
|
|
end
|
|
|
|
defp related_posts(links, key) do
|
|
Enum.map(links, fn link ->
|
|
case Repo.get(Post, Map.fetch!(link, key)) do
|
|
%Post{} = post -> %{id: post.id, title: post.title || post.slug || post.id, text: link.link_text || post.slug || post.id}
|
|
_other -> nil
|
|
end
|
|
end)
|
|
|> Enum.reject(&is_nil/1)
|
|
end
|
|
|
|
defp translation_flags(post, canonical_language, active_language, translations) do
|
|
canonical = %{language: canonical_language, flag: I18n.flag(canonical_language), status: Atom.to_string(post.status || :draft), active: active_language == canonical_language, label: canonical_language}
|
|
|
|
others =
|
|
translations
|
|
|> Map.values()
|
|
|> Enum.sort_by(& &1.language)
|
|
|> Enum.map(fn translation ->
|
|
%{
|
|
language: translation.language,
|
|
flag: I18n.flag(translation.language),
|
|
status: Atom.to_string(translation.status || :draft),
|
|
active: active_language == translation.language,
|
|
label: translation.language
|
|
}
|
|
end)
|
|
|
|
[canonical | others]
|
|
end
|
|
|
|
defp footer(post, translation, active_language, canonical_language) do
|
|
if active_language == canonical_language do
|
|
%{
|
|
created_at: format_timestamp(post.created_at),
|
|
updated_at: format_timestamp(post.updated_at),
|
|
published_at: format_timestamp(post.published_at)
|
|
}
|
|
else
|
|
%{
|
|
created_at: format_timestamp(translation && translation.created_at || post.created_at),
|
|
updated_at: format_timestamp(translation && translation.updated_at || post.updated_at),
|
|
published_at: format_timestamp(translation && translation.published_at)
|
|
}
|
|
end
|
|
end
|
|
|
|
defp format_timestamp(nil), do: ""
|
|
|
|
defp format_timestamp(timestamp) do
|
|
timestamp
|
|
|> DateTime.from_unix!(:millisecond)
|
|
|> Calendar.strftime("%x")
|
|
end
|
|
|
|
defp display_title(title, slug, fallback_id) do
|
|
blank_to_nil(title) || blank_to_nil(slug) || fallback_id || translated("Untitled")
|
|
end
|
|
|
|
defp active_language_subtitle(active_language, canonical_language) do
|
|
if active_language == canonical_language do
|
|
translated("Canonical draft")
|
|
else
|
|
translated("Translation: %{language}", %{language: String.upcase(active_language)})
|
|
end
|
|
end
|
|
|
|
defp save_canonical_draft(%Post{id: post_id}, draft) do
|
|
Posts.update_post(post_id, %{
|
|
title: blank_to_nil(Map.get(draft, "title")),
|
|
excerpt: blank_to_nil(Map.get(draft, "excerpt")),
|
|
content: blank_to_nil(Map.get(draft, "content")),
|
|
tags: csv_to_list(Map.get(draft, "tags")),
|
|
categories: csv_to_list(Map.get(draft, "categories")),
|
|
author: blank_to_nil(Map.get(draft, "author")),
|
|
language: blank_to_nil(Map.get(draft, "language")),
|
|
do_not_translate: Map.get(draft, "do_not_translate", false),
|
|
template_slug: blank_to_nil(Map.get(draft, "template_slug"))
|
|
})
|
|
end
|
|
|
|
defp save_translation_draft(post_id, language, draft) do
|
|
Posts.upsert_post_translation(post_id, language, %{
|
|
title: Map.get(draft, "title", ""),
|
|
excerpt: blank_to_nil(Map.get(draft, "excerpt")),
|
|
content: blank_to_nil(Map.get(draft, "content"))
|
|
})
|
|
end
|
|
|
|
defp maybe_publish_post({:ok, %Post{}}, post_id, :publish), do: Posts.publish_post(post_id)
|
|
defp maybe_publish_post(result, _post_id, _action), do: result
|
|
|
|
defp maybe_publish_translation({:ok, _translation}, post_id, language, :publish), do: Posts.publish_post_translation(post_id, language)
|
|
defp maybe_publish_translation(result, _post_id, _language, _action), do: result
|
|
|
|
defp maybe_drop_old_language_draft(socket, _post_id, current_language, next_language) when current_language == next_language,
|
|
do: socket
|
|
|
|
defp maybe_drop_old_language_draft(socket, post_id, current_language, _next_language) do
|
|
assign(socket, :post_editor_drafts, delete_nested_map(socket.assigns.post_editor_drafts, post_id, current_language))
|
|
end
|
|
|
|
defp toggled_sections(expanded_by_post, post_id, section) do
|
|
expanded_by_post
|
|
|> Map.get(post_id, %{metadata: false, excerpt: false})
|
|
|> Map.put_new(:metadata, false)
|
|
|> Map.put_new(:excerpt, false)
|
|
|> Map.update!(section, ¬ &1)
|
|
end
|
|
|
|
defp put_nested_map(map, key, nested_key, value) do
|
|
Map.update(map, key, %{nested_key => value}, &Map.put(&1, nested_key, value))
|
|
end
|
|
|
|
defp delete_nested_map(map, key, nested_key) do
|
|
case Map.get(map, key) do
|
|
nil -> map
|
|
nested ->
|
|
case Map.delete(nested, nested_key) do
|
|
emptied when map_size(emptied) == 0 -> Map.delete(map, key)
|
|
remaining -> Map.put(map, key, remaining)
|
|
end
|
|
end
|
|
end
|
|
end
|