chore: added more @spec
This commit is contained in:
@@ -8,10 +8,11 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
alias BDS.Desktop.ShellData
|
||||
alias BDS.Desktop.ShellLive.ChatEditor.{MessageBuild, ModelSelection, ToolTracking}
|
||||
|
||||
embed_templates "chat_editor_html/*"
|
||||
embed_templates("chat_editor_html/*")
|
||||
|
||||
# ── Public API: state assignment ───────────────────────────────────────────
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
assign(socket, :chat_editor, MessageBuild.build(socket.assigns))
|
||||
end
|
||||
@@ -25,6 +26,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|
||||
# ── Public API: input + surface state ──────────────────────────────────────
|
||||
|
||||
@spec update_input(term(), term(), term()) :: term()
|
||||
def update_input(socket, value, reload) do
|
||||
%{id: conversation_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -36,6 +38,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec update_surface_form(term(), term(), term(), term()) :: term()
|
||||
def update_surface_form(socket, surface_id, fields, reload)
|
||||
when is_binary(surface_id) and is_map(fields) do
|
||||
next_data = Map.put(socket.assigns.chat_editor_surface_data, surface_id, fields)
|
||||
@@ -45,6 +48,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec select_surface_tab(term(), term(), term(), term()) :: term()
|
||||
def select_surface_tab(socket, surface_id, index, reload)
|
||||
when is_binary(surface_id) and is_integer(index) and index >= 0 do
|
||||
socket
|
||||
@@ -55,10 +59,12 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec current_surface_data(term(), term()) :: term()
|
||||
def current_surface_data(socket, surface_id) when is_binary(surface_id) do
|
||||
Map.get(socket.assigns.chat_editor_surface_data, surface_id, %{})
|
||||
end
|
||||
|
||||
@spec set_action_error(term(), term(), term(), term()) :: term()
|
||||
def set_action_error(socket, conversation_id, message, reload)
|
||||
when is_binary(conversation_id) and is_binary(message) do
|
||||
socket
|
||||
@@ -69,6 +75,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec clear_action_error(term(), term(), term()) :: term()
|
||||
def clear_action_error(socket, conversation_id, reload) when is_binary(conversation_id) do
|
||||
socket
|
||||
|> assign(
|
||||
@@ -80,6 +87,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|
||||
# ── Public API: messaging ──────────────────────────────────────────────────
|
||||
|
||||
@spec send_message(term(), term(), term()) :: term()
|
||||
def send_message(socket, reload, append_output) do
|
||||
%{id: conversation_id} = socket.assigns.current_tab
|
||||
message = socket.assigns.chat_editor_inputs |> Map.get(conversation_id, "") |> String.trim()
|
||||
@@ -144,6 +152,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec abort_message(term(), term()) :: term()
|
||||
def abort_message(socket, reload) do
|
||||
%{id: conversation_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -167,6 +176,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec note_tool_call(term(), term(), term(), term()) :: term()
|
||||
def note_tool_call(socket, conversation_id, tool_call, reload)
|
||||
when is_binary(conversation_id) and is_map(tool_call) do
|
||||
update_request(
|
||||
@@ -189,6 +199,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
)
|
||||
end
|
||||
|
||||
@spec note_tool_result(term(), term(), term(), term()) :: term()
|
||||
def note_tool_result(socket, conversation_id, name, reload)
|
||||
when is_binary(conversation_id) and is_binary(name) do
|
||||
update_request(
|
||||
@@ -201,6 +212,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
)
|
||||
end
|
||||
|
||||
@spec note_streaming_content(term(), term(), term(), term()) :: term()
|
||||
def note_streaming_content(socket, conversation_id, content, reload)
|
||||
when is_binary(conversation_id) and is_binary(content) do
|
||||
update_request(
|
||||
@@ -211,6 +223,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
)
|
||||
end
|
||||
|
||||
@spec finish_request(term(), term(), term(), term(), term()) :: term()
|
||||
def finish_request(socket, ref, result, reload, append_output) when is_reference(ref) do
|
||||
case Map.pop(socket.assigns.chat_editor_request_refs, ref) do
|
||||
{nil, _remaining_refs} ->
|
||||
@@ -245,12 +258,14 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|
||||
# ── HEEx-callable helpers ─────────────────────────────────────────────────
|
||||
|
||||
@spec message_role_label(term()) :: term()
|
||||
def message_role_label(:user), do: translated("chat.role.you")
|
||||
def message_role_label(_role), do: translated("chat.role.assistant")
|
||||
|
||||
defdelegate tool_call_name(tool_call), to: ToolTracking
|
||||
defdelegate tool_call_arguments(tool_call), to: ToolTracking
|
||||
|
||||
@spec tool_surface_type(term()) :: term()
|
||||
def tool_surface_type(surface), do: Map.get(surface, :type, "json")
|
||||
|
||||
def markdown_html(content) when is_binary(content) do
|
||||
@@ -264,8 +279,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
raw(html)
|
||||
end
|
||||
|
||||
@spec markdown_html(term()) :: term()
|
||||
def markdown_html(_content), do: ""
|
||||
|
||||
@spec payload_json(term()) :: term()
|
||||
def payload_json(nil), do: "{}"
|
||||
def payload_json(payload) when is_map(payload), do: Jason.encode!(payload)
|
||||
|
||||
@@ -280,15 +297,18 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|> Float.round(2)
|
||||
end
|
||||
|
||||
@spec chart_width(term(), term()) :: term()
|
||||
def chart_width(_max_value, _value), do: 0
|
||||
|
||||
def truthy?(value) when value in [true, "true", 1, "1", "on"], do: true
|
||||
@spec truthy?(term()) :: term()
|
||||
def truthy?(_value), do: false
|
||||
|
||||
# ── HEEx components ───────────────────────────────────────────────────────
|
||||
|
||||
attr :markers, :list, required: true
|
||||
attr(:markers, :list, required: true)
|
||||
|
||||
@spec chat_tool_markers(term()) :: term()
|
||||
def chat_tool_markers(assigns) do
|
||||
~H"""
|
||||
<%= if @markers != [] do %>
|
||||
@@ -307,8 +327,9 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
"""
|
||||
end
|
||||
|
||||
attr :surface, :map, required: true
|
||||
attr(:surface, :map, required: true)
|
||||
|
||||
@spec chat_surface(term()) :: term()
|
||||
def chat_surface(assigns) do
|
||||
~H"""
|
||||
<article class={["chat-inline-surface", "chat-inline-surface-#{@surface.type}"]} data-testid="chat-inline-surface">
|
||||
@@ -548,7 +569,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
fn _match, src, alt -> external_image_link(src, alt) end
|
||||
)
|
||||
|
||||
Regex.replace(~r/<img\b(?=[^>]*\bsrc="(https?:\/\/[^\"]+)")[^>]*\/?>/i, html, fn _match, src ->
|
||||
Regex.replace(~r/<img\b(?=[^>]*\bsrc="(https?:\/\/[^\"]+)")[^>]*\/?>/i, html, fn _match,
|
||||
src ->
|
||||
external_image_link(src, src)
|
||||
end)
|
||||
end
|
||||
@@ -571,6 +593,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|
||||
defp format_error(reason), do: inspect(reason)
|
||||
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
|
||||
alias BDS.Desktop.ShellData
|
||||
alias BDS.Desktop.ShellLive.ChatEditor.{ModelSelection, ToolSurfaces, ToolTracking}
|
||||
|
||||
@spec build(term()) :: term()
|
||||
def build(%{current_tab: %{type: :chat, id: conversation_id}} = assigns) do
|
||||
case AI.get_chat_conversation(conversation_id) do
|
||||
nil ->
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
|
||||
|
||||
import Phoenix.Component, only: [assign: 3]
|
||||
|
||||
@spec toggle_model_selector(term(), term()) :: term()
|
||||
def toggle_model_selector(socket, reload) do
|
||||
%{id: conversation_id} = socket.assigns.current_tab
|
||||
current = Map.get(socket.assigns.chat_model_selectors_open, conversation_id, false)
|
||||
@@ -18,6 +19,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec set_model(term(), term(), term(), term()) :: term()
|
||||
def set_model(socket, model_id, reload, append_output) do
|
||||
%{id: conversation_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -37,6 +39,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
|
||||
end
|
||||
end
|
||||
|
||||
@spec group_available_models(term()) :: term()
|
||||
def group_available_models(models) when is_list(models) do
|
||||
models
|
||||
|> Enum.group_by(&Map.get(&1, :provider, "other"))
|
||||
@@ -54,6 +57,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
|
||||
|> Enum.sort_by(&String.downcase(to_string(&1.label)))
|
||||
end
|
||||
|
||||
@spec needs_api_key?(term()) :: term()
|
||||
def needs_api_key?(true), do: false
|
||||
|
||||
def needs_api_key?(false) do
|
||||
|
||||
@@ -14,9 +14,12 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
"render_tabs"
|
||||
])
|
||||
|
||||
@spec render_tool?(term()) :: term()
|
||||
def render_tool?(name) when is_binary(name), do: MapSet.member?(@render_tool_names, name)
|
||||
@spec render_tool?(term()) :: term()
|
||||
def render_tool?(_name), do: false
|
||||
|
||||
@spec build_render_surfaces(term(), term(), term()) :: term()
|
||||
def build_render_surfaces(tool_calls, message_id, assigns) do
|
||||
tool_calls
|
||||
|> Enum.with_index()
|
||||
@@ -28,6 +31,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec build_render_surface(term(), term(), term()) :: term()
|
||||
def build_render_surface(%{name: name, arguments: arguments}, surface_id, assigns) do
|
||||
if MapSet.member?(@render_tool_names, name) do
|
||||
do_build_render_surface(name, arguments || %{}, surface_id, assigns)
|
||||
@@ -51,6 +55,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
end
|
||||
end
|
||||
|
||||
@spec normalize_tool_surface(term()) :: term()
|
||||
def normalize_tool_surface(_content), do: nil
|
||||
|
||||
defp do_build_render_surface("render_card", arguments, surface_id, _assigns) do
|
||||
@@ -150,7 +155,12 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
label: map_value(field, "label", key),
|
||||
input_type: map_value(field, "inputType") || map_value(field, "input_type", "text"),
|
||||
placeholder: map_value(field, "placeholder"),
|
||||
value: Map.get(stored_fields, key, map_value(field, "defaultValue") || map_value(field, "default_value")),
|
||||
value:
|
||||
Map.get(
|
||||
stored_fields,
|
||||
key,
|
||||
map_value(field, "defaultValue") || map_value(field, "default_value")
|
||||
),
|
||||
options: decode_surface_options(map_value(field, "options", [])),
|
||||
required?: truthy?(map_value(field, "required", false))
|
||||
}
|
||||
@@ -161,8 +171,12 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
type: "form",
|
||||
title: map_value(arguments, "title"),
|
||||
fields: fields,
|
||||
submit_label: map_value(arguments, "submitLabel") || map_value(arguments, "submit_label", translated("chat.stop")),
|
||||
submit_action: map_value(arguments, "submitAction") || map_value(arguments, "submit_action", "submitForm")
|
||||
submit_label:
|
||||
map_value(arguments, "submitLabel") ||
|
||||
map_value(arguments, "submit_label", translated("chat.stop")),
|
||||
submit_action:
|
||||
map_value(arguments, "submitAction") ||
|
||||
map_value(arguments, "submit_action", "submitForm")
|
||||
}
|
||||
end
|
||||
|
||||
@@ -181,7 +195,11 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
|> List.wrap()
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {content, content_index} ->
|
||||
build_tab_surface(content, "#{surface_id}-tab-#{tab_index}-#{content_index}", assigns)
|
||||
build_tab_surface(
|
||||
content,
|
||||
"#{surface_id}-tab-#{tab_index}-#{content_index}",
|
||||
assigns
|
||||
)
|
||||
end)
|
||||
}
|
||||
end)
|
||||
@@ -203,11 +221,21 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
||||
type = map_value(content, "type", "text")
|
||||
|
||||
case type do
|
||||
render_type when render_type in ["card", "chart", "form", "list", "metric", "mindmap", "table", "tabs"] ->
|
||||
do_build_render_surface("render_#{render_type}", Map.delete(content, "type"), surface_id, assigns)
|
||||
render_type
|
||||
when render_type in ["card", "chart", "form", "list", "metric", "mindmap", "table", "tabs"] ->
|
||||
do_build_render_surface(
|
||||
"render_#{render_type}",
|
||||
Map.delete(content, "type"),
|
||||
surface_id,
|
||||
assigns
|
||||
)
|
||||
|
||||
"text" ->
|
||||
%{id: surface_id, type: "text", body: map_value(content, "body") || map_value(content, "text", "")}
|
||||
%{
|
||||
id: surface_id,
|
||||
type: "text",
|
||||
body: map_value(content, "body") || map_value(content, "text", "")
|
||||
}
|
||||
|
||||
_other ->
|
||||
%{id: surface_id, type: "json", raw: content}
|
||||
|
||||
@@ -3,10 +3,12 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolTracking do
|
||||
|
||||
@tool_args_max_length 30
|
||||
|
||||
@spec tool_call_name(term()) :: term()
|
||||
def tool_call_name(tool_call) when is_map(tool_call) do
|
||||
BDS.MapUtils.attr(tool_call, :name) || "tool"
|
||||
end
|
||||
|
||||
@spec tool_call_arguments(term()) :: term()
|
||||
def tool_call_arguments(tool_call) when is_map(tool_call) do
|
||||
BDS.MapUtils.attr(tool_call, :arguments) || BDS.MapUtils.attr(tool_call, :args) || %{}
|
||||
end
|
||||
@@ -25,6 +27,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolTracking do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec normalize_tool_calls(term()) :: term()
|
||||
def normalize_tool_calls(_tool_calls), do: []
|
||||
|
||||
def tool_arguments_preview(arguments) when is_map(arguments) do
|
||||
@@ -33,6 +36,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolTracking do
|
||||
|> Enum.join(", ")
|
||||
end
|
||||
|
||||
@spec tool_arguments_preview(term()) :: term()
|
||||
def tool_arguments_preview(_arguments), do: ""
|
||||
|
||||
def mark_tool_call_completed(entry, tool_call_id) when is_binary(tool_call_id) do
|
||||
@@ -47,8 +51,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolTracking do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec mark_tool_call_completed(term(), term()) :: term()
|
||||
def mark_tool_call_completed(entry, _tool_call_id), do: entry
|
||||
|
||||
@spec tool_markers_from_events(term()) :: term()
|
||||
def tool_markers_from_events(nil), do: []
|
||||
|
||||
def tool_markers_from_events(%{tool_events: tool_events}) do
|
||||
|
||||
@@ -10,12 +10,14 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
|
||||
embed_templates("code_entity_editor_html/*")
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
socket
|
||||
|> assign(:script_editor, build_script(socket.assigns))
|
||||
|> assign(:template_editor, build_template(socket.assigns))
|
||||
end
|
||||
|
||||
@spec update_script(term(), term(), term()) :: term()
|
||||
def update_script(socket, params, reload) do
|
||||
%{id: script_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -27,6 +29,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec save_script(term(), term(), term()) :: term()
|
||||
def save_script(socket, reload, append_output) do
|
||||
%{id: script_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -62,6 +65,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_script(term(), term(), term()) :: term()
|
||||
def check_script(socket, reload, append_output) do
|
||||
%{id: script_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -82,6 +86,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec run_script(term(), term(), term()) :: term()
|
||||
def run_script(socket, reload, append_output) do
|
||||
%{id: script_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -111,6 +116,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_script(term(), term(), term()) :: term()
|
||||
def delete_script(socket, reload, append_output) do
|
||||
%{id: script_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -124,6 +130,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_template(term(), term(), term()) :: term()
|
||||
def update_template(socket, params, reload) do
|
||||
%{id: template_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -139,6 +146,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec save_template(term(), term(), term()) :: term()
|
||||
def save_template(socket, reload, append_output) do
|
||||
%{id: template_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -169,6 +177,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec validate_template(term(), term(), term()) :: term()
|
||||
def validate_template(socket, reload, append_output) do
|
||||
%{id: template_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -195,6 +204,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_template(term(), term(), term()) :: term()
|
||||
def delete_template(socket, reload, append_output) do
|
||||
%{id: template_id} = socket.assigns.current_tab
|
||||
|
||||
@@ -211,6 +221,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_script(term()) :: term()
|
||||
def build_script(%{current_tab: %{type: :scripts, id: script_id}} = assigns) do
|
||||
case Scripts.get_script(script_id) do
|
||||
nil ->
|
||||
@@ -236,6 +247,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
|
||||
def build_script(_assigns), do: nil
|
||||
|
||||
@spec build_template(term()) :: term()
|
||||
def build_template(%{current_tab: %{type: :templates, id: template_id}} = assigns) do
|
||||
case Templates.get_template(template_id) do
|
||||
nil ->
|
||||
@@ -259,9 +271,11 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
||||
|
||||
def build_template(_assigns), do: nil
|
||||
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
@spec format_timestamp(term()) :: term()
|
||||
def format_timestamp(nil), do: ""
|
||||
def format_timestamp(timestamp), do: BDS.Persistence.timestamp_to_iso8601(timestamp)
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
total,
|
||||
detail,
|
||||
reload
|
||||
), to: ProgressTracking
|
||||
),
|
||||
to: ProgressTracking
|
||||
|
||||
defdelegate finish_execution(socket, ref, result, reload, append_output), to: ProgressTracking
|
||||
|
||||
@@ -72,6 +73,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
defdelegate clear_taxonomy_mapping(socket, params, reload), to: TaxonomyEditing
|
||||
defdelegate analyze_taxonomy_ai(socket, reload, append_output), to: TaxonomyEditing
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
case socket.assigns[:current_tab] do
|
||||
%{type: :import, id: definition_id} ->
|
||||
@@ -140,6 +142,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec toggle_section(term(), term(), term()) :: term()
|
||||
def toggle_section(socket, section, reload) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab,
|
||||
section_key
|
||||
@@ -171,6 +174,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec toggle_model_selector(term(), term()) :: term()
|
||||
def toggle_model_selector(socket, reload) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab do
|
||||
current = Map.get(socket.assigns.import_editor_model_selectors_open, definition_id, false)
|
||||
@@ -186,6 +190,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec select_ai_model(term(), term(), term()) :: term()
|
||||
def select_ai_model(socket, model_id, reload) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab do
|
||||
socket
|
||||
@@ -205,6 +210,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
|
||||
attr(:import_editor, :map, required: true)
|
||||
|
||||
@spec import_editor(term()) :: term()
|
||||
def import_editor(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
@@ -547,6 +553,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:expanded, :boolean, required: true)
|
||||
attr(:section, :string, required: true)
|
||||
|
||||
@spec conflict_section(term()) :: term()
|
||||
def conflict_section(assigns) do
|
||||
~H"""
|
||||
<section class="import-detail-section conflicts-section">
|
||||
@@ -597,6 +604,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:section, :string, required: true)
|
||||
attr(:show_type, :boolean, default: false)
|
||||
|
||||
@spec post_detail_section(term()) :: term()
|
||||
def post_detail_section(assigns) do
|
||||
~H"""
|
||||
<section class="import-detail-section">
|
||||
@@ -646,6 +654,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:expanded, :boolean, required: true)
|
||||
attr(:section, :string, required: true)
|
||||
|
||||
@spec media_detail_section(term()) :: term()
|
||||
def media_detail_section(assigns) do
|
||||
~H"""
|
||||
<section class="import-detail-section">
|
||||
@@ -685,6 +694,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:label, :string, required: true)
|
||||
attr(:stats, :map, required: true)
|
||||
|
||||
@spec stat_card(term()) :: term()
|
||||
def stat_card(assigns) do
|
||||
~H"""
|
||||
<div class="import-stat-card">
|
||||
@@ -703,6 +713,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:label, :string, required: true)
|
||||
attr(:stats, :map, required: true)
|
||||
|
||||
@spec other_stat_card(term()) :: term()
|
||||
def other_stat_card(assigns) do
|
||||
~H"""
|
||||
<div class="import-stat-card import-stat-card-other">
|
||||
@@ -720,6 +731,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:label, :string, required: true)
|
||||
attr(:stats, :map, required: true)
|
||||
|
||||
@spec media_stat_card(term()) :: term()
|
||||
def media_stat_card(assigns) do
|
||||
~H"""
|
||||
<div class="import-stat-card">
|
||||
@@ -739,6 +751,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:label, :string, required: true)
|
||||
attr(:stats, :map, required: true)
|
||||
|
||||
@spec taxonomy_stat_card(term()) :: term()
|
||||
def taxonomy_stat_card(assigns) do
|
||||
~H"""
|
||||
<div class="import-stat-card">
|
||||
@@ -759,6 +772,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
||||
attr(:edit, :map, default: nil)
|
||||
attr(:type, :string, required: true)
|
||||
|
||||
@spec taxonomy_group(term()) :: term()
|
||||
def taxonomy_group(assigns) do
|
||||
~H"""
|
||||
<div class="taxonomy-group">
|
||||
|
||||
@@ -4,20 +4,27 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
alias BDS.{ImportAnalysis, ImportDefinitions, Metadata}
|
||||
alias BDS.Desktop.{FilePicker, FolderPicker, ShellData}
|
||||
|
||||
@spec change_definition(term(), term(), term()) :: term()
|
||||
def change_definition(socket, params, reload) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab,
|
||||
{:ok, _definition} <- ImportDefinitions.update_definition(definition_id, %{name: Map.get(params, "name", "")}) do
|
||||
{:ok, _definition} <-
|
||||
ImportDefinitions.update_definition(definition_id, %{name: Map.get(params, "name", "")}) do
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
else
|
||||
_other -> reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@spec select_uploads_folder(term(), term(), term()) :: term()
|
||||
def select_uploads_folder(socket, reload, append_output) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab do
|
||||
case FolderPicker.choose_directory(translated("importAnalysis.uploadsFolder")) do
|
||||
{:ok, uploads_folder_path} ->
|
||||
{:ok, _definition} = ImportDefinitions.update_definition(definition_id, %{uploads_folder_path: uploads_folder_path})
|
||||
{:ok, _definition} =
|
||||
ImportDefinitions.update_definition(definition_id, %{
|
||||
uploads_folder_path: uploads_folder_path
|
||||
})
|
||||
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
|
||||
:cancel ->
|
||||
@@ -33,6 +40,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
end
|
||||
end
|
||||
|
||||
@spec select_and_analyze(term(), term(), term()) :: term()
|
||||
def select_and_analyze(socket, reload, append_output) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab,
|
||||
%{} = definition <- ImportDefinitions.get_definition(definition_id) do
|
||||
@@ -50,9 +58,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
|
||||
task =
|
||||
Task.Supervisor.async_nolink(BDS.Tasks.TaskSupervisor, fn ->
|
||||
ImportAnalysis.analyze_wxr(project_id, wxr_file_path, definition.uploads_folder_path,
|
||||
ImportAnalysis.analyze_wxr(
|
||||
project_id,
|
||||
wxr_file_path,
|
||||
definition.uploads_folder_path,
|
||||
on_progress: fn step, detail ->
|
||||
send(live_view_pid, {:import_analysis_progress, definition_id, translate_phase(step), detail})
|
||||
send(
|
||||
live_view_pid,
|
||||
{:import_analysis_progress, definition_id, translate_phase(step), detail}
|
||||
)
|
||||
end
|
||||
)
|
||||
end)
|
||||
@@ -70,8 +84,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
ref: task.ref
|
||||
})
|
||||
)
|
||||
|> Phoenix.Component.assign(:import_editor_analysis_task_refs, Map.put(socket.assigns.import_editor_analysis_task_refs, task.ref, definition_id))
|
||||
|> Phoenix.Component.assign(:import_editor_execution_states, Map.delete(socket.assigns.import_editor_execution_states, definition_id))
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_analysis_task_refs,
|
||||
Map.put(socket.assigns.import_editor_analysis_task_refs, task.ref, definition_id)
|
||||
)
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_execution_states,
|
||||
Map.delete(socket.assigns.import_editor_execution_states, definition_id)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
:cancel ->
|
||||
@@ -87,32 +107,50 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
end
|
||||
end
|
||||
|
||||
@spec note_analysis_progress(term(), term(), term(), term(), term()) :: term()
|
||||
def note_analysis_progress(socket, definition_id, step, detail, reload) do
|
||||
socket
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_analysis_states,
|
||||
Map.update(socket.assigns.import_editor_analysis_states, definition_id, default_analysis_state(), fn state ->
|
||||
state
|
||||
|> Map.put(:loading, true)
|
||||
|> Map.put(:step, step)
|
||||
|> Map.put(:detail, detail)
|
||||
end)
|
||||
Map.update(
|
||||
socket.assigns.import_editor_analysis_states,
|
||||
definition_id,
|
||||
default_analysis_state(),
|
||||
fn state ->
|
||||
state
|
||||
|> Map.put(:loading, true)
|
||||
|> Map.put(:step, step)
|
||||
|> Map.put(:detail, detail)
|
||||
end
|
||||
)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec finish_analysis(term(), term(), term(), term(), term()) :: term()
|
||||
def finish_analysis(socket, ref, result, reload, append_output) do
|
||||
case Map.get(socket.assigns.import_editor_analysis_task_refs, ref) do
|
||||
nil ->
|
||||
socket
|
||||
|
||||
definition_id ->
|
||||
analysis_state = Map.get(socket.assigns.import_editor_analysis_states, definition_id, default_analysis_state())
|
||||
analysis_state =
|
||||
Map.get(
|
||||
socket.assigns.import_editor_analysis_states,
|
||||
definition_id,
|
||||
default_analysis_state()
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> Phoenix.Component.assign(:import_editor_analysis_task_refs, Map.delete(socket.assigns.import_editor_analysis_task_refs, ref))
|
||||
|> Phoenix.Component.assign(:import_editor_analysis_states, Map.delete(socket.assigns.import_editor_analysis_states, definition_id))
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_analysis_task_refs,
|
||||
Map.delete(socket.assigns.import_editor_analysis_task_refs, ref)
|
||||
)
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_analysis_states,
|
||||
Map.delete(socket.assigns.import_editor_analysis_states, definition_id)
|
||||
)
|
||||
|
||||
case result do
|
||||
{:ok, report} ->
|
||||
@@ -146,6 +184,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
end
|
||||
end
|
||||
|
||||
@spec handle_analysis_task_down(term(), term(), term(), term(), term()) :: term()
|
||||
def handle_analysis_task_down(socket, ref, message, reload, append_output) do
|
||||
case Map.get(socket.assigns.import_editor_analysis_task_refs, ref) do
|
||||
nil ->
|
||||
@@ -153,13 +192,20 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
|
||||
definition_id ->
|
||||
socket
|
||||
|> Phoenix.Component.assign(:import_editor_analysis_task_refs, Map.delete(socket.assigns.import_editor_analysis_task_refs, ref))
|
||||
|> Phoenix.Component.assign(:import_editor_analysis_states, Map.delete(socket.assigns.import_editor_analysis_states, definition_id))
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_analysis_task_refs,
|
||||
Map.delete(socket.assigns.import_editor_analysis_task_refs, ref)
|
||||
)
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_analysis_states,
|
||||
Map.delete(socket.assigns.import_editor_analysis_states, definition_id)
|
||||
)
|
||||
|> append_output.(translated("activity.import"), message, nil, "error")
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@spec importable_counts(term()) :: term()
|
||||
def importable_counts(nil), do: %{total: 0, tags: 0, posts: 0, media: 0, pages: 0}
|
||||
|
||||
def importable_counts(report) do
|
||||
@@ -171,25 +217,37 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
pages = importable_entity_count(Map.get(report.items, :pages, []))
|
||||
media = importable_entity_count(Map.get(report.items, :media, []))
|
||||
|
||||
%{total: tag_count + posts + pages + media, tags: tag_count, posts: posts, media: media, pages: pages}
|
||||
%{
|
||||
total: tag_count + posts + pages + media,
|
||||
tags: tag_count,
|
||||
posts: posts,
|
||||
media: media,
|
||||
pages: pages
|
||||
}
|
||||
end
|
||||
|
||||
@spec importable_entity_count(term()) :: term()
|
||||
def importable_entity_count(items) do
|
||||
Enum.count(items || [], fn item ->
|
||||
item.status == "new" or (item.status == "conflict" and Map.get(item, :resolution, "ignore") not in ["ignore", "skip"])
|
||||
item.status == "new" or
|
||||
(item.status == "conflict" and
|
||||
Map.get(item, :resolution, "ignore") not in ["ignore", "skip"])
|
||||
end)
|
||||
end
|
||||
|
||||
@spec detail_items(term(), term()) :: term()
|
||||
def detail_items(nil, _bucket), do: []
|
||||
|
||||
def detail_items(report, bucket) do
|
||||
get_in(report, [:details, bucket]) || get_in(report, [:items, bucket]) || []
|
||||
end
|
||||
|
||||
@spec default_analysis_state() :: term()
|
||||
def default_analysis_state do
|
||||
%{loading: false, step: nil, detail: nil, file_path: nil, ref: nil}
|
||||
end
|
||||
|
||||
@spec default_sections() :: term()
|
||||
def default_sections do
|
||||
%{
|
||||
post_conflicts: true,
|
||||
@@ -203,18 +261,22 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
}
|
||||
end
|
||||
|
||||
@spec default_author(term()) :: term()
|
||||
def default_author(project_id) do
|
||||
{:ok, metadata} = Metadata.get_project_metadata(project_id)
|
||||
Map.get(metadata, :default_author)
|
||||
end
|
||||
|
||||
@spec suggested_definition_name(term()) :: term()
|
||||
def suggested_definition_name(report) do
|
||||
get_in(report, [:site_info, :url]) || get_in(report, [:site_info, :title])
|
||||
end
|
||||
|
||||
@spec maybe_put(term(), term(), term()) :: term()
|
||||
def maybe_put(map, _key, nil), do: map
|
||||
def maybe_put(map, key, value), do: Map.put(map, key, value)
|
||||
|
||||
@spec allow_repo_sandbox(term()) :: term()
|
||||
def allow_repo_sandbox(pid) when is_pid(pid) do
|
||||
if Code.ensure_loaded?(Ecto.Adapters.SQL.Sandbox) do
|
||||
try do
|
||||
@@ -241,8 +303,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
||||
end
|
||||
end
|
||||
|
||||
@spec translate_phase(term()) :: term()
|
||||
def translate_phase(other), do: other
|
||||
|
||||
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
defp translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
defp present?(value), do: value not in [nil, ""]
|
||||
end
|
||||
|
||||
@@ -3,18 +3,27 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ConflictResolution do
|
||||
|
||||
alias BDS.ImportDefinitions
|
||||
|
||||
def change_conflict_resolution(socket, %{"item_type" => item_type, "item_name" => item_name, "resolution" => resolution}, reload) do
|
||||
@spec change_conflict_resolution(term(), term(), term()) :: term()
|
||||
def change_conflict_resolution(
|
||||
socket,
|
||||
%{"item_type" => item_type, "item_name" => item_name, "resolution" => resolution},
|
||||
reload
|
||||
) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab,
|
||||
%{} = definition <- ImportDefinitions.get_definition(definition_id),
|
||||
%{} = report <- ImportDefinitions.decode_analysis_result(definition),
|
||||
updated_report <- update_conflict_resolution(report, item_type, item_name, resolution),
|
||||
{:ok, _definition} <- ImportDefinitions.update_definition(definition_id, %{last_analysis_result: updated_report}) do
|
||||
{:ok, _definition} <-
|
||||
ImportDefinitions.update_definition(definition_id, %{
|
||||
last_analysis_result: updated_report
|
||||
}) do
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
else
|
||||
_other -> reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_conflict_resolution(term(), term(), term(), term()) :: term()
|
||||
def update_conflict_resolution(report, item_type, item_name, resolution) do
|
||||
report
|
||||
|> update_in([:conflicts], fn conflicts ->
|
||||
@@ -30,10 +39,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ConflictResolution do
|
||||
|> update_in([:details], &update_conflict_bucket(&1, item_type, item_name, resolution))
|
||||
end
|
||||
|
||||
@spec update_conflict_bucket(term(), term(), term(), term()) :: term()
|
||||
def update_conflict_bucket(nil, _item_type, _item_name, _resolution), do: nil
|
||||
|
||||
def update_conflict_bucket(buckets, item_type, item_name, resolution) do
|
||||
bucket_key = if(item_type == "page", do: :pages, else: if(item_type == "media", do: :media, else: :posts))
|
||||
bucket_key =
|
||||
if(item_type == "page",
|
||||
do: :pages,
|
||||
else: if(item_type == "media", do: :media, else: :posts)
|
||||
)
|
||||
|
||||
update_in(buckets, [bucket_key], fn items ->
|
||||
Enum.map(items || [], fn item ->
|
||||
|
||||
@@ -5,6 +5,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
alias BDS.Desktop.ShellData
|
||||
alias BDS.Desktop.ShellLive.ImportEditor.AnalysisState
|
||||
|
||||
@spec execute_import(term(), term(), term()) :: term()
|
||||
def execute_import(socket, reload, _append_output) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab,
|
||||
%{} = definition <- ImportDefinitions.get_definition(definition_id),
|
||||
@@ -24,7 +25,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
uploads_folder_path: definition.uploads_folder_path,
|
||||
default_author: default_author,
|
||||
on_progress: fn phase, current, total, detail ->
|
||||
send(live_view_pid, {:import_execution_progress, definition_id, phase, current, total, detail})
|
||||
send(
|
||||
live_view_pid,
|
||||
{:import_execution_progress, definition_id, phase, current, total, detail}
|
||||
)
|
||||
end
|
||||
)
|
||||
end)
|
||||
@@ -50,7 +54,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
ref: task.ref
|
||||
})
|
||||
)
|
||||
|> Phoenix.Component.assign(:import_editor_execution_task_refs, Map.put(socket.assigns.import_editor_execution_task_refs, task.ref, definition_id))
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_execution_task_refs,
|
||||
Map.put(socket.assigns.import_editor_execution_task_refs, task.ref, definition_id)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
else
|
||||
@@ -58,6 +65,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
end
|
||||
end
|
||||
|
||||
@spec note_execution_progress(term(), term(), term(), term(), term(), term(), term()) :: term()
|
||||
def note_execution_progress(socket, definition_id, phase, current, total, detail, reload) do
|
||||
{detail_text, eta} = decompose_progress_detail(detail)
|
||||
translated_phase = translate_execution_phase(phase)
|
||||
@@ -65,30 +73,44 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
socket
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_execution_states,
|
||||
Map.update(socket.assigns.import_editor_execution_states, definition_id, default_execution_state(), fn state ->
|
||||
state
|
||||
|> Map.put(:is_executing, true)
|
||||
|> Map.put(:phase, translated_phase)
|
||||
|> Map.put(:current, current)
|
||||
|> Map.put(:total, total)
|
||||
|> Map.put(:detail, detail_text)
|
||||
|> Map.put(:eta, eta)
|
||||
end)
|
||||
Map.update(
|
||||
socket.assigns.import_editor_execution_states,
|
||||
definition_id,
|
||||
default_execution_state(),
|
||||
fn state ->
|
||||
state
|
||||
|> Map.put(:is_executing, true)
|
||||
|> Map.put(:phase, translated_phase)
|
||||
|> Map.put(:current, current)
|
||||
|> Map.put(:total, total)
|
||||
|> Map.put(:detail, detail_text)
|
||||
|> Map.put(:eta, eta)
|
||||
end
|
||||
)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec finish_execution(term(), term(), term(), term(), term()) :: term()
|
||||
def finish_execution(socket, ref, result, reload, append_output) do
|
||||
case Map.get(socket.assigns.import_editor_execution_task_refs, ref) do
|
||||
nil ->
|
||||
socket
|
||||
|
||||
definition_id ->
|
||||
previous_state = Map.get(socket.assigns.import_editor_execution_states, definition_id, default_execution_state())
|
||||
previous_state =
|
||||
Map.get(
|
||||
socket.assigns.import_editor_execution_states,
|
||||
definition_id,
|
||||
default_execution_state()
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> Phoenix.Component.assign(:import_editor_execution_task_refs, Map.delete(socket.assigns.import_editor_execution_task_refs, ref))
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_execution_task_refs,
|
||||
Map.delete(socket.assigns.import_editor_execution_task_refs, ref)
|
||||
)
|
||||
|
||||
case result do
|
||||
{:ok, execution_result} ->
|
||||
@@ -106,7 +128,12 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
ref: nil
|
||||
})
|
||||
)
|
||||
|> append_output.(translated("activity.import"), translated("importAnalysis.importComplete", %{count: previous_state.count}), nil, "info")
|
||||
|> append_output.(
|
||||
translated("activity.import"),
|
||||
translated("importAnalysis.importComplete", %{count: previous_state.count}),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
{:error, %{message: message}} ->
|
||||
@@ -144,7 +171,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_task_down(socket, kind, ref, reason, reload, append_output) when reason not in [:normal, :shutdown] do
|
||||
@spec handle_task_down(term(), term(), term(), term(), term(), term()) :: term()
|
||||
def handle_task_down(socket, kind, ref, reason, reload, append_output)
|
||||
when reason not in [:normal, :shutdown] do
|
||||
message = inspect(reason)
|
||||
|
||||
case kind do
|
||||
@@ -157,10 +186,18 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
socket
|
||||
|
||||
definition_id ->
|
||||
previous_state = Map.get(socket.assigns.import_editor_execution_states, definition_id, default_execution_state())
|
||||
previous_state =
|
||||
Map.get(
|
||||
socket.assigns.import_editor_execution_states,
|
||||
definition_id,
|
||||
default_execution_state()
|
||||
)
|
||||
|
||||
socket
|
||||
|> Phoenix.Component.assign(:import_editor_execution_task_refs, Map.delete(socket.assigns.import_editor_execution_task_refs, ref))
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_execution_task_refs,
|
||||
Map.delete(socket.assigns.import_editor_execution_task_refs, ref)
|
||||
)
|
||||
|> Phoenix.Component.assign(
|
||||
:import_editor_execution_states,
|
||||
Map.put(socket.assigns.import_editor_execution_states, definition_id, %{
|
||||
@@ -177,8 +214,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
end
|
||||
end
|
||||
|
||||
@spec handle_task_down(term(), term(), term(), term(), term(), term()) :: term()
|
||||
def handle_task_down(socket, _kind, _ref, _reason, _reload, _append_output), do: socket
|
||||
|
||||
@spec default_execution_state() :: term()
|
||||
def default_execution_state do
|
||||
%{
|
||||
is_executing: false,
|
||||
@@ -195,6 +234,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
}
|
||||
end
|
||||
|
||||
@spec execution_progress_width(term()) :: term()
|
||||
def execution_progress_width(state) do
|
||||
current = Map.get(state, :current, 0)
|
||||
total = Map.get(state, :total, 0)
|
||||
@@ -205,25 +245,36 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
end
|
||||
end
|
||||
|
||||
@spec decompose_progress_detail(term()) :: term()
|
||||
def decompose_progress_detail(%{detail: detail, eta: eta}), do: {to_string_or_nil(detail), eta}
|
||||
def decompose_progress_detail(detail) when is_binary(detail) or is_nil(detail), do: {detail, nil}
|
||||
|
||||
def decompose_progress_detail(detail) when is_binary(detail) or is_nil(detail),
|
||||
do: {detail, nil}
|
||||
|
||||
def decompose_progress_detail(detail), do: {to_string_or_nil(detail), nil}
|
||||
|
||||
@spec to_string_or_nil(term()) :: term()
|
||||
def to_string_or_nil(nil), do: nil
|
||||
def to_string_or_nil(value) when is_binary(value), do: value
|
||||
def to_string_or_nil(value), do: inspect(value)
|
||||
|
||||
@spec format_eta(term()) :: term()
|
||||
def format_eta(nil), do: nil
|
||||
|
||||
def format_eta(ms) when is_integer(ms) and ms >= 0 do
|
||||
seconds = div(ms, 1000)
|
||||
|
||||
if seconds < 60 do
|
||||
translated("importAnalysis.eta", %{value: translated("importAnalysis.etaSeconds", %{count: seconds})})
|
||||
translated("importAnalysis.eta", %{
|
||||
value: translated("importAnalysis.etaSeconds", %{count: seconds})
|
||||
})
|
||||
else
|
||||
m = div(seconds, 60)
|
||||
s = rem(seconds, 60)
|
||||
translated("importAnalysis.eta", %{value: translated("importAnalysis.etaMinutes", %{minutes: m, seconds: s})})
|
||||
|
||||
translated("importAnalysis.eta", %{
|
||||
value: translated("importAnalysis.etaMinutes", %{minutes: m, seconds: s})
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -240,7 +291,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
||||
end
|
||||
end
|
||||
|
||||
@spec translate_execution_phase(term()) :: term()
|
||||
def translate_execution_phase(other), do: other
|
||||
|
||||
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
defp translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
alias BDS.{AI, ImportDefinitions, Metadata, Tags}
|
||||
alias BDS.Desktop.ShellData
|
||||
|
||||
@spec start_taxonomy_edit(term(), term(), term()) :: term()
|
||||
def start_taxonomy_edit(
|
||||
socket,
|
||||
%{"type" => type, "name" => name, "mapped_to" => mapped_to},
|
||||
@@ -25,6 +26,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end
|
||||
end
|
||||
|
||||
@spec cancel_taxonomy_edit(term(), term()) :: term()
|
||||
def cancel_taxonomy_edit(socket, reload) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab do
|
||||
socket
|
||||
@@ -38,6 +40,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end
|
||||
end
|
||||
|
||||
@spec save_taxonomy_edit(term(), term(), term()) :: term()
|
||||
def save_taxonomy_edit(
|
||||
socket,
|
||||
%{"type" => type, "name" => name, "mapped_to" => mapped_to},
|
||||
@@ -68,10 +71,12 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end
|
||||
end
|
||||
|
||||
@spec clear_taxonomy_mapping(term(), term(), term()) :: term()
|
||||
def clear_taxonomy_mapping(socket, %{"type" => type, "name" => name}, reload) do
|
||||
save_taxonomy_edit(socket, %{"type" => type, "name" => name, "mapped_to" => ""}, reload)
|
||||
end
|
||||
|
||||
@spec analyze_taxonomy_ai(term(), term(), term()) :: term()
|
||||
def analyze_taxonomy_ai(socket, reload, append_output) do
|
||||
with %{id: definition_id} <- socket.assigns.current_tab,
|
||||
%{} = definition <- ImportDefinitions.get_definition(definition_id),
|
||||
@@ -142,6 +147,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_taxonomy_mapping(term(), term(), term(), term()) :: term()
|
||||
def update_taxonomy_mapping(report, type, name, mapped_to) do
|
||||
bucket_key = if(type == "categories", do: :categories, else: :tags)
|
||||
normalized_value = mapped_to |> to_string() |> String.trim() |> blank_to_nil()
|
||||
@@ -164,6 +170,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
)
|
||||
end
|
||||
|
||||
@spec rebuild_taxonomy_stats(term()) :: term()
|
||||
def rebuild_taxonomy_stats(items) do
|
||||
%{
|
||||
existing_count: Enum.count(items, & &1.exists_in_project),
|
||||
@@ -172,9 +179,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
}
|
||||
end
|
||||
|
||||
@spec stat_key(term()) :: term()
|
||||
def stat_key(:categories), do: :category_stats
|
||||
def stat_key(:tags), do: :tag_stats
|
||||
|
||||
@spec apply_taxonomy_mappings(term(), term()) :: term()
|
||||
def apply_taxonomy_mappings(report, analysis) do
|
||||
report
|
||||
|> update_in(
|
||||
@@ -198,6 +207,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec apply_taxonomy_mapping_bucket(term(), term()) :: term()
|
||||
def apply_taxonomy_mapping_bucket(items, mappings) do
|
||||
Enum.map(items || [], fn item ->
|
||||
case Map.fetch(mappings, item.name) do
|
||||
@@ -207,6 +217,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec existing_taxonomy_terms(term()) :: term()
|
||||
def existing_taxonomy_terms(project_id) do
|
||||
{:ok, metadata} = Metadata.get_project_metadata(project_id)
|
||||
|
||||
@@ -216,6 +227,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
}
|
||||
end
|
||||
|
||||
@spec normalize_taxonomy_mapping_value(term(), term(), term()) :: term()
|
||||
def normalize_taxonomy_mapping_value(project_id, type, mapped_to) do
|
||||
normalized_value = mapped_to |> to_string() |> String.trim() |> blank_to_nil()
|
||||
|
||||
@@ -231,6 +243,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end
|
||||
end
|
||||
|
||||
@spec auto_mapped_count(term(), term()) :: term()
|
||||
def auto_mapped_count(previous_report, next_report) do
|
||||
previous_count =
|
||||
(Map.get(previous_report.items, :categories, []) ++
|
||||
@@ -244,6 +257,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
max(next_count - previous_count, 0)
|
||||
end
|
||||
|
||||
@spec taxonomy_pill_class(term()) :: term()
|
||||
def taxonomy_pill_class(item) do
|
||||
cond do
|
||||
item.exists_in_project -> "import-taxonomy-pill exists"
|
||||
@@ -252,9 +266,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
end
|
||||
end
|
||||
|
||||
@spec taxonomy_item_editing?(term(), term(), term()) :: term()
|
||||
def taxonomy_item_editing?(%{type: type, name: name}, type, name), do: true
|
||||
def taxonomy_item_editing?(_edit, _type, _name), do: false
|
||||
|
||||
@spec taxonomy_mapping_tooltip(term()) :: term()
|
||||
def taxonomy_mapping_tooltip(item) do
|
||||
action =
|
||||
if present?(item.mapped_to),
|
||||
@@ -264,6 +280,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
|
||||
translated("importAnalysis.mappingTooltip", %{action: action})
|
||||
end
|
||||
|
||||
@spec maybe_put_option(term(), term(), term()) :: term()
|
||||
def maybe_put_option(opts, _key, nil), do: opts
|
||||
def maybe_put_option(opts, key, value), do: Keyword.put(opts, key, value)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ defmodule BDS.Desktop.ShellLive.Layout do
|
||||
end
|
||||
|
||||
defp maybe_set_sidebar_width(workbench, nil), do: workbench
|
||||
|
||||
defp maybe_set_sidebar_width(workbench, width),
|
||||
do: Workbench.set_sidebar_width(workbench, parse_width(width))
|
||||
|
||||
|
||||
@@ -13,14 +13,16 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
alias BDS.Repo
|
||||
alias BDS.UI.Workbench
|
||||
|
||||
embed_templates "media_editor_html/*"
|
||||
embed_templates("media_editor_html/*")
|
||||
|
||||
@post_picker_limit 10
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
assign(socket, :media_editor, build(socket.assigns))
|
||||
end
|
||||
|
||||
@spec update(term(), term(), term()) :: term()
|
||||
def update(socket, params, reload) do
|
||||
case socket.assigns.current_tab do
|
||||
%{type: :media, id: media_id} ->
|
||||
@@ -38,6 +40,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec persist_socket(term(), term(), term(), term()) :: term()
|
||||
def persist_socket(socket, media_id, reload, append_output) do
|
||||
case Media.get_media(media_id) do
|
||||
nil ->
|
||||
@@ -52,9 +55,18 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|
||||
socket
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:media_editor_drafts, Map.delete(socket.assigns.media_editor_drafts, media_id))
|
||||
|> assign(:media_editor_save_states, Map.put(socket.assigns.media_editor_save_states, media_id, :saved))
|
||||
|> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:media, media_id}, tab_meta(updated_media)))
|
||||
|> assign(
|
||||
:media_editor_drafts,
|
||||
Map.delete(socket.assigns.media_editor_drafts, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_save_states,
|
||||
Map.put(socket.assigns.media_editor_save_states, media_id, :saved)
|
||||
)
|
||||
|> assign(
|
||||
:tab_meta,
|
||||
Map.put(socket.assigns.tab_meta, {:media, media_id}, tab_meta(updated_media))
|
||||
)
|
||||
|> reload.(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -65,14 +77,19 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec toggle_quick_actions(term(), term(), term()) :: term()
|
||||
def toggle_quick_actions(socket, media_id, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
|
||||
socket
|
||||
|> assign(:media_editor_quick_actions_open, Map.update(socket.assigns.media_editor_quick_actions_open, media_id, true, &(!&1)))
|
||||
|> assign(
|
||||
:media_editor_quick_actions_open,
|
||||
Map.update(socket.assigns.media_editor_quick_actions_open, media_id, true, &(!&1))
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec replace_file(term(), term(), term(), term()) :: term()
|
||||
def replace_file(socket, media_id, reload, append_output) do
|
||||
case FilePicker.choose_file(translated("Replace Media File")) do
|
||||
{:ok, source_path} ->
|
||||
@@ -82,9 +99,18 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|
||||
socket
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:media_editor_drafts, Map.delete(socket.assigns.media_editor_drafts, media_id))
|
||||
|> assign(:media_editor_save_states, Map.put(socket.assigns.media_editor_save_states, media_id, :saved))
|
||||
|> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:media, media_id}, tab_meta(updated_media)))
|
||||
|> assign(
|
||||
:media_editor_drafts,
|
||||
Map.delete(socket.assigns.media_editor_drafts, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_save_states,
|
||||
Map.put(socket.assigns.media_editor_save_states, media_id, :saved)
|
||||
)
|
||||
|> assign(
|
||||
:tab_meta,
|
||||
Map.put(socket.assigns.tab_meta, {:media, media_id}, tab_meta(updated_media))
|
||||
)
|
||||
|> reload.(workbench)
|
||||
|
||||
{:ok, nil} ->
|
||||
@@ -106,10 +132,16 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec detect_language(term(), term(), term(), term()) :: term()
|
||||
def detect_language(socket, media_id, reload, append_output) do
|
||||
if Map.get(socket.assigns, :offline_mode, true) do
|
||||
socket
|
||||
|> append_output.(translated("Detect Language"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info")
|
||||
|> append_output.(
|
||||
translated("Detect Language"),
|
||||
translated("Automatic AI actions stay gated by airplane mode."),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
else
|
||||
case Media.get_media(media_id) do
|
||||
@@ -118,15 +150,26 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|
||||
%MediaRecord{} = media ->
|
||||
draft = current_draft(socket.assigns, media)
|
||||
text = Enum.join([Map.get(draft, "title", ""), Map.get(draft, "alt", ""), Map.get(draft, "caption", "")], "\n\n")
|
||||
|
||||
text =
|
||||
Enum.join(
|
||||
[
|
||||
Map.get(draft, "title", ""),
|
||||
Map.get(draft, "alt", ""),
|
||||
Map.get(draft, "caption", "")
|
||||
],
|
||||
"\n\n"
|
||||
)
|
||||
|
||||
case AI.detect_language(text) do
|
||||
{:ok, %{language_code: language_code}} when is_binary(language_code) and language_code != "" ->
|
||||
{:ok, %{language_code: language_code}}
|
||||
when is_binary(language_code) and language_code != "" ->
|
||||
normalized = normalize_language(language_code)
|
||||
|
||||
case Media.update_media(media.id, %{language: normalized}) do
|
||||
{:ok, updated_media} ->
|
||||
updated_draft = Map.put(current_draft(socket.assigns, media), "language", normalized)
|
||||
updated_draft =
|
||||
Map.put(current_draft(socket.assigns, media), "language", normalized)
|
||||
|
||||
socket
|
||||
|> reconcile_draft(updated_media, updated_draft)
|
||||
@@ -145,17 +188,28 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|
||||
_other ->
|
||||
socket
|
||||
|> append_output.(translated("Detect Language"), translated("Language detection failed."), nil, "error")
|
||||
|> append_output.(
|
||||
translated("Detect Language"),
|
||||
translated("Language detection failed."),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec translate(term(), term(), term(), term(), term()) :: term()
|
||||
def translate(socket, media_id, language, reload, append_output) do
|
||||
if Map.get(socket.assigns, :offline_mode, true) do
|
||||
socket
|
||||
|> append_output.(translated("Translate"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info")
|
||||
|> append_output.(
|
||||
translated("Translate"),
|
||||
translated("Automatic AI actions stay gated by airplane mode."),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
else
|
||||
normalized_language = normalize_language(language)
|
||||
@@ -165,8 +219,14 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
case Media.upsert_media_translation(media_id, normalized_language, translation) do
|
||||
{:ok, _saved_translation} ->
|
||||
socket
|
||||
|> assign(:media_editor_quick_actions_open, Map.put(socket.assigns.media_editor_quick_actions_open, media_id, false))
|
||||
|> assign(:media_editor_translation_forms, Map.delete(socket.assigns.media_editor_translation_forms, media_id))
|
||||
|> assign(
|
||||
:media_editor_quick_actions_open,
|
||||
Map.put(socket.assigns.media_editor_quick_actions_open, media_id, false)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.delete(socket.assigns.media_editor_translation_forms, media_id)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -183,6 +243,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec apply_ai_suggestions(term(), term(), term(), term(), term()) :: term()
|
||||
def apply_ai_suggestions(socket, media_id, fields, reload, append_output) do
|
||||
try do
|
||||
case Media.get_media(media_id) do
|
||||
@@ -213,6 +274,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_socket(term(), term(), term(), term()) :: term()
|
||||
def delete_socket(socket, media_id, reload, append_output) do
|
||||
case Media.delete_media(media_id) do
|
||||
{:ok, :deleted} ->
|
||||
@@ -223,11 +285,26 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:media, media_id}))
|
||||
|> assign(:media_editor_drafts, Map.delete(socket.assigns.media_editor_drafts, media_id))
|
||||
|> assign(:media_editor_quick_actions_open, Map.delete(socket.assigns.media_editor_quick_actions_open, media_id))
|
||||
|> assign(:media_editor_post_pickers_open, Map.delete(socket.assigns.media_editor_post_pickers_open, media_id))
|
||||
|> assign(:media_editor_post_picker_queries, Map.delete(socket.assigns.media_editor_post_picker_queries, media_id))
|
||||
|> assign(:media_editor_save_states, Map.delete(socket.assigns.media_editor_save_states, media_id))
|
||||
|> assign(:media_editor_translation_forms, Map.delete(socket.assigns.media_editor_translation_forms, media_id))
|
||||
|> assign(
|
||||
:media_editor_quick_actions_open,
|
||||
Map.delete(socket.assigns.media_editor_quick_actions_open, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_post_pickers_open,
|
||||
Map.delete(socket.assigns.media_editor_post_pickers_open, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_post_picker_queries,
|
||||
Map.delete(socket.assigns.media_editor_post_picker_queries, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_save_states,
|
||||
Map.delete(socket.assigns.media_editor_save_states, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.delete(socket.assigns.media_editor_translation_forms, media_id)
|
||||
)
|
||||
|> reload.(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -237,28 +314,43 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec toggle_post_picker(term(), term(), term()) :: term()
|
||||
def toggle_post_picker(socket, media_id, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
|
||||
socket
|
||||
|> assign(:media_editor_post_pickers_open, Map.update(socket.assigns.media_editor_post_pickers_open, media_id, true, &(!&1)))
|
||||
|> assign(
|
||||
:media_editor_post_pickers_open,
|
||||
Map.update(socket.assigns.media_editor_post_pickers_open, media_id, true, &(!&1))
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec set_post_picker_query(term(), term(), term(), term()) :: term()
|
||||
def set_post_picker_query(socket, media_id, query, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
|
||||
socket
|
||||
|> assign(:media_editor_post_picker_queries, Map.put(socket.assigns.media_editor_post_picker_queries, media_id, to_string(query || "")))
|
||||
|> assign(
|
||||
:media_editor_post_picker_queries,
|
||||
Map.put(socket.assigns.media_editor_post_picker_queries, media_id, to_string(query || ""))
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec link_post(term(), term(), term(), term(), term()) :: term()
|
||||
def link_post(socket, media_id, post_id, reload, append_output) do
|
||||
case Media.link_media_to_post(media_id, post_id) do
|
||||
{:ok, _linked} ->
|
||||
socket
|
||||
|> assign(:media_editor_post_pickers_open, Map.put(socket.assigns.media_editor_post_pickers_open, media_id, false))
|
||||
|> assign(:media_editor_post_picker_queries, Map.put(socket.assigns.media_editor_post_picker_queries, media_id, ""))
|
||||
|> assign(
|
||||
:media_editor_post_pickers_open,
|
||||
Map.put(socket.assigns.media_editor_post_pickers_open, media_id, false)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_post_picker_queries,
|
||||
Map.put(socket.assigns.media_editor_post_picker_queries, media_id, "")
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -268,6 +360,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec unlink_post(term(), term(), term(), term(), term()) :: term()
|
||||
def unlink_post(socket, media_id, post_id, reload, append_output) do
|
||||
case Media.unlink_media_from_post(media_id, post_id) do
|
||||
{:ok, _unlinked} ->
|
||||
@@ -280,6 +373,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec edit_translation(term(), term(), term(), term()) :: term()
|
||||
def edit_translation(socket, media_id, language, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
|
||||
@@ -287,16 +381,20 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|
||||
form = %{
|
||||
"language" => language,
|
||||
"title" => translation && translation.title || "",
|
||||
"alt" => translation && translation.alt || "",
|
||||
"caption" => translation && translation.caption || ""
|
||||
"title" => (translation && translation.title) || "",
|
||||
"alt" => (translation && translation.alt) || "",
|
||||
"caption" => (translation && translation.caption) || ""
|
||||
}
|
||||
|
||||
socket
|
||||
|> assign(:media_editor_translation_forms, Map.put(socket.assigns.media_editor_translation_forms, media_id, form))
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.put(socket.assigns.media_editor_translation_forms, media_id, form)
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec update_translation(term(), term(), term(), term()) :: term()
|
||||
def update_translation(socket, media_id, params, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
|
||||
@@ -308,10 +406,14 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
}
|
||||
|
||||
socket
|
||||
|> assign(:media_editor_translation_forms, Map.put(socket.assigns.media_editor_translation_forms, media_id, form))
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.put(socket.assigns.media_editor_translation_forms, media_id, form)
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec save_translation(term(), term(), term(), term()) :: term()
|
||||
def save_translation(socket, media_id, reload, append_output) do
|
||||
case Map.get(socket.assigns.media_editor_translation_forms, media_id) do
|
||||
%{"language" => language} = form when language not in [nil, ""] ->
|
||||
@@ -322,7 +424,10 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
}) do
|
||||
{:ok, _translation} ->
|
||||
socket
|
||||
|> assign(:media_editor_translation_forms, Map.delete(socket.assigns.media_editor_translation_forms, media_id))
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.delete(socket.assigns.media_editor_translation_forms, media_id)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -336,16 +441,23 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec refresh_translation(term(), term(), term(), term(), term()) :: term()
|
||||
def refresh_translation(socket, media_id, language, reload, append_output) do
|
||||
if Map.get(socket.assigns, :offline_mode, true) do
|
||||
socket
|
||||
|> append_output.(translated("Translate"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info")
|
||||
|> append_output.(
|
||||
translated("Translate"),
|
||||
translated("Automatic AI actions stay gated by airplane mode."),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
else
|
||||
case AI.translate_media(media_id, normalize_language(language)) do
|
||||
{:ok, translation} ->
|
||||
case Media.upsert_media_translation(media_id, language, translation) do
|
||||
{:ok, _saved_translation} -> socket |> reload.(socket.assigns.workbench)
|
||||
{:ok, _saved_translation} ->
|
||||
socket |> reload.(socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
@@ -361,11 +473,15 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_translation(term(), term(), term(), term(), term()) :: term()
|
||||
def delete_translation(socket, media_id, language, reload, append_output) do
|
||||
case Media.delete_media_translation(media_id, language) do
|
||||
{:ok, _deleted?} ->
|
||||
socket
|
||||
|> assign(:media_editor_translation_forms, Map.delete(socket.assigns.media_editor_translation_forms, media_id))
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.delete(socket.assigns.media_editor_translation_forms, media_id)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -375,6 +491,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec build(term()) :: term()
|
||||
def build(%{current_tab: %{type: :media, id: media_id}} = assigns) do
|
||||
case Media.get_media(media_id) do
|
||||
nil ->
|
||||
@@ -385,7 +502,9 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
translations = Media.list_media_translations(media.id)
|
||||
form = current_draft(assigns, media)
|
||||
picker_query = Map.get(assigns.media_editor_post_picker_queries, media.id, "")
|
||||
{picker_results, picker_overflow_count} = post_picker_results(media, linked_posts, picker_query)
|
||||
|
||||
{picker_results, picker_overflow_count} =
|
||||
post_picker_results(media, linked_posts, picker_query)
|
||||
|
||||
%{
|
||||
id: media.id,
|
||||
@@ -416,20 +535,26 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
|
||||
def build(_assigns), do: nil
|
||||
|
||||
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
@spec media_editor_save_state_label(term()) :: term()
|
||||
def media_editor_save_state_label(:dirty), do: translated("Unsaved")
|
||||
def media_editor_save_state_label(:saved), do: translated("Saved")
|
||||
def media_editor_save_state_label(_state), do: translated("Idle")
|
||||
|
||||
@spec language_label(term()) :: term()
|
||||
def language_label(code) do
|
||||
code
|
||||
|> to_string()
|
||||
|> String.upcase()
|
||||
end
|
||||
|
||||
@spec normalize_language(term()) :: term()
|
||||
def normalize_language(value), do: value |> to_string() |> String.trim() |> String.downcase()
|
||||
|
||||
@spec persist(term(), term()) :: term()
|
||||
def persist(%MediaRecord{} = media, draft) do
|
||||
Media.update_media(media.id, %{
|
||||
title: blank_to_nil(Map.get(draft, "title")),
|
||||
@@ -444,7 +569,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
defp reconcile_draft(socket, %MediaRecord{} = media, draft) do
|
||||
persisted = persisted_form(media)
|
||||
dirty? = draft != persisted
|
||||
workbench = if dirty?, do: Workbench.mark_dirty(socket.assigns.workbench, :media, media.id), else: Workbench.clear_dirty(socket.assigns.workbench, :media, media.id)
|
||||
|
||||
workbench =
|
||||
if dirty?,
|
||||
do: Workbench.mark_dirty(socket.assigns.workbench, :media, media.id),
|
||||
else: Workbench.clear_dirty(socket.assigns.workbench, :media, media.id)
|
||||
|
||||
drafts =
|
||||
if dirty? do
|
||||
@@ -456,8 +585,21 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
socket
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:media_editor_drafts, drafts)
|
||||
|> assign(:media_editor_save_states, Map.put(socket.assigns.media_editor_save_states, media.id, if(dirty?, do: :dirty, else: :idle)))
|
||||
|> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:media, media.id}, %{title: blank_to_nil(Map.get(draft, "title")) || display_title(media), subtitle: media.original_name || media.mime_type || ""}))
|
||||
|> assign(
|
||||
:media_editor_save_states,
|
||||
Map.put(
|
||||
socket.assigns.media_editor_save_states,
|
||||
media.id,
|
||||
if(dirty?, do: :dirty, else: :idle)
|
||||
)
|
||||
)
|
||||
|> assign(
|
||||
:tab_meta,
|
||||
Map.put(socket.assigns.tab_meta, {:media, media.id}, %{
|
||||
title: blank_to_nil(Map.get(draft, "title")) || display_title(media),
|
||||
subtitle: media.original_name || media.mime_type || ""
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
defp current_draft(assigns, %MediaRecord{} = media) do
|
||||
@@ -505,10 +647,15 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
from post in Post,
|
||||
where: post.project_id == ^media.project_id,
|
||||
order_by: [desc: post.updated_at, desc: post.created_at],
|
||||
select: %{post_id: post.id, title: fragment("COALESCE(?, ?, ?)", post.title, post.slug, post.id)}
|
||||
select: %{
|
||||
post_id: post.id,
|
||||
title: fragment("COALESCE(?, ?, ?)", post.title, post.slug, post.id)
|
||||
}
|
||||
)
|
||||
|> Enum.reject(&MapSet.member?(linked_ids, &1.post_id))
|
||||
|> Enum.filter(fn post -> normalized_query == "" or String.contains?(String.downcase(post.title), normalized_query) end)
|
||||
|> Enum.filter(fn post ->
|
||||
normalized_query == "" or String.contains?(String.downcase(post.title), normalized_query)
|
||||
end)
|
||||
|
||||
{Enum.take(posts, @post_picker_limit), max(length(posts) - @post_picker_limit, 0)}
|
||||
end
|
||||
@@ -518,18 +665,28 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
|
||||
defp preview_url(%MediaRecord{} = media) do
|
||||
if image?(media), do: "/media-thumbnail/#{media.id}?size=large&t=#{media.updated_at}", else: nil
|
||||
if image?(media),
|
||||
do: "/media-thumbnail/#{media.id}?size=large&t=#{media.updated_at}",
|
||||
else: nil
|
||||
end
|
||||
|
||||
defp image?(%MediaRecord{} = media), do: String.starts_with?(to_string(media.mime_type || ""), "image/")
|
||||
defp image?(%MediaRecord{} = media),
|
||||
do: String.starts_with?(to_string(media.mime_type || ""), "image/")
|
||||
|
||||
defp display_title(%MediaRecord{} = media), do: blank_to_nil(media.title) || blank_to_nil(media.original_name) || media.id
|
||||
defp display_title(%MediaRecord{} = media),
|
||||
do: blank_to_nil(media.title) || blank_to_nil(media.original_name) || media.id
|
||||
|
||||
defp dimensions_label(%MediaRecord{width: width, height: height})
|
||||
when is_integer(width) and is_integer(height), do: "#{width} x #{height}"
|
||||
|
||||
defp dimensions_label(%MediaRecord{width: width, height: height}) when is_integer(width) and is_integer(height), do: "#{width} x #{height}"
|
||||
defp dimensions_label(_media), do: nil
|
||||
|
||||
defp format_file_size(size) when is_integer(size) and size >= 1_048_576, do: :erlang.float_to_binary(size / 1_048_576, decimals: 1) <> " MB"
|
||||
defp format_file_size(size) when is_integer(size), do: :erlang.float_to_binary(size / 1024, decimals: 1) <> " KB"
|
||||
defp format_file_size(size) when is_integer(size) and size >= 1_048_576,
|
||||
do: :erlang.float_to_binary(size / 1_048_576, decimals: 1) <> " MB"
|
||||
|
||||
defp format_file_size(size) when is_integer(size),
|
||||
do: :erlang.float_to_binary(size / 1024, decimals: 1) <> " KB"
|
||||
|
||||
defp format_file_size(_size), do: "0.0 KB"
|
||||
|
||||
defp detect_language_enabled?(form) do
|
||||
@@ -567,5 +724,6 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
||||
end
|
||||
end
|
||||
|
||||
defp reload_with_assigned_workbench(socket, reload), do: reload.(socket, socket.assigns.workbench)
|
||||
defp reload_with_assigned_workbench(socket, reload),
|
||||
do: reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
use Phoenix.Component
|
||||
|
||||
alias BDS.Desktop.ShellData
|
||||
|
||||
alias BDS.Desktop.ShellLive.MenuEditor.{
|
||||
DraftManagement,
|
||||
PageCategory,
|
||||
@@ -12,8 +13,9 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
TreePredicates
|
||||
}
|
||||
|
||||
embed_templates "menu_editor_html/*"
|
||||
embed_templates("menu_editor_html/*")
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
case socket.assigns[:current_tab] do
|
||||
%{type: :menu_editor, id: tab_id} ->
|
||||
@@ -36,12 +38,14 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec select_item(term(), term(), term()) :: term()
|
||||
def select_item(socket, item_id, reload) do
|
||||
socket
|
||||
|> State.update_state(fn state -> %{state | selected_id: item_id} end)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec change_entry(term(), term(), term()) :: term()
|
||||
def change_entry(socket, params, reload) do
|
||||
query = Map.get(params, "query", "")
|
||||
|
||||
@@ -50,6 +54,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec submit_entry(term(), term()) :: term()
|
||||
def submit_entry(socket, reload) do
|
||||
case DraftManagement.current_draft(socket.assigns) do
|
||||
%{type: :page} ->
|
||||
@@ -67,12 +72,14 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec cancel_entry(term(), term()) :: term()
|
||||
def cancel_entry(socket, reload) do
|
||||
socket
|
||||
|> State.update_state(&DraftManagement.cancel_draft/1)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec select_page(term(), term(), term()) :: term()
|
||||
def select_page(socket, post_id, reload) do
|
||||
case PageCategory.page_post(socket.assigns.projects.active_project_id, post_id) do
|
||||
nil ->
|
||||
@@ -85,6 +92,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec select_category(term(), term(), term()) :: term()
|
||||
def select_category(socket, name, reload) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
@@ -99,6 +107,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec toolbar_action(term(), term(), term(), term()) :: term()
|
||||
def toolbar_action(socket, action, reload, append_output) do
|
||||
case action do
|
||||
"add-entry" ->
|
||||
@@ -144,12 +153,14 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec drop_item(term(), term(), term(), term(), term()) :: term()
|
||||
def drop_item(socket, drag_item_id, target_item_id, position, reload) do
|
||||
socket
|
||||
|> State.update_state(&TreeOps.drop_selected(&1, drag_item_id, target_item_id, position))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec handle_keydown(term(), term(), term()) :: term()
|
||||
def handle_keydown(socket, "Escape", reload) do
|
||||
cancel_entry(socket, reload)
|
||||
end
|
||||
@@ -158,14 +169,16 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
|
||||
attr :menu_editor, :map, required: true
|
||||
attr(:menu_editor, :map, required: true)
|
||||
|
||||
@spec menu_editor(term()) :: term()
|
||||
def menu_editor(assigns)
|
||||
|
||||
attr :items, :list, required: true
|
||||
attr :menu_editor, :map, required: true
|
||||
attr :depth, :integer, required: true
|
||||
attr(:items, :list, required: true)
|
||||
attr(:menu_editor, :map, required: true)
|
||||
attr(:depth, :integer, required: true)
|
||||
|
||||
@spec menu_tree_level(term()) :: term()
|
||||
def menu_tree_level(assigns) do
|
||||
~H"""
|
||||
<%= for item <- @items do %>
|
||||
@@ -289,8 +302,9 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
"""
|
||||
end
|
||||
|
||||
attr :kind, :atom, required: true
|
||||
attr(:kind, :atom, required: true)
|
||||
|
||||
@spec kind_icon(term()) :: term()
|
||||
def kind_icon(assigns) do
|
||||
~H"""
|
||||
<%= case @kind do %>
|
||||
@@ -306,9 +320,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
"""
|
||||
end
|
||||
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
@spec row_label(term(), term()) :: term()
|
||||
def row_label(item, category_titles) do
|
||||
if item.kind == :category_archive do
|
||||
Map.get(category_titles || %{}, item.slug, item.label)
|
||||
@@ -317,6 +333,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec kind_label(term()) :: term()
|
||||
def kind_label(:home), do: translated("menuEditor.type.home")
|
||||
def kind_label(:page), do: translated("menuEditor.type.page")
|
||||
def kind_label(:category_archive), do: translated("menuEditor.type.categoryArchive")
|
||||
@@ -324,12 +341,17 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
||||
|
||||
defdelegate draft_item?(menu_editor, item_id), to: TreePredicates
|
||||
|
||||
@spec editing_title(term()) :: term()
|
||||
def editing_title(%{draft: %{type: :category}}), do: translated("menuEditor.addCategoryArchive")
|
||||
def editing_title(_menu_editor), do: translated("menuEditor.pagePicker.title")
|
||||
|
||||
@spec editing_hint(term()) :: term()
|
||||
def editing_hint(%{draft: %{type: :category}}), do: translated("menuEditor.categoryPicker.hint")
|
||||
def editing_hint(_menu_editor), do: translated("menuEditor.createHint")
|
||||
|
||||
def editing_placeholder(%{draft: %{type: :category}}), do: translated("menuEditor.newCategoryPlaceholder")
|
||||
@spec editing_placeholder(term()) :: term()
|
||||
def editing_placeholder(%{draft: %{type: :category}}),
|
||||
do: translated("menuEditor.newCategoryPlaceholder")
|
||||
|
||||
def editing_placeholder(_menu_editor), do: translated("menuEditor.newEntryPlaceholder")
|
||||
end
|
||||
|
||||
@@ -6,8 +6,10 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
alias BDS.Desktop.ShellLive.MenuEditor.PageCategory
|
||||
alias BDS.Desktop.ShellLive.MenuEditor.TreeOps
|
||||
|
||||
@spec current_draft(term()) :: term()
|
||||
def current_draft(assigns), do: Map.get(assigns.menu_editor_state || %{}, :draft)
|
||||
|
||||
@spec start_page_draft(term()) :: term()
|
||||
def start_page_draft(state) do
|
||||
item = %{
|
||||
item_id: Ecto.UUID.generate(),
|
||||
@@ -29,6 +31,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
}
|
||||
end
|
||||
|
||||
@spec start_category_draft(term()) :: term()
|
||||
def start_category_draft(state) do
|
||||
item = %{
|
||||
item_id: Ecto.UUID.generate(),
|
||||
@@ -50,6 +53,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
}
|
||||
end
|
||||
|
||||
@spec finalize_submenu_draft(term()) :: term()
|
||||
def finalize_submenu_draft(%{draft: %{item_id: item_id, query: query}} = state) do
|
||||
label =
|
||||
if(String.trim(query) == "",
|
||||
@@ -69,12 +73,19 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
|
||||
def finalize_submenu_draft(state), do: state
|
||||
|
||||
@spec assign_page_to_draft(term(), term()) :: term()
|
||||
def assign_page_to_draft(%{draft: %{item_id: item_id}} = state, post) do
|
||||
%{
|
||||
state
|
||||
| items:
|
||||
TreeOps.update_item(state.items, item_id, fn item ->
|
||||
%{item | kind: :page, label: post.title, slug: PageCategory.blank_to_nil(post.slug), children: []}
|
||||
%{
|
||||
item
|
||||
| kind: :page,
|
||||
label: post.title,
|
||||
slug: PageCategory.blank_to_nil(post.slug),
|
||||
children: []
|
||||
}
|
||||
end),
|
||||
draft: nil
|
||||
}
|
||||
@@ -82,6 +93,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
|
||||
def assign_page_to_draft(state, _post), do: state
|
||||
|
||||
@spec assign_category_to_draft(term(), term()) :: term()
|
||||
def assign_category_to_draft(%{draft: %{item_id: item_id}} = state, category) do
|
||||
label = PageCategory.blank_to_nil(category.title) || category.name
|
||||
|
||||
@@ -97,6 +109,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
|
||||
def assign_category_to_draft(state, _category), do: state
|
||||
|
||||
@spec cancel_draft(term()) :: term()
|
||||
def cancel_draft(%{draft: %{item_id: item_id}} = state) do
|
||||
items = TreeOps.remove_item(state.items, item_id)
|
||||
%{state | items: items, selected_id: TreeOps.first_item_id(items), draft: nil}
|
||||
@@ -104,6 +117,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
|
||||
def cancel_draft(state), do: state
|
||||
|
||||
@spec confirm_category_draft(term(), term()) :: term()
|
||||
def confirm_category_draft(socket, update_state_fun) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
draft = current_draft(socket.assigns)
|
||||
@@ -117,8 +131,12 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
||||
|
||||
category =
|
||||
cond do
|
||||
category != nil -> category
|
||||
normalized == "" -> %{name: "", title: ""}
|
||||
category != nil ->
|
||||
category
|
||||
|
||||
normalized == "" ->
|
||||
%{name: "", title: ""}
|
||||
|
||||
true ->
|
||||
{:ok, _metadata} = Metadata.add_category(project_id, normalized)
|
||||
%{name: normalized, title: normalized}
|
||||
|
||||
@@ -6,19 +6,26 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.PageCategory do
|
||||
alias BDS.{Metadata, Repo}
|
||||
alias BDS.Posts.Post
|
||||
|
||||
@spec page_posts(term()) :: term()
|
||||
def page_posts(nil), do: []
|
||||
|
||||
def page_posts(project_id) do
|
||||
Repo.all(from post in Post, where: post.project_id == ^project_id, order_by: [asc: post.title, asc: post.slug])
|
||||
Repo.all(
|
||||
from post in Post,
|
||||
where: post.project_id == ^project_id,
|
||||
order_by: [asc: post.title, asc: post.slug]
|
||||
)
|
||||
|> Enum.filter(&("page" in (&1.categories || [])))
|
||||
end
|
||||
|
||||
@spec page_post(term(), term()) :: term()
|
||||
def page_post(nil, _post_id), do: nil
|
||||
|
||||
def page_post(project_id, post_id) do
|
||||
Enum.find(page_posts(project_id), &(&1.id == post_id))
|
||||
end
|
||||
|
||||
@spec filter_page_posts(term(), term()) :: term()
|
||||
def filter_page_posts(posts, query) do
|
||||
normalized = query |> to_string() |> String.trim() |> String.downcase()
|
||||
|
||||
@@ -29,6 +36,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.PageCategory do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec category_options(term()) :: term()
|
||||
def category_options(nil), do: []
|
||||
|
||||
def category_options(project_id) do
|
||||
@@ -40,6 +48,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.PageCategory do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec filter_categories(term(), term()) :: term()
|
||||
def filter_categories(categories, query) do
|
||||
normalized = query |> to_string() |> String.trim() |> String.downcase()
|
||||
|
||||
@@ -50,7 +59,9 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.PageCategory do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec blank_to_nil(term()) :: term()
|
||||
def blank_to_nil(nil), do: nil
|
||||
|
||||
def blank_to_nil(value) do
|
||||
trimmed = String.trim(to_string(value))
|
||||
if trimmed == "", do: nil, else: trimmed
|
||||
|
||||
@@ -7,6 +7,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
|
||||
alias BDS.Menu
|
||||
alias BDS.Desktop.ShellLive.MenuEditor.{PageCategory, TreeOps, TreePredicates}
|
||||
|
||||
@spec ensure_state(term()) :: term()
|
||||
def ensure_state(assigns) do
|
||||
project_id = assigns.projects.active_project_id
|
||||
|
||||
@@ -16,11 +17,13 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_state(term(), term()) :: term()
|
||||
def update_state(socket, updater) do
|
||||
state = ensure_state(socket.assigns)
|
||||
assign(socket, :menu_editor_state, updater.(state))
|
||||
end
|
||||
|
||||
@spec build(term(), term()) :: term()
|
||||
def build(_assigns, state) do
|
||||
categories = PageCategory.category_options(state.project_id)
|
||||
draft = state.draft
|
||||
@@ -35,7 +38,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
|
||||
draft_query: draft_query,
|
||||
filtered_pages:
|
||||
if(match?(%{type: :page}, draft),
|
||||
do: PageCategory.filter_page_posts(PageCategory.page_posts(state.project_id), draft_query),
|
||||
do:
|
||||
PageCategory.filter_page_posts(PageCategory.page_posts(state.project_id), draft_query),
|
||||
else: []
|
||||
),
|
||||
filtered_categories:
|
||||
@@ -53,6 +57,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
|
||||
}
|
||||
end
|
||||
|
||||
@spec save(term(), term(), term()) :: term()
|
||||
def save(socket, reload, append_output) do
|
||||
state = socket.assigns.menu_editor_state
|
||||
|
||||
@@ -60,12 +65,22 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
|
||||
Menu.update_menu(state.project_id, Enum.map(state.items, &TreeOps.persisted_item/1))
|
||||
|
||||
socket
|
||||
|> append_output.(translated("menuEditor.tabTitle"), translated("menuEditor.saved"), nil, "info")
|
||||
|> append_output.(
|
||||
translated("menuEditor.tabTitle"),
|
||||
translated("menuEditor.saved"),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp load_state(nil) do
|
||||
%{project_id: nil, items: [TreeOps.home_item()], selected_id: TreeOps.home_item_id(), draft: nil}
|
||||
%{
|
||||
project_id: nil,
|
||||
items: [TreeOps.home_item()],
|
||||
selected_id: TreeOps.home_item_id(),
|
||||
draft: nil
|
||||
}
|
||||
end
|
||||
|
||||
defp load_state(project_id) do
|
||||
|
||||
@@ -3,12 +3,15 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
|
||||
@home_item_id "menu-home"
|
||||
|
||||
@spec home_item_id() :: term()
|
||||
def home_item_id, do: @home_item_id
|
||||
|
||||
@spec home_item() :: term()
|
||||
def home_item do
|
||||
%{item_id: @home_item_id, kind: :home, label: "Home", slug: nil, children: [], is_home: true}
|
||||
end
|
||||
|
||||
@spec ui_item(term()) :: term()
|
||||
def ui_item(%{kind: :home}), do: home_item()
|
||||
|
||||
def ui_item(item) do
|
||||
@@ -24,25 +27,37 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
}
|
||||
end
|
||||
|
||||
@spec persisted_item(term()) :: term()
|
||||
def persisted_item(%{kind: :home}), do: %{kind: :home, label: "Home", slug: nil}
|
||||
|
||||
def persisted_item(%{kind: :submenu} = item) do
|
||||
%{kind: :submenu, label: item.label, slug: nil, children: Enum.map(item.children || [], &persisted_item/1)}
|
||||
%{
|
||||
kind: :submenu,
|
||||
label: item.label,
|
||||
slug: nil,
|
||||
children: Enum.map(item.children || [], &persisted_item/1)
|
||||
}
|
||||
end
|
||||
|
||||
def persisted_item(item) do
|
||||
%{kind: item.kind, label: item.label, slug: item.slug}
|
||||
end
|
||||
|
||||
@spec first_item_id(term()) :: term()
|
||||
def first_item_id([item | _rest]), do: item.item_id
|
||||
def first_item_id([]), do: nil
|
||||
|
||||
@spec insert_target(term(), term()) :: term()
|
||||
def insert_target(items, nil), do: {[], length(items)}
|
||||
|
||||
def insert_target(items, selected_id) do
|
||||
case find_path(items, selected_id) do
|
||||
nil -> {[], length(items)}
|
||||
[] -> {[], length(items)}
|
||||
nil ->
|
||||
{[], length(items)}
|
||||
|
||||
[] ->
|
||||
{[], length(items)}
|
||||
|
||||
path ->
|
||||
case item_at_path(items, path) do
|
||||
%{kind: :submenu} -> {path, 0}
|
||||
@@ -51,9 +66,12 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end
|
||||
end
|
||||
|
||||
@spec path_prefix?(term(), term()) :: term()
|
||||
def path_prefix?(prefix, path) when length(prefix) > length(path), do: false
|
||||
@spec path_prefix?(term(), term()) :: term()
|
||||
def path_prefix?(prefix, path), do: Enum.take(path, length(prefix)) == prefix
|
||||
|
||||
@spec find_path(term(), term(), term()) :: term()
|
||||
def find_path(items, item_id, path \\ []) do
|
||||
Enum.find_value(Enum.with_index(items), fn {item, index} ->
|
||||
next_path = path ++ [index]
|
||||
@@ -71,6 +89,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec item_at_path(term(), term()) :: term()
|
||||
def item_at_path(_items, []), do: nil
|
||||
|
||||
def item_at_path(items, [index]) do
|
||||
@@ -84,6 +103,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end
|
||||
end
|
||||
|
||||
@spec items_at_path(term(), term()) :: term()
|
||||
def items_at_path(items, []), do: items
|
||||
|
||||
def items_at_path(items, [index | rest]) do
|
||||
@@ -93,6 +113,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end
|
||||
end
|
||||
|
||||
@spec replace_items_at_path(term(), term(), term()) :: term()
|
||||
def replace_items_at_path(_items, [], replacement), do: replacement
|
||||
|
||||
def replace_items_at_path(items, [index | rest], replacement) do
|
||||
@@ -101,6 +122,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec update_item(term(), term(), term()) :: term()
|
||||
def update_item(items, item_id, updater) do
|
||||
Enum.map(items, fn item ->
|
||||
cond do
|
||||
@@ -111,6 +133,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec insert_item(term(), term(), term(), term()) :: term()
|
||||
def insert_item(items, [], index, item) do
|
||||
List.insert_at(items, index, item)
|
||||
end
|
||||
@@ -121,10 +144,12 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec remove_item(term(), term()) :: term()
|
||||
def remove_item(items, item_id) do
|
||||
remove_item_with_value(items, item_id) |> elem(0)
|
||||
end
|
||||
|
||||
@spec remove_item_with_value(term(), term()) :: term()
|
||||
def remove_item_with_value(items, item_id) do
|
||||
Enum.reduce_while(Enum.with_index(items), {items, nil}, fn {item, index}, _acc ->
|
||||
cond do
|
||||
@@ -135,7 +160,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
{next_children, removed_item} = remove_item_with_value(item.children, item_id)
|
||||
|
||||
if removed_item do
|
||||
{:halt, {List.replace_at(items, index, %{item | children: next_children}), removed_item}}
|
||||
{:halt,
|
||||
{List.replace_at(items, index, %{item | children: next_children}), removed_item}}
|
||||
else
|
||||
{:cont, {items, nil}}
|
||||
end
|
||||
@@ -146,16 +172,23 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec append_child(term(), term(), term()) :: term()
|
||||
def append_child(items, parent_item_id, child) do
|
||||
update_item(items, parent_item_id, fn item ->
|
||||
%{item | children: (item.children || []) ++ [child]}
|
||||
end)
|
||||
end
|
||||
|
||||
def move_selected(%{selected_id: selected_id} = state, direction) when direction in [:up, :down] do
|
||||
@spec move_selected(term(), term()) :: term()
|
||||
def move_selected(%{selected_id: selected_id} = state, direction)
|
||||
when direction in [:up, :down] do
|
||||
case find_path(state.items, selected_id) do
|
||||
nil -> state
|
||||
[] -> state
|
||||
nil ->
|
||||
state
|
||||
|
||||
[] ->
|
||||
state
|
||||
|
||||
path ->
|
||||
parent_path = Enum.drop(path, -1)
|
||||
index = List.last(path)
|
||||
@@ -175,10 +208,15 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end
|
||||
end
|
||||
|
||||
@spec indent_selected(term()) :: term()
|
||||
def indent_selected(%{selected_id: selected_id} = state) do
|
||||
case find_path(state.items, selected_id) do
|
||||
nil -> state
|
||||
[] -> state
|
||||
nil ->
|
||||
state
|
||||
|
||||
[] ->
|
||||
state
|
||||
|
||||
path ->
|
||||
parent_path = Enum.drop(path, -1)
|
||||
index = List.last(path)
|
||||
@@ -193,7 +231,9 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
case item_at_path(state.items, previous_sibling_path) do
|
||||
%{kind: :submenu, item_id: sibling_id} ->
|
||||
case remove_item_with_value(state.items, selected_id) do
|
||||
{_next_items, nil} -> state
|
||||
{_next_items, nil} ->
|
||||
state
|
||||
|
||||
{next_items, removed_item} ->
|
||||
%{
|
||||
state
|
||||
@@ -208,18 +248,27 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end
|
||||
end
|
||||
|
||||
@spec unindent_selected(term()) :: term()
|
||||
def unindent_selected(%{selected_id: selected_id} = state) do
|
||||
case find_path(state.items, selected_id) do
|
||||
nil -> state
|
||||
[] -> state
|
||||
[_root_index] -> state
|
||||
nil ->
|
||||
state
|
||||
|
||||
[] ->
|
||||
state
|
||||
|
||||
[_root_index] ->
|
||||
state
|
||||
|
||||
path ->
|
||||
parent_path = Enum.drop(path, -1)
|
||||
parent_index = List.last(parent_path)
|
||||
grand_parent_path = Enum.drop(parent_path, -1)
|
||||
|
||||
case remove_item_with_value(state.items, selected_id) do
|
||||
{_next_items, nil} -> state
|
||||
{_next_items, nil} ->
|
||||
state
|
||||
|
||||
{next_items, removed_item} ->
|
||||
%{
|
||||
state
|
||||
@@ -229,6 +278,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_selected(term()) :: term()
|
||||
def delete_selected(%{selected_id: @home_item_id} = state), do: state
|
||||
|
||||
def delete_selected(%{selected_id: selected_id} = state) do
|
||||
@@ -241,9 +291,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
state
|
||||
end
|
||||
|
||||
def drop_selected(state, drag_item_id, target_item_id, _position) when drag_item_id == target_item_id,
|
||||
do: state
|
||||
def drop_selected(state, drag_item_id, target_item_id, _position)
|
||||
when drag_item_id == target_item_id,
|
||||
do: state
|
||||
|
||||
@spec drop_selected(term(), term(), term(), term()) :: term()
|
||||
def drop_selected(state, drag_item_id, target_item_id, position) do
|
||||
drag_path = find_path(state.items, drag_item_id)
|
||||
target_path = find_path(state.items, target_item_id)
|
||||
@@ -275,7 +327,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
defp insert_dropped_item(state, next_items, dragged_item, target_path, "inside") do
|
||||
case item_at_path(next_items, target_path) do
|
||||
%{kind: :submenu} ->
|
||||
%{state | items: insert_item(next_items, target_path, 0, dragged_item), selected_id: dragged_item.item_id}
|
||||
%{
|
||||
state
|
||||
| items: insert_item(next_items, target_path, 0, dragged_item),
|
||||
selected_id: dragged_item.item_id
|
||||
}
|
||||
|
||||
_other ->
|
||||
state
|
||||
@@ -285,12 +341,22 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreeOps do
|
||||
defp insert_dropped_item(state, next_items, dragged_item, target_path, "before") do
|
||||
parent_path = Enum.drop(target_path, -1)
|
||||
index = List.last(target_path)
|
||||
%{state | items: insert_item(next_items, parent_path, index, dragged_item), selected_id: dragged_item.item_id}
|
||||
|
||||
%{
|
||||
state
|
||||
| items: insert_item(next_items, parent_path, index, dragged_item),
|
||||
selected_id: dragged_item.item_id
|
||||
}
|
||||
end
|
||||
|
||||
defp insert_dropped_item(state, next_items, dragged_item, target_path, _position) do
|
||||
parent_path = Enum.drop(target_path, -1)
|
||||
index = List.last(target_path) + 1
|
||||
%{state | items: insert_item(next_items, parent_path, index, dragged_item), selected_id: dragged_item.item_id}
|
||||
|
||||
%{
|
||||
state
|
||||
| items: insert_item(next_items, parent_path, index, dragged_item),
|
||||
selected_id: dragged_item.item_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreePredicates do
|
||||
|
||||
alias BDS.Desktop.ShellLive.MenuEditor.TreeOps
|
||||
|
||||
@spec can_move_up?(term(), term()) :: term()
|
||||
def can_move_up?(items, selected_id) do
|
||||
case TreeOps.find_path(items, selected_id) do
|
||||
[_parent, index] -> index > 0
|
||||
@@ -12,9 +13,12 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreePredicates do
|
||||
end
|
||||
end
|
||||
|
||||
@spec can_move_down?(term(), term()) :: term()
|
||||
def can_move_down?(items, selected_id) do
|
||||
case TreeOps.find_path(items, selected_id) do
|
||||
nil -> false
|
||||
nil ->
|
||||
false
|
||||
|
||||
path ->
|
||||
parent_path = Enum.drop(path, -1)
|
||||
index = List.last(path)
|
||||
@@ -22,10 +26,15 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreePredicates do
|
||||
end
|
||||
end
|
||||
|
||||
@spec can_indent?(term(), term()) :: term()
|
||||
def can_indent?(items, selected_id) do
|
||||
case TreeOps.find_path(items, selected_id) do
|
||||
nil -> false
|
||||
[] -> false
|
||||
nil ->
|
||||
false
|
||||
|
||||
[] ->
|
||||
false
|
||||
|
||||
[_index] = path ->
|
||||
index = List.last(path)
|
||||
index > 0 and match?(%{kind: :submenu}, TreeOps.item_at_path(items, [index - 1]))
|
||||
@@ -34,10 +43,14 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreePredicates do
|
||||
index = List.last(path)
|
||||
|
||||
index > 0 and
|
||||
match?(%{kind: :submenu}, TreeOps.item_at_path(items, Enum.drop(path, -1) ++ [index - 1]))
|
||||
match?(
|
||||
%{kind: :submenu},
|
||||
TreeOps.item_at_path(items, Enum.drop(path, -1) ++ [index - 1])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@spec can_unindent?(term(), term()) :: term()
|
||||
def can_unindent?(items, selected_id) do
|
||||
case TreeOps.find_path(items, selected_id) do
|
||||
[_index] -> false
|
||||
@@ -46,9 +59,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.TreePredicates do
|
||||
end
|
||||
end
|
||||
|
||||
@spec can_delete?(term()) :: term()
|
||||
def can_delete?(selected_id),
|
||||
do: is_binary(selected_id) and selected_id != TreeOps.home_item_id()
|
||||
|
||||
@spec draft_item?(term(), term()) :: term()
|
||||
def draft_item?(menu_editor, item_id) do
|
||||
match?(%{item_id: ^item_id}, menu_editor.draft)
|
||||
end
|
||||
|
||||
@@ -20,10 +20,12 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
:git_diff
|
||||
]
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
assign(socket, :misc_editor, build(socket.assigns))
|
||||
end
|
||||
|
||||
@spec rerun(term()) :: term()
|
||||
def rerun(socket) do
|
||||
case meta(socket.assigns) do
|
||||
%{action: action} when is_binary(action) ->
|
||||
@@ -37,6 +39,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec apply_site_validation(term(), term()) :: term()
|
||||
def apply_site_validation(socket, append_output) do
|
||||
meta = meta(socket.assigns)
|
||||
payload = Map.get(meta, :payload, %{})
|
||||
@@ -68,6 +71,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
append_output.(socket, translated("Site Validation"), inspect(error), nil, "error")}
|
||||
end
|
||||
|
||||
@spec toggle_duplicate(term(), term(), term()) :: term()
|
||||
def toggle_duplicate(socket, pair_id, reload) do
|
||||
selected_by_tab = Map.get(socket.assigns, :misc_editor_selected_pairs, %{})
|
||||
current = Map.get(selected_by_tab, socket.assigns.current_tab.id, MapSet.new())
|
||||
@@ -87,6 +91,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec dismiss_duplicate(term(), term(), term(), term(), term()) :: term()
|
||||
def dismiss_duplicate(socket, post_id_a, post_id_b, reload, append_output) do
|
||||
case Embeddings.dismiss_duplicate_pair(post_id_a, post_id_b) do
|
||||
{:ok, _saved_pair} ->
|
||||
@@ -109,6 +114,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec dismiss_selected(term(), term(), term()) :: term()
|
||||
def dismiss_selected(socket, reload, append_output) do
|
||||
tab_id = socket.assigns.current_tab.id
|
||||
|
||||
@@ -141,6 +147,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec fix_translation_validation(term(), term()) :: term()
|
||||
def fix_translation_validation(socket, append_output) do
|
||||
report =
|
||||
socket.assigns
|
||||
@@ -166,6 +173,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
append_output.(socket, translated("Translation Validation"), inspect(error), nil, "error")}
|
||||
end
|
||||
|
||||
@spec select_git_diff_file(term(), term()) :: term()
|
||||
def select_git_diff_file(socket, file_path) do
|
||||
assign(
|
||||
socket,
|
||||
@@ -178,6 +186,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
)
|
||||
end
|
||||
|
||||
@spec metadata_diff_repair_request(term(), term(), term()) :: term()
|
||||
def metadata_diff_repair_request(socket, field, direction) do
|
||||
meta = meta(socket.assigns)
|
||||
payload = Map.get(meta, :payload, %{})
|
||||
@@ -209,6 +218,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec metadata_diff_orphan_import_request(term()) :: term()
|
||||
def metadata_diff_orphan_import_request(socket) do
|
||||
meta = meta(socket.assigns)
|
||||
payload = Map.get(meta, :payload, %{})
|
||||
@@ -232,6 +242,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec build(term()) :: term()
|
||||
def build(%{current_tab: %{type: type}} = assigns) when type in @misc_routes do
|
||||
meta = meta(assigns)
|
||||
payload = Map.get(meta, :payload, %{})
|
||||
@@ -245,11 +256,14 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec build(term()) :: term()
|
||||
def build(_assigns), do: nil
|
||||
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
@spec misc_class(term()) :: term()
|
||||
def misc_class(:site_validation), do: "site-validation-view"
|
||||
def misc_class(:metadata_diff), do: "metadata-diff-view"
|
||||
def misc_class(:translation_validation), do: "translation-validation-view"
|
||||
@@ -257,10 +271,13 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
def misc_class(:git_diff), do: "git-diff-view"
|
||||
|
||||
def summary_items(%{summary: summary}) when is_map(summary), do: Enum.to_list(summary)
|
||||
@spec summary_items(term()) :: term()
|
||||
def summary_items(_misc), do: []
|
||||
|
||||
@spec duplicate_checked?(term(), term()) :: term()
|
||||
def duplicate_checked?(misc, pair_id), do: MapSet.member?(misc.selected_pairs, pair_id)
|
||||
|
||||
@spec pair_id_from_pair(term()) :: term()
|
||||
def pair_id_from_pair(pair), do: pair_identity(pair)
|
||||
|
||||
defp build_site_validation(meta, payload) do
|
||||
@@ -410,6 +427,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
}
|
||||
end
|
||||
|
||||
@spec translation_issue_label(term()) :: term()
|
||||
def translation_issue_label(issue) do
|
||||
case issue_value(issue, :issue) do
|
||||
"same-language-as-canonical" ->
|
||||
@@ -426,6 +444,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec translation_issue_languages(term()) :: term()
|
||||
def translation_issue_languages(issue) do
|
||||
canonical_language = issue_value(issue, :canonical_language)
|
||||
translation_language = issue_value(issue, :translation_language)
|
||||
@@ -440,8 +459,10 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec translation_issue_value(term(), term()) :: term()
|
||||
def translation_issue_value(issue, key), do: issue_value(issue, key)
|
||||
|
||||
@spec git_diff_language(term()) :: term()
|
||||
def git_diff_language(nil), do: "plaintext"
|
||||
|
||||
def git_diff_language(file_path) do
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
alias BDS.Posts.{Post, PostMedia, Translation}
|
||||
alias BDS.Tags.Tag
|
||||
|
||||
embed_templates "overlay_html/*"
|
||||
embed_templates("overlay_html/*")
|
||||
|
||||
def context(assigns, tab_title, tab_subtitle) do
|
||||
project_id = assigns.projects.active_project_id
|
||||
@@ -23,7 +23,12 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
media = media(project_id)
|
||||
|
||||
%{
|
||||
current_tab: %{type: current_tab.type, id: current_tab.id, title: tab_title, subtitle: tab_subtitle},
|
||||
current_tab: %{
|
||||
type: current_tab.type,
|
||||
id: current_tab.id,
|
||||
title: tab_title,
|
||||
subtitle: tab_subtitle
|
||||
},
|
||||
current_post_language: source_language(current_tab, metadata),
|
||||
current_media_language: source_language(current_tab, metadata),
|
||||
posts: posts,
|
||||
@@ -59,7 +64,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
|
||||
def markdown_link(text, url), do: "[#{text}](#{url})"
|
||||
|
||||
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
|
||||
|
||||
@@ -77,7 +83,15 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
from post in Post,
|
||||
where: post.project_id == ^project_id,
|
||||
order_by: [desc: post.updated_at, desc: post.created_at],
|
||||
select: %{id: post.id, title: post.title, slug: post.slug, status: post.status, published_at: post.published_at, updated_at: post.updated_at, language: post.language}
|
||||
select: %{
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
slug: post.slug,
|
||||
status: post.status,
|
||||
published_at: post.published_at,
|
||||
updated_at: post.updated_at,
|
||||
language: post.language
|
||||
}
|
||||
)
|
||||
|> Enum.map(fn post ->
|
||||
%{
|
||||
@@ -96,7 +110,14 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
from media in MediaRecord,
|
||||
where: media.project_id == ^project_id,
|
||||
order_by: [desc: media.updated_at, desc: media.created_at],
|
||||
select: %{id: media.id, title: media.title, original_name: media.original_name, mime_type: media.mime_type, alt: media.alt, caption: media.caption}
|
||||
select: %{
|
||||
id: media.id,
|
||||
title: media.title,
|
||||
original_name: media.original_name,
|
||||
mime_type: media.mime_type,
|
||||
alt: media.alt,
|
||||
caption: media.caption
|
||||
}
|
||||
)
|
||||
|> Enum.map(fn media ->
|
||||
%{
|
||||
@@ -149,7 +170,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
defp existing_translations(_tab), do: %{}
|
||||
|
||||
defp blog_languages(metadata) do
|
||||
([metadata.main_language || "en"] ++ (metadata.blog_languages || []) ++ Enum.map(I18n.supported_languages(), & &1.code))
|
||||
([metadata.main_language || "en"] ++
|
||||
(metadata.blog_languages || []) ++ Enum.map(I18n.supported_languages(), & &1.code))
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.uniq()
|
||||
end
|
||||
@@ -193,9 +215,27 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
case Posts.get_post(post_id) do
|
||||
%Post{} = post ->
|
||||
[
|
||||
%{key: "title", label: ShellData.translate("Title", %{}, page_language), current_value: post.title || title, suggested_value: refine_title(post.title || title), locked: false},
|
||||
%{key: "excerpt", label: ShellData.translate("Excerpt", %{}, page_language), current_value: post.excerpt || subtitle, suggested_value: refine_excerpt(post.title || title, post.excerpt || subtitle), locked: false},
|
||||
%{key: "slug", label: ShellData.translate("Slug", %{}, page_language), current_value: post.slug || slugify(post.title || title), suggested_value: refine_slug(post.slug || slugify(post.title || title)), locked: post.status == :published}
|
||||
%{
|
||||
key: "title",
|
||||
label: ShellData.translate("Title", %{}, page_language),
|
||||
current_value: post.title || title,
|
||||
suggested_value: refine_title(post.title || title),
|
||||
locked: false
|
||||
},
|
||||
%{
|
||||
key: "excerpt",
|
||||
label: ShellData.translate("Excerpt", %{}, page_language),
|
||||
current_value: post.excerpt || subtitle,
|
||||
suggested_value: refine_excerpt(post.title || title, post.excerpt || subtitle),
|
||||
locked: false
|
||||
},
|
||||
%{
|
||||
key: "slug",
|
||||
label: ShellData.translate("Slug", %{}, page_language),
|
||||
current_value: post.slug || slugify(post.title || title),
|
||||
suggested_value: refine_slug(post.slug || slugify(post.title || title)),
|
||||
locked: post.status == :published
|
||||
}
|
||||
]
|
||||
|
||||
_other ->
|
||||
@@ -209,9 +249,27 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
case Media.get_media(media_id) do
|
||||
%MediaRecord{} = media ->
|
||||
[
|
||||
%{key: "title", label: ShellData.translate("Title", %{}, page_language), current_value: media.title || title, suggested_value: refine_title(media.title || title), locked: false},
|
||||
%{key: "alt", label: ShellData.translate("Alt Text", %{}, page_language), current_value: media.alt || "", suggested_value: media.alt || title, locked: false},
|
||||
%{key: "caption", label: ShellData.translate("Caption", %{}, page_language), current_value: media.caption || "", suggested_value: refine_excerpt(title, media.caption || title), locked: false}
|
||||
%{
|
||||
key: "title",
|
||||
label: ShellData.translate("Title", %{}, page_language),
|
||||
current_value: media.title || title,
|
||||
suggested_value: refine_title(media.title || title),
|
||||
locked: false
|
||||
},
|
||||
%{
|
||||
key: "alt",
|
||||
label: ShellData.translate("Alt Text", %{}, page_language),
|
||||
current_value: media.alt || "",
|
||||
suggested_value: media.alt || title,
|
||||
locked: false
|
||||
},
|
||||
%{
|
||||
key: "caption",
|
||||
label: ShellData.translate("Caption", %{}, page_language),
|
||||
current_value: media.caption || "",
|
||||
suggested_value: refine_excerpt(title, media.caption || title),
|
||||
locked: false
|
||||
}
|
||||
]
|
||||
|
||||
_other ->
|
||||
@@ -248,7 +306,13 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
reference_list: reference_list
|
||||
}
|
||||
rescue
|
||||
_error -> %{title: ShellData.translate("Delete Media", %{}, page_language), entity_name: media_id, entity_type: "media", reference_list: []}
|
||||
_error ->
|
||||
%{
|
||||
title: ShellData.translate("Delete Media", %{}, page_language),
|
||||
entity_name: media_id,
|
||||
entity_type: "media",
|
||||
reference_list: []
|
||||
}
|
||||
end
|
||||
|
||||
defp delete_details(%{type: :tags}, page_language) do
|
||||
@@ -263,16 +327,33 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
reference_list: []
|
||||
}
|
||||
rescue
|
||||
_error -> %{title: ShellData.translate("Delete Tag", %{}, page_language), entity_name: "tag", entity_type: "tag", reference_list: []}
|
||||
_error ->
|
||||
%{
|
||||
title: ShellData.translate("Delete Tag", %{}, page_language),
|
||||
entity_name: "tag",
|
||||
entity_type: "tag",
|
||||
reference_list: []
|
||||
}
|
||||
end
|
||||
|
||||
defp delete_details(_tab, page_language) do
|
||||
%{title: ShellData.translate("Delete", %{}, page_language), entity_name: "", entity_type: "item", reference_list: []}
|
||||
%{
|
||||
title: ShellData.translate("Delete", %{}, page_language),
|
||||
entity_name: "",
|
||||
entity_type: "item",
|
||||
reference_list: []
|
||||
}
|
||||
end
|
||||
|
||||
defp merge_details(project_id, page_language) do
|
||||
tags =
|
||||
Repo.all(from tag in Tag, where: tag.project_id == ^project_id, order_by: [asc: tag.name], limit: 3, select: tag.name)
|
||||
Repo.all(
|
||||
from tag in Tag,
|
||||
where: tag.project_id == ^project_id,
|
||||
order_by: [asc: tag.name],
|
||||
limit: 3,
|
||||
select: tag.name
|
||||
)
|
||||
|
||||
target = List.first(tags) || "tag"
|
||||
|
||||
@@ -283,7 +364,13 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
message: ShellData.translate("Cannot be undone.", %{}, page_language)
|
||||
}
|
||||
rescue
|
||||
_error -> %{target: "tag", count: 1, title: ShellData.translate("Merge Tags", %{}, page_language), message: ShellData.translate("Cannot be undone.", %{}, page_language)}
|
||||
_error ->
|
||||
%{
|
||||
target: "tag",
|
||||
count: 1,
|
||||
title: ShellData.translate("Merge Tags", %{}, page_language),
|
||||
message: ShellData.translate("Cannot be undone.", %{}, page_language)
|
||||
}
|
||||
end
|
||||
|
||||
defp canonical_post_url(post) do
|
||||
@@ -302,7 +389,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
||||
if base == "", do: "#{title} overview", else: base <> "."
|
||||
end
|
||||
|
||||
defp refine_slug(slug), do: slug |> to_string() |> String.trim_trailing("-") |> Kernel.<>("-updated")
|
||||
defp refine_slug(slug),
|
||||
do: slug |> to_string() |> String.trim_trailing("-") |> Kernel.<>("-updated")
|
||||
|
||||
defp slugify(value) do
|
||||
value
|
||||
|
||||
@@ -210,8 +210,15 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
defp related_posts(links, key) do
|
||||
Enum.map(links, fn link ->
|
||||
case Posts.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
|
||||
%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)
|
||||
@@ -232,15 +239,22 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
|
||||
defp git_history_target(%{type: :post, id: post_id}) do
|
||||
case Posts.get_post(post_id) do
|
||||
%Post{project_id: project_id, file_path: file_path} when file_path not in [nil, ""] -> {project_id, file_path}
|
||||
_other -> nil
|
||||
%Post{project_id: project_id, file_path: file_path} when file_path not in [nil, ""] ->
|
||||
{project_id, file_path}
|
||||
|
||||
_other ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp git_history_target(%{type: :media, id: media_id}) do
|
||||
case Media.get_media(media_id) do
|
||||
%MediaRecord{project_id: project_id, file_path: file_path} when file_path not in [nil, ""] -> {project_id, file_path}
|
||||
_other -> nil
|
||||
%MediaRecord{project_id: project_id, file_path: file_path}
|
||||
when file_path not in [nil, ""] ->
|
||||
{project_id, file_path}
|
||||
|
||||
_other ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -287,5 +301,6 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
|
||||
defp present?(value), do: value not in [nil, ""]
|
||||
|
||||
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
defp translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
end
|
||||
|
||||
@@ -74,13 +74,21 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
|
||||
defdelegate tag_chip_style(color), to: ListValues
|
||||
|
||||
embed_templates "post_editor_html/*"
|
||||
embed_templates("post_editor_html/*")
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
assigns = Map.put(socket.assigns, :project_metadata, project_metadata(socket.assigns.projects.active_project_id))
|
||||
assigns =
|
||||
Map.put(
|
||||
socket.assigns,
|
||||
:project_metadata,
|
||||
project_metadata(socket.assigns.projects.active_project_id)
|
||||
)
|
||||
|
||||
assign(socket, :post_editor, build(assigns))
|
||||
end
|
||||
|
||||
@spec update(term(), term(), term()) :: term()
|
||||
def update(socket, params, reload) do
|
||||
case socket.assigns.current_tab do
|
||||
%{type: :post, id: post_id} ->
|
||||
@@ -91,7 +99,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
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 =
|
||||
@@ -117,6 +128,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec persist_socket(term(), term(), term(), term(), term()) :: term()
|
||||
def persist_socket(socket, post_id, action, reload, append_output) do
|
||||
case Posts.get_post(post_id) do
|
||||
nil ->
|
||||
@@ -125,7 +137,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
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
|
||||
@@ -135,9 +150,30 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
|
||||
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, Posts.get_post!(post_id)), subtitle: Atom.to_string(record_status(record))}))
|
||||
|> 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, Posts.get_post!(post_id)),
|
||||
subtitle: Atom.to_string(record_status(record))
|
||||
})
|
||||
)
|
||||
|> reload.(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -148,6 +184,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec discard_socket(term(), term(), term(), term()) :: term()
|
||||
def discard_socket(socket, post_id, reload, append_output) do
|
||||
case Posts.get_post(post_id) do
|
||||
nil ->
|
||||
@@ -156,7 +193,9 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
active_language =
|
||||
Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
||||
|
||||
case discard(post, active_language, metadata) do
|
||||
{:ok, restored_post} ->
|
||||
@@ -164,9 +203,21 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
|
||||
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)}))
|
||||
|> 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} ->
|
||||
@@ -177,6 +228,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_socket(term(), term(), term(), term()) :: term()
|
||||
def delete_socket(socket, post_id, reload, append_output) do
|
||||
case Posts.delete_post(post_id) do
|
||||
{:ok, :deleted} ->
|
||||
@@ -185,13 +237,28 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
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_tag_queries, Map.delete(socket.assigns.post_editor_tag_queries, post_id))
|
||||
|> assign(:post_editor_category_queries, Map.delete(socket.assigns.post_editor_category_queries, post_id))
|
||||
|> assign(:post_editor_quick_actions_open, Map.delete(socket.assigns.post_editor_quick_actions_open, post_id))
|
||||
|> assign(
|
||||
:post_editor_active_languages,
|
||||
Map.delete(socket.assigns.post_editor_active_languages, post_id)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_tag_queries,
|
||||
Map.delete(socket.assigns.post_editor_tag_queries, post_id)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_category_queries,
|
||||
Map.delete(socket.assigns.post_editor_category_queries, post_id)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_quick_actions_open,
|
||||
Map.delete(socket.assigns.post_editor_quick_actions_open, 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))
|
||||
|> assign(
|
||||
:post_editor_save_states,
|
||||
Map.delete(socket.assigns.post_editor_save_states, post_id)
|
||||
)
|
||||
|> reload.(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -201,6 +268,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec set_mode(term(), term(), term(), term()) :: term()
|
||||
def set_mode(socket, post_id, mode, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
normalized_mode = normalize_mode(mode)
|
||||
@@ -216,38 +284,67 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(:post_editor_modes, Map.put(socket.assigns.post_editor_modes, post_id, normalized_mode))
|
||||
|> assign(
|
||||
:post_editor_modes,
|
||||
Map.put(socket.assigns.post_editor_modes, post_id, normalized_mode)
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec toggle_section(term(), term(), term(), term()) :: term()
|
||||
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)))
|
||||
|> 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
|
||||
|
||||
@spec select_language(term(), term(), term(), term()) :: term()
|
||||
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)))
|
||||
|> assign(
|
||||
:post_editor_active_languages,
|
||||
Map.put(
|
||||
socket.assigns.post_editor_active_languages,
|
||||
post_id,
|
||||
normalize_language(language, language)
|
||||
)
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec toggle_quick_actions(term(), term(), term()) :: term()
|
||||
def toggle_quick_actions(socket, post_id, reload) do
|
||||
workbench = socket.assigns.workbench
|
||||
|
||||
socket
|
||||
|> assign(:post_editor_quick_actions_open, Map.update(socket.assigns.post_editor_quick_actions_open, post_id, true, &(!&1)))
|
||||
|> assign(
|
||||
:post_editor_quick_actions_open,
|
||||
Map.update(socket.assigns.post_editor_quick_actions_open, post_id, true, &(!&1))
|
||||
)
|
||||
|> reload.(workbench)
|
||||
end
|
||||
|
||||
@spec detect_language(term(), term(), term(), term()) :: term()
|
||||
def detect_language(socket, post_id, reload, append_output) do
|
||||
if Map.get(socket.assigns, :offline_mode, true) do
|
||||
socket
|
||||
|> append_output.(translated("Detect Language"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info")
|
||||
|> append_output.(
|
||||
translated("Detect Language"),
|
||||
translated("Automatic AI actions stay gated by airplane mode."),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
else
|
||||
case Posts.get_post(post_id) do
|
||||
@@ -257,14 +354,24 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
active_language =
|
||||
Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
||||
|
||||
draft = current_draft(socket.assigns, post, metadata, active_language)
|
||||
text = Enum.join([Map.get(draft, "title", ""), Map.get(draft, "content", "")], "\n\n")
|
||||
|
||||
case AI.detect_language(text) do
|
||||
{:ok, %{language_code: language_code}} when is_binary(language_code) and language_code != "" ->
|
||||
{:ok, %{language_code: language_code}}
|
||||
when is_binary(language_code) and language_code != "" ->
|
||||
socket
|
||||
|> put_draft_field(post_id, post, active_language, "language", normalize_language(language_code, canonical_language))
|
||||
|> put_draft_field(
|
||||
post_id,
|
||||
post,
|
||||
active_language,
|
||||
"language",
|
||||
normalize_language(language_code, canonical_language)
|
||||
)
|
||||
|> reload_with_assigned_workbench(reload)
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -274,17 +381,28 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
|
||||
_other ->
|
||||
socket
|
||||
|> append_output.(translated("Detect Language"), translated("Language detection failed."), nil, "error")
|
||||
|> append_output.(
|
||||
translated("Detect Language"),
|
||||
translated("Language detection failed."),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec translate(term(), term(), term(), term(), term()) :: term()
|
||||
def translate(socket, post_id, language, reload, append_output) do
|
||||
if Map.get(socket.assigns, :offline_mode, true) do
|
||||
socket
|
||||
|> append_output.(translated("Translate"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info")
|
||||
|> append_output.(
|
||||
translated("Translate"),
|
||||
translated("Automatic AI actions stay gated by airplane mode."),
|
||||
nil,
|
||||
"info"
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
else
|
||||
normalized_language = normalize_language(language, "")
|
||||
@@ -298,9 +416,18 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
content: translation.content
|
||||
}) do
|
||||
socket
|
||||
|> assign(:post_editor_active_languages, Map.put(socket.assigns.post_editor_active_languages, post_id, normalized_language))
|
||||
|> assign(:post_editor_drafts, delete_nested_map(socket.assigns.post_editor_drafts, post_id, normalized_language))
|
||||
|> assign(:post_editor_quick_actions_open, Map.put(socket.assigns.post_editor_quick_actions_open, post_id, false))
|
||||
|> assign(
|
||||
:post_editor_active_languages,
|
||||
Map.put(socket.assigns.post_editor_active_languages, post_id, normalized_language)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_drafts,
|
||||
delete_nested_map(socket.assigns.post_editor_drafts, post_id, normalized_language)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_quick_actions_open,
|
||||
Map.put(socket.assigns.post_editor_quick_actions_open, post_id, false)
|
||||
)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
else
|
||||
{:error, reason} ->
|
||||
@@ -317,6 +444,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec apply_ai_suggestions(term(), term(), term(), term(), term()) :: term()
|
||||
def apply_ai_suggestions(socket, post_id, fields, reload, append_output) do
|
||||
case Posts.get_post(post_id) do
|
||||
nil ->
|
||||
@@ -340,12 +468,30 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
case Posts.update_post(post_id, attrs) do
|
||||
{:ok, updated_post} ->
|
||||
metadata = project_metadata(updated_post.project_id)
|
||||
active_language = Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language(updated_post, metadata))
|
||||
|
||||
active_language =
|
||||
Map.get(
|
||||
socket.assigns.post_editor_active_languages,
|
||||
post_id,
|
||||
canonical_language(updated_post, metadata)
|
||||
)
|
||||
|
||||
refreshed_form = persisted_form(updated_post, metadata, active_language)
|
||||
|
||||
socket
|
||||
|> assign(:post_editor_drafts, put_nested_map(socket.assigns.post_editor_drafts, post_id, active_language, refreshed_form))
|
||||
|> assign(:post_editor_save_states, Map.put(socket.assigns.post_editor_save_states, post_id, :dirty))
|
||||
|> assign(
|
||||
:post_editor_drafts,
|
||||
put_nested_map(
|
||||
socket.assigns.post_editor_drafts,
|
||||
post_id,
|
||||
active_language,
|
||||
refreshed_form
|
||||
)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_save_states,
|
||||
Map.put(socket.assigns.post_editor_save_states, post_id, :dirty)
|
||||
)
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
|
||||
@@ -358,6 +504,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec insert_content(term(), term(), term(), term()) :: term()
|
||||
def insert_content(socket, post_id, snippet, reload) do
|
||||
socket
|
||||
|> Phoenix.LiveView.push_event("post-editor-insert-content", %{id: post_id, content: snippet})
|
||||
@@ -365,6 +512,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec add_list_value(term(), term(), term(), term(), term()) :: term()
|
||||
def add_list_value(socket, post_id, kind, value, reload) when kind in [:tags, :categories] do
|
||||
case Posts.get_post(post_id) do
|
||||
nil ->
|
||||
@@ -373,7 +521,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
active_language =
|
||||
Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
||||
|
||||
draft = current_draft(socket.assigns, post, metadata, active_language)
|
||||
normalized = normalize_list_entry(value)
|
||||
|
||||
@@ -398,6 +549,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec remove_list_value(term(), term(), term(), term(), term()) :: term()
|
||||
def remove_list_value(socket, post_id, kind, value, reload) when kind in [:tags, :categories] do
|
||||
case Posts.get_post(post_id) do
|
||||
nil ->
|
||||
@@ -406,9 +558,18 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
active_language =
|
||||
Map.get(socket.assigns.post_editor_active_languages, post_id, canonical_language)
|
||||
|
||||
draft = current_draft(socket.assigns, post, metadata, active_language)
|
||||
updated = draft |> Map.get(field_key(kind), "") |> csv_to_list() |> Enum.reject(&(&1 == value)) |> Enum.join(", ")
|
||||
|
||||
updated =
|
||||
draft
|
||||
|> Map.get(field_key(kind), "")
|
||||
|> csv_to_list()
|
||||
|> Enum.reject(&(&1 == value))
|
||||
|> Enum.join(", ")
|
||||
|
||||
socket
|
||||
|> put_draft_field(post_id, post, active_language, field_key(kind), updated)
|
||||
@@ -416,6 +577,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec build(term()) :: term()
|
||||
def build(%{current_tab: %{type: :post, id: post_id}} = assigns) do
|
||||
case Posts.get_post(post_id) do
|
||||
nil ->
|
||||
@@ -424,7 +586,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
%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)
|
||||
|
||||
active_language =
|
||||
Map.get(assigns.post_editor_active_languages, post.id, canonical_language)
|
||||
|
||||
translations = translations(post.id)
|
||||
persisted = DraftManagement.persisted_form(post, metadata, active_language, translations)
|
||||
|
||||
@@ -453,13 +618,15 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
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),
|
||||
editing_canonical?:
|
||||
editing_canonical_language?(translations, active_language, canonical_language),
|
||||
can_publish?: post.status == :draft,
|
||||
can_delete?: post.status == :published,
|
||||
has_published_version?: has_published_version?(post),
|
||||
discard_label: discard_label(post),
|
||||
discard_title: discard_title(post),
|
||||
detect_language_enabled?: not blank?(Map.get(form, "title")) or not blank?(Map.get(form, "content")),
|
||||
detect_language_enabled?:
|
||||
not blank?(Map.get(form, "title")) or not blank?(Map.get(form, "content")),
|
||||
can_translate?: Enum.any?(languages(metadata), &(&1 != canonical_language)),
|
||||
languages: languages(metadata),
|
||||
form: form,
|
||||
@@ -469,16 +636,45 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
tag_values: tag_values(form),
|
||||
tag_chips: tag_chips(form, Tags.list_tags(post.project_id)),
|
||||
tag_query: query_value(assigns, :tags, post.id),
|
||||
tag_query_addable?: query_addable?(query_value(assigns, :tags, post.id), tag_values(form), Tags.list_tags(post.project_id), fn option -> option.name end),
|
||||
tag_query_addable?:
|
||||
query_addable?(
|
||||
query_value(assigns, :tags, post.id),
|
||||
tag_values(form),
|
||||
Tags.list_tags(post.project_id),
|
||||
fn option -> option.name end
|
||||
),
|
||||
category_values: category_values(form),
|
||||
category_query: query_value(assigns, :categories, post.id),
|
||||
category_options: metadata.categories || [],
|
||||
category_query_addable?: query_addable?(query_value(assigns, :categories, post.id), category_values(form), metadata.categories || [], & &1),
|
||||
tag_suggestions: tag_suggestions(form, Tags.list_tags(post.project_id), query_value(assigns, :tags, post.id)),
|
||||
category_suggestions: category_suggestions(form, metadata.categories || [], query_value(assigns, :categories, post.id)),
|
||||
category_query_addable?:
|
||||
query_addable?(
|
||||
query_value(assigns, :categories, post.id),
|
||||
category_values(form),
|
||||
metadata.categories || [],
|
||||
& &1
|
||||
),
|
||||
tag_suggestions:
|
||||
tag_suggestions(
|
||||
form,
|
||||
Tags.list_tags(post.project_id),
|
||||
query_value(assigns, :tags, post.id)
|
||||
),
|
||||
category_suggestions:
|
||||
category_suggestions(
|
||||
form,
|
||||
metadata.categories || [],
|
||||
query_value(assigns, :categories, post.id)
|
||||
),
|
||||
gallery_count: gallery_count(form),
|
||||
preview_url: preview_url(post, active_language, canonical_language, Map.get(assigns.post_editor_modes, post.id, :markdown)),
|
||||
translation_flags: translation_flags(post, canonical_language, active_language, translations),
|
||||
preview_url:
|
||||
preview_url(
|
||||
post,
|
||||
active_language,
|
||||
canonical_language,
|
||||
Map.get(assigns.post_editor_modes, post.id, :markdown)
|
||||
),
|
||||
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)
|
||||
@@ -488,17 +684,21 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
|
||||
def build(_assigns), do: nil
|
||||
|
||||
@spec post_status_label(term()) :: term()
|
||||
def post_status_label(status), do: ShellData.dashboard_status_label(status)
|
||||
|
||||
@spec post_editor_save_state_label(term()) :: term()
|
||||
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")
|
||||
|
||||
@spec post_editor_mode_label(term()) :: term()
|
||||
def post_editor_mode_label(:markdown), do: translated("Markdown")
|
||||
def post_editor_mode_label(:preview), do: translated("Preview")
|
||||
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
|
||||
@@ -8,11 +8,14 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
alias BDS.Desktop.ShellLive.PostEditor.PostMetadata
|
||||
alias BDS.UI.Workbench
|
||||
|
||||
@spec normalize_mode(term()) :: term()
|
||||
def normalize_mode(mode) when mode in [:markdown, :preview], do: mode
|
||||
@spec normalize_mode(term()) :: term()
|
||||
def normalize_mode("visual"), do: :markdown
|
||||
def normalize_mode("preview"), do: :preview
|
||||
def normalize_mode(_mode), do: :markdown
|
||||
|
||||
@spec normalize_language(term(), term()) :: term()
|
||||
def normalize_language(value, fallback) do
|
||||
case value |> to_string() |> String.trim() do
|
||||
"" -> fallback
|
||||
@@ -20,6 +23,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
end
|
||||
end
|
||||
|
||||
@spec normalize_params(term(), term(), term()) :: term()
|
||||
def normalize_params(params, current_language, next_language) do
|
||||
%{
|
||||
"title" => Map.get(params, "title", ""),
|
||||
@@ -28,12 +32,17 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
"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),
|
||||
"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
|
||||
|
||||
@spec current_draft(term(), term(), term(), term()) :: term()
|
||||
def current_draft(assigns, %Post{} = post, metadata, active_language) do
|
||||
persisted = persisted_form(post, metadata, active_language)
|
||||
|
||||
@@ -42,10 +51,12 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
|> Map.get(active_language, persisted)
|
||||
end
|
||||
|
||||
@spec persisted_form(term(), term(), term()) :: term()
|
||||
def persisted_form(%Post{} = post, metadata, active_language) do
|
||||
persisted_form(post, metadata, active_language, PostMetadata.translations(post.id))
|
||||
end
|
||||
|
||||
@spec persisted_form(term(), term(), term(), term()) :: term()
|
||||
def persisted_form(post, metadata, active_language, translations) do
|
||||
canonical_language = PostMetadata.canonical_language(post, metadata)
|
||||
translation = Map.get(translations, active_language)
|
||||
@@ -64,8 +75,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
}
|
||||
else
|
||||
%{
|
||||
"title" => translation && translation.title || "",
|
||||
"excerpt" => translation && translation.excerpt || "",
|
||||
"title" => (translation && translation.title) || "",
|
||||
"excerpt" => (translation && translation.excerpt) || "",
|
||||
"content" => if(translation, do: Posts.editor_body(translation), else: ""),
|
||||
"tags" => Enum.join(post.tags || [], ", "),
|
||||
"categories" => Enum.join(post.categories || [], ", "),
|
||||
@@ -77,22 +88,43 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_update_draft(term(), term(), term(), term(), term(), term(), term()) :: term()
|
||||
def maybe_update_draft(socket, post_id, post, current_language, next_language, draft, true) do
|
||||
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)}))
|
||||
|> 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)
|
||||
end
|
||||
|
||||
def maybe_update_draft(socket, post_id, _post, _current_language, next_language, _draft, false) do
|
||||
assign(socket, :post_editor_active_languages, Map.put(socket.assigns.post_editor_active_languages, post_id, next_language))
|
||||
assign(
|
||||
socket,
|
||||
:post_editor_active_languages,
|
||||
Map.put(socket.assigns.post_editor_active_languages, post_id, next_language)
|
||||
)
|
||||
end
|
||||
|
||||
@spec put_draft_field(term(), term(), term(), term(), term(), term()) :: term()
|
||||
def put_draft_field(socket, post_id, post, active_language, field, value) do
|
||||
metadata = PostMetadata.project_metadata(post.project_id)
|
||||
draft = Map.put(current_draft(socket.assigns, post, metadata, active_language), field, value)
|
||||
@@ -100,15 +132,28 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
|
||||
socket
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:post_editor_drafts, put_nested_map(socket.assigns.post_editor_drafts, post_id, active_language, draft))
|
||||
|> assign(:post_editor_save_states, Map.put(socket.assigns.post_editor_save_states, post_id, :dirty))
|
||||
|> assign(
|
||||
:post_editor_drafts,
|
||||
put_nested_map(socket.assigns.post_editor_drafts, post_id, active_language, draft)
|
||||
)
|
||||
|> assign(
|
||||
:post_editor_save_states,
|
||||
Map.put(socket.assigns.post_editor_save_states, post_id, :dirty)
|
||||
)
|
||||
end
|
||||
|
||||
@spec put_query_state(term(), term(), term(), term()) :: term()
|
||||
def put_query_state(socket, post_id, kind, value) do
|
||||
key = query_key(kind)
|
||||
assign(socket, key, Map.put(Map.get(socket.assigns, key, %{}), post_id, to_string(value || "")))
|
||||
|
||||
assign(
|
||||
socket,
|
||||
key,
|
||||
Map.put(Map.get(socket.assigns, key, %{}), post_id, to_string(value || ""))
|
||||
)
|
||||
end
|
||||
|
||||
@spec query_value(term(), term(), term()) :: term()
|
||||
def query_value(assigns, kind, post_id) do
|
||||
assigns
|
||||
|> Map.get(query_key(kind), %{})
|
||||
@@ -118,25 +163,33 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
defp query_key(:tags), do: :post_editor_tag_queries
|
||||
defp query_key(:categories), do: :post_editor_category_queries
|
||||
|
||||
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)
|
||||
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))
|
||||
assign(
|
||||
socket,
|
||||
:post_editor_drafts,
|
||||
delete_nested_map(socket.assigns.post_editor_drafts, post_id, current_language)
|
||||
)
|
||||
end
|
||||
|
||||
@spec toggled_sections(term(), term(), term()) :: term()
|
||||
def 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)
|
||||
|> Map.update!(section, &(not &1))
|
||||
end
|
||||
|
||||
@spec put_nested_map(term(), term(), term(), term()) :: term()
|
||||
def put_nested_map(map, key, nested_key, value) do
|
||||
Map.update(map, key, %{nested_key => value}, &Map.put(&1, nested_key, value))
|
||||
end
|
||||
|
||||
@spec delete_nested_map(term(), term(), term()) :: term()
|
||||
def delete_nested_map(map, key, nested_key) do
|
||||
case Map.get(map, key) do
|
||||
nil ->
|
||||
@@ -150,20 +203,26 @@ defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
||||
end
|
||||
end
|
||||
|
||||
def reload_with_assigned_workbench(socket, reload), do: reload.(socket, socket.assigns.workbench)
|
||||
@spec reload_with_assigned_workbench(term(), term()) :: term()
|
||||
def reload_with_assigned_workbench(socket, reload),
|
||||
do: reload.(socket, socket.assigns.workbench)
|
||||
|
||||
@spec save_state_for_action(term()) :: term()
|
||||
def save_state_for_action(:publish), do: :published
|
||||
def save_state_for_action(_action), do: :saved
|
||||
|
||||
@spec record_title(term(), term()) :: term()
|
||||
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
|
||||
|
||||
@spec record_status(term()) :: term()
|
||||
def record_status(%Translation{status: status}), do: status || :draft
|
||||
def record_status(%Post{status: status}), do: status || :draft
|
||||
|
||||
@spec editing_canonical_language?(term(), term(), term()) :: term()
|
||||
def editing_canonical_language?(translations, active_language, canonical_language) do
|
||||
active_language == canonical_language or not Map.has_key?(translations, active_language)
|
||||
end
|
||||
|
||||
@@ -3,17 +3,22 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
|
||||
alias BDS.{Metadata, Tags}
|
||||
|
||||
@spec field_key(term()) :: term()
|
||||
def field_key(:tags), do: "tags"
|
||||
def field_key(:categories), do: "categories"
|
||||
|
||||
@spec tag_values(term()) :: term()
|
||||
def tag_values(form), do: csv_to_list(Map.get(form, "tags", ""))
|
||||
@spec category_values(term()) :: term()
|
||||
def category_values(form), do: csv_to_list(Map.get(form, "categories", ""))
|
||||
|
||||
@spec tag_suggestions(term(), term(), term()) :: term()
|
||||
def tag_suggestions(form, options, query) do
|
||||
selected = MapSet.new(tag_values(form))
|
||||
filter_suggestions(options, query, fn option -> option.name end, selected)
|
||||
end
|
||||
|
||||
@spec tag_chips(term(), term()) :: term()
|
||||
def tag_chips(form, options) do
|
||||
option_map = Map.new(options, fn option -> {option.name, option} end)
|
||||
|
||||
@@ -23,6 +28,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec category_suggestions(term(), term(), term()) :: term()
|
||||
def category_suggestions(form, options, query) do
|
||||
selected = MapSet.new(category_values(form))
|
||||
filter_suggestions(options, query, & &1, selected)
|
||||
@@ -34,11 +40,14 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
options
|
||||
|> Enum.filter(fn option ->
|
||||
label = labeler.(option)
|
||||
not MapSet.member?(selected, label) and (query == "" or String.contains?(String.downcase(label), query))
|
||||
|
||||
not MapSet.member?(selected, label) and
|
||||
(query == "" or String.contains?(String.downcase(label), query))
|
||||
end)
|
||||
|> Enum.take(8)
|
||||
end
|
||||
|
||||
@spec query_addable?(term(), term(), term(), term()) :: term()
|
||||
def query_addable?(query, selected_values, options, labeler) do
|
||||
normalized = normalize_query(query)
|
||||
|
||||
@@ -54,6 +63,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
|> String.downcase()
|
||||
end
|
||||
|
||||
@spec normalize_list_entry(term()) :: term()
|
||||
def normalize_list_entry(value) do
|
||||
value
|
||||
|> to_string()
|
||||
@@ -61,6 +71,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
|> String.downcase()
|
||||
end
|
||||
|
||||
@spec ensure_list_value(term(), term(), term()) :: term()
|
||||
def ensure_list_value(project_id, :tags, value) do
|
||||
if Enum.any?(Tags.list_tags(project_id), &(String.downcase(&1.name) == value)) do
|
||||
:ok
|
||||
@@ -83,6 +94,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
_error -> :ok
|
||||
end
|
||||
|
||||
@spec csv_to_list(term()) :: term()
|
||||
def csv_to_list(value) do
|
||||
value
|
||||
|> to_string()
|
||||
@@ -91,6 +103,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
|> Enum.reject(&(&1 == ""))
|
||||
end
|
||||
|
||||
@spec tag_chip_style(term()) :: term()
|
||||
def tag_chip_style(nil), do: nil
|
||||
|
||||
def tag_chip_style(color) do
|
||||
@@ -121,5 +134,6 @@ defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
||||
|
||||
defp contrast_color(_color), do: "#ffffff"
|
||||
|
||||
@spec ai_overlay_fields(term()) :: term()
|
||||
def ai_overlay_fields(selected), do: Enum.filter(selected, & &1.accepted)
|
||||
end
|
||||
|
||||
@@ -6,11 +6,16 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
||||
alias BDS.Desktop.ShellData
|
||||
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, PostMetadata}
|
||||
|
||||
@spec persist(term(), term(), term(), term(), term()) :: term()
|
||||
def persist(%Post{} = post, draft, active_language, metadata, action) do
|
||||
canonical_language = PostMetadata.canonical_language(post, metadata)
|
||||
translations = PostMetadata.translations(post.id)
|
||||
|
||||
if DraftManagement.editing_canonical_language?(translations, active_language, canonical_language) do
|
||||
if DraftManagement.editing_canonical_language?(
|
||||
translations,
|
||||
active_language,
|
||||
canonical_language
|
||||
) do
|
||||
post
|
||||
|> save_canonical_draft(draft)
|
||||
|> maybe_publish_post(post.id, action)
|
||||
@@ -21,12 +26,17 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
||||
end
|
||||
end
|
||||
|
||||
@spec discard(term(), term(), term()) :: term()
|
||||
def discard(%Post{} = post, active_language, metadata) do
|
||||
canonical_language = PostMetadata.canonical_language(post, metadata)
|
||||
current_translations = PostMetadata.translations(post.id)
|
||||
|
||||
cond do
|
||||
not DraftManagement.editing_canonical_language?(current_translations, active_language, canonical_language) ->
|
||||
not DraftManagement.editing_canonical_language?(
|
||||
current_translations,
|
||||
active_language,
|
||||
canonical_language
|
||||
) ->
|
||||
{:ok, post}
|
||||
|
||||
post.file_path not in [nil, ""] and post.status == :draft ->
|
||||
@@ -37,15 +47,18 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
||||
end
|
||||
end
|
||||
|
||||
@spec has_published_version?(term()) :: term()
|
||||
def has_published_version?(%Post{} = post),
|
||||
do: not is_nil(post.published_at) or post.file_path not in [nil, ""]
|
||||
|
||||
@spec discard_label(term()) :: term()
|
||||
def discard_label(%Post{} = post) do
|
||||
if has_published_version?(post),
|
||||
do: translated("Discard Changes"),
|
||||
else: translated("Discard Draft")
|
||||
end
|
||||
|
||||
@spec discard_title(term()) :: term()
|
||||
def discard_title(%Post{} = post) do
|
||||
if has_published_version?(post),
|
||||
do: translated("Discard changes and restore the published version"),
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
alias BDS.Media.Media, as: MediaRecord
|
||||
alias BDS.Posts.{Post, PostMedia}
|
||||
|
||||
@spec project_metadata(term()) :: term()
|
||||
def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
|
||||
|
||||
def project_metadata(project_id) do
|
||||
@@ -17,6 +18,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
_error -> %{main_language: "en", blog_languages: []}
|
||||
end
|
||||
|
||||
@spec canonical_language(term(), term()) :: term()
|
||||
def canonical_language(post, metadata) do
|
||||
BDS.Desktop.ShellLive.PostEditor.DraftManagement.normalize_language(
|
||||
post.language,
|
||||
@@ -24,28 +26,36 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
)
|
||||
end
|
||||
|
||||
@spec translations(term()) :: term()
|
||||
def translations(post_id) do
|
||||
{:ok, translations} = Posts.list_post_translations(post_id)
|
||||
Map.new(translations, fn translation -> {translation.language, translation} end)
|
||||
end
|
||||
|
||||
@spec languages(term()) :: term()
|
||||
def languages(metadata) do
|
||||
(([metadata.main_language || "en"] ++ (metadata.blog_languages || [])) ++ Enum.map(I18n.supported_languages(), & &1.code))
|
||||
(([metadata.main_language || "en"] ++ (metadata.blog_languages || [])) ++
|
||||
Enum.map(I18n.supported_languages(), & &1.code))
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
||||
@spec template_options(term()) :: term()
|
||||
def 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)}
|
||||
select: %{
|
||||
slug: template.slug,
|
||||
title: fragment("COALESCE(?, ?)", template.title, template.slug)
|
||||
}
|
||||
)
|
||||
rescue
|
||||
_error -> []
|
||||
end
|
||||
|
||||
@spec linked_media(term()) :: term()
|
||||
def linked_media(post_id) do
|
||||
rows =
|
||||
Repo.all(
|
||||
@@ -74,6 +84,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
_error -> []
|
||||
end
|
||||
|
||||
@spec post_links(term()) :: term()
|
||||
def post_links(post_id) do
|
||||
%{
|
||||
backlinks: related_posts(PostLinks.list_incoming_links(post_id), :source_post_id),
|
||||
@@ -84,15 +95,29 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
defp related_posts(links, key) do
|
||||
Enum.map(links, fn link ->
|
||||
case Posts.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
|
||||
%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
|
||||
|
||||
@spec translation_flags(term(), term(), term(), term()) :: term()
|
||||
def 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}
|
||||
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
|
||||
@@ -111,6 +136,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
[canonical | others]
|
||||
end
|
||||
|
||||
@spec footer(term(), term(), term(), term()) :: term()
|
||||
def footer(post, translation, active_language, canonical_language) do
|
||||
if active_language == canonical_language do
|
||||
%{
|
||||
@@ -120,8 +146,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
}
|
||||
else
|
||||
%{
|
||||
created_at: format_timestamp(translation && translation.created_at || post.created_at),
|
||||
updated_at: format_timestamp(translation && translation.updated_at || post.updated_at),
|
||||
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
|
||||
@@ -135,10 +161,12 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
|> Calendar.strftime("%x")
|
||||
end
|
||||
|
||||
@spec display_title(term(), term(), term()) :: term()
|
||||
def display_title(title, slug, fallback_id) do
|
||||
blank_to_nil(title) || blank_to_nil(slug) || fallback_id || translated("Untitled")
|
||||
end
|
||||
|
||||
@spec gallery_count(term()) :: term()
|
||||
def gallery_count(form) do
|
||||
form
|
||||
|> Map.get("content", "")
|
||||
@@ -147,8 +175,11 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
|> length()
|
||||
end
|
||||
|
||||
def preview_url(_post, _active_language, _canonical_language, mode) when mode != :preview, do: nil
|
||||
@spec preview_url(term(), term(), term(), term()) :: term()
|
||||
def preview_url(_post, _active_language, _canonical_language, mode) when mode != :preview,
|
||||
do: nil
|
||||
|
||||
@spec preview_url(term(), term(), term(), term()) :: term()
|
||||
def preview_url(%Post{} = post, active_language, canonical_language, :preview) do
|
||||
query =
|
||||
%{}
|
||||
@@ -156,7 +187,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
|> maybe_put_query("post_id", post.id)
|
||||
|> maybe_put_query("lang", active_language != canonical_language && active_language)
|
||||
|
||||
Preview.base_url() <> canonical_preview_path(post.created_at, post.slug) <> "?" <> URI.encode_query(query)
|
||||
Preview.base_url() <>
|
||||
canonical_preview_path(post.created_at, post.slug) <> "?" <> URI.encode_query(query)
|
||||
end
|
||||
|
||||
defp canonical_preview_path(created_at_ms, slug) do
|
||||
@@ -171,10 +203,13 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
||||
defp maybe_put_query(query, key, value), do: Map.put(query, key, value)
|
||||
|
||||
def truthy?(value) when value in [true, "true", "on", 1, "1"], do: true
|
||||
@spec truthy?(term()) :: term()
|
||||
def truthy?(_value), do: false
|
||||
|
||||
@spec blank?(term()) :: term()
|
||||
def blank?(value), do: blank_to_nil(value) == nil
|
||||
|
||||
@spec blank_to_nil(term()) :: term()
|
||||
def blank_to_nil(value) do
|
||||
value
|
||||
|> to_string()
|
||||
|
||||
@@ -19,7 +19,9 @@ defmodule BDS.Desktop.ShellLive.SessionUtil do
|
||||
Stream.iterate(1, &(&1 + 1))
|
||||
|> Enum.find_value(fn index ->
|
||||
candidate =
|
||||
if index == 1, do: @default_new_project_name, else: "#{@default_new_project_name} #{index}"
|
||||
if index == 1,
|
||||
do: @default_new_project_name,
|
||||
else: "#{@default_new_project_name} #{index}"
|
||||
|
||||
if MapSet.member?(existing_names, candidate), do: nil, else: candidate
|
||||
end)
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
alias BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings
|
||||
alias BDS.Desktop.ShellLive.SettingsEditor.StyleEditor
|
||||
|
||||
embed_templates "settings_editor_html/*"
|
||||
embed_templates("settings_editor_html/*")
|
||||
|
||||
@settings_sections ~w(project editor content ai technology publishing data mcp)
|
||||
@supported_languages ["en", "de", "fr", "it", "es"]
|
||||
@@ -45,6 +45,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
defdelegate theme_display_name(theme), to: StyleEditor
|
||||
defdelegate protected_category?(category), to: ManagedCategories
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
case socket.assigns[:current_tab] do
|
||||
%{type: :settings} ->
|
||||
@@ -64,12 +65,14 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_search(term(), term(), term()) :: term()
|
||||
def update_search(socket, query, reload) do
|
||||
socket
|
||||
|> assign(:settings_editor_search, to_string(query || ""))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec build_settings(term()) :: term()
|
||||
def build_settings(%{projects: %{active_project_id: nil}}), do: nil
|
||||
|
||||
def build_settings(assigns) do
|
||||
@@ -82,7 +85,10 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
)
|
||||
|
||||
editor_form =
|
||||
Map.merge(EditorSettings.editor_form(), Map.get(assigns, :settings_editor_editor_draft, %{}))
|
||||
Map.merge(
|
||||
EditorSettings.editor_form(),
|
||||
Map.get(assigns, :settings_editor_editor_draft, %{})
|
||||
)
|
||||
|
||||
ai_form =
|
||||
Map.merge(AISettings.ai_form(assigns), Map.get(assigns, :settings_editor_ai_draft, %{}))
|
||||
@@ -142,6 +148,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
}
|
||||
end
|
||||
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
@@ -171,7 +178,10 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
Enum.filter(@settings_sections, fn section ->
|
||||
case section do
|
||||
"project" ->
|
||||
section_matches?(query, ~w(project name description data url language author bookmarklet))
|
||||
section_matches?(
|
||||
query,
|
||||
~w(project name description data url language author bookmarklet)
|
||||
)
|
||||
|
||||
"editor" ->
|
||||
section_matches?(query, ~w(editor mode markdown preview diff wrap unchanged))
|
||||
@@ -195,7 +205,10 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
section_matches?(query, ~w(data rebuild maintenance links thumbnails filesystem))
|
||||
|
||||
"mcp" ->
|
||||
section_matches?(query, ~w(mcp claude copilot gemini opencode mistral codex agent server))
|
||||
section_matches?(
|
||||
query,
|
||||
~w(mcp claude copilot gemini opencode mistral codex agent server)
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
|
||||
alias BDS.Desktop.ShellData
|
||||
alias BDS.Desktop.ShellLive.SettingsEditor.EditorSettings
|
||||
|
||||
@spec ai_form(term()) :: term()
|
||||
def ai_form(assigns) do
|
||||
{:ok, online_endpoint} = AI.get_endpoint(:online)
|
||||
{:ok, airplane_endpoint} = AI.get_endpoint(:airplane)
|
||||
@@ -30,18 +31,21 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
|
||||
}
|
||||
end
|
||||
|
||||
@spec endpoint_model_options(term(), term()) :: term()
|
||||
def endpoint_model_options(assigns, endpoint_key) do
|
||||
assigns
|
||||
|> Map.get(:settings_editor_endpoint_models, %{})
|
||||
|> Map.get(endpoint_key, [])
|
||||
end
|
||||
|
||||
@spec update_ai_draft(term(), term(), term()) :: term()
|
||||
def update_ai_draft(socket, params, reload) do
|
||||
socket
|
||||
|> assign(:settings_editor_ai_draft, normalize_ai_params(params))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec refresh_ai_models(term(), term(), term(), term()) :: term()
|
||||
def refresh_ai_models(socket, endpoint_key, reload, append_output) do
|
||||
attrs = ai_attrs(socket.assigns)
|
||||
|
||||
@@ -65,11 +69,17 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
|
||||
end
|
||||
end
|
||||
|
||||
@spec save_ai(term(), term(), term()) :: term()
|
||||
def save_ai(socket, reload, append_output) do
|
||||
attrs = ai_attrs(socket.assigns)
|
||||
|
||||
with :ok <-
|
||||
put_endpoint_preferences(:online, attrs.online_url, attrs.online_api_key, attrs.online_chat_model),
|
||||
put_endpoint_preferences(
|
||||
:online,
|
||||
attrs.online_url,
|
||||
attrs.online_api_key,
|
||||
attrs.online_chat_model
|
||||
),
|
||||
:ok <-
|
||||
put_endpoint_preferences(
|
||||
:airplane,
|
||||
@@ -85,7 +95,10 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
|
||||
:ok <- maybe_put_model_preference(:airplane_chat, attrs.offline_chat_model),
|
||||
:ok <- maybe_put_model_preference(:airplane_title, attrs.offline_title_model),
|
||||
:ok <-
|
||||
maybe_put_model_preference(:airplane_image_analysis, attrs.offline_image_analysis_model),
|
||||
maybe_put_model_preference(
|
||||
:airplane_image_analysis,
|
||||
attrs.offline_image_analysis_model
|
||||
),
|
||||
:ok <- EditorSettings.put_global_setting("ai.system_prompt", attrs.system_prompt) do
|
||||
socket
|
||||
|> assign(:settings_editor_ai_draft, %{})
|
||||
@@ -99,6 +112,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
|
||||
end
|
||||
end
|
||||
|
||||
@spec reset_ai_prompt(term(), term(), term()) :: term()
|
||||
def reset_ai_prompt(socket, reload, append_output) do
|
||||
case EditorSettings.put_global_setting("ai.system_prompt", "") do
|
||||
:ok ->
|
||||
|
||||
@@ -6,21 +6,25 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
|
||||
alias BDS.Settings
|
||||
alias BDS.Desktop.ShellData
|
||||
|
||||
@spec editor_form() :: term()
|
||||
def editor_form do
|
||||
%{
|
||||
"default_mode" => get_global_setting("ui.preferred_editor_mode") || "markdown",
|
||||
"diff_view_style" => get_global_setting("ui.git_diff_view_style") || "inline",
|
||||
"wrap_long_lines" => get_global_setting("ui.git_diff_word_wrap") == "true",
|
||||
"hide_unchanged_regions" => get_global_setting("ui.git_diff_hide_unchanged_regions") == "true"
|
||||
"hide_unchanged_regions" =>
|
||||
get_global_setting("ui.git_diff_hide_unchanged_regions") == "true"
|
||||
}
|
||||
end
|
||||
|
||||
@spec update_editor_draft(term(), term(), term()) :: term()
|
||||
def update_editor_draft(socket, params, reload) do
|
||||
socket
|
||||
|> assign(:settings_editor_editor_draft, normalize_editor_params(params))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec save_editor(term(), term(), term()) :: term()
|
||||
def save_editor(socket, reload, append_output) do
|
||||
attrs = editor_attrs(socket.assigns)
|
||||
|
||||
@@ -43,10 +47,12 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_global_setting(term()) :: term()
|
||||
def get_global_setting(key) do
|
||||
Settings.get_global_setting(key)
|
||||
end
|
||||
|
||||
@spec put_global_setting(term(), term()) :: term()
|
||||
def put_global_setting(key, value) do
|
||||
Settings.put_global_setting(key, value)
|
||||
end
|
||||
|
||||
@@ -14,10 +14,13 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
|
||||
"page" => %{title: "page", render_in_lists: false, show_title: true}
|
||||
}
|
||||
|
||||
@spec protected_categories() :: term()
|
||||
def protected_categories, do: @protected_categories
|
||||
|
||||
@spec protected_category?(term()) :: term()
|
||||
def protected_category?(category), do: MapSet.member?(@protected_categories, category)
|
||||
|
||||
@spec category_rows(term()) :: term()
|
||||
def category_rows(metadata) do
|
||||
categories = Map.get(metadata, :categories, [])
|
||||
settings = Map.get(metadata, :category_settings, %{})
|
||||
@@ -37,12 +40,14 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec update_new_category(term(), term(), term()) :: term()
|
||||
def update_new_category(socket, name, reload) do
|
||||
socket
|
||||
|> assign(:settings_editor_new_category, to_string(name || ""))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec add_category(term(), term(), term()) :: term()
|
||||
def add_category(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
name = socket.assigns[:settings_editor_new_category] |> to_string() |> String.trim()
|
||||
@@ -73,11 +78,13 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
|
||||
end
|
||||
end
|
||||
|
||||
@spec reset_categories(term(), term(), term()) :: term()
|
||||
def reset_categories(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
result =
|
||||
Enum.reduce_while(category_names(project_metadata(socket.assigns)), :ok, fn category, _acc ->
|
||||
Enum.reduce_while(category_names(project_metadata(socket.assigns)), :ok, fn category,
|
||||
_acc ->
|
||||
if MapSet.member?(@protected_categories, category) do
|
||||
{:cont, :ok}
|
||||
else
|
||||
@@ -102,6 +109,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
|
||||
end
|
||||
end
|
||||
|
||||
@spec save_category(term(), term(), term(), term()) :: term()
|
||||
def save_category(socket, params, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
category = Map.get(params, "category", "")
|
||||
@@ -125,6 +133,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
|
||||
end
|
||||
end
|
||||
|
||||
@spec remove_category(term(), term(), term(), term()) :: term()
|
||||
def remove_category(socket, category, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
|
||||
%{id: :openai_codex, label: "OpenAI Codex", supported?: false}
|
||||
]
|
||||
|
||||
@spec mcp_rows() :: term()
|
||||
def mcp_rows do
|
||||
Enum.map(@mcp_agents, fn agent ->
|
||||
%{
|
||||
@@ -28,6 +29,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec toggle_mcp_agent(term(), term(), term(), term()) :: term()
|
||||
def toggle_mcp_agent(socket, agent, reload, append_output) do
|
||||
case find_mcp_agent(agent) do
|
||||
%{id: agent_id, supported?: true} = config ->
|
||||
|
||||
@@ -6,12 +6,14 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
|
||||
alias BDS.Metadata
|
||||
alias BDS.Desktop.ShellData
|
||||
|
||||
@spec project_metadata(term()) :: term()
|
||||
def project_metadata(assigns) do
|
||||
case Metadata.get_project_metadata(assigns.projects.active_project_id) do
|
||||
{:ok, metadata} -> metadata
|
||||
end
|
||||
end
|
||||
|
||||
@spec project_form(term()) :: term()
|
||||
def project_form(metadata) do
|
||||
%{
|
||||
"name" => Map.get(metadata, :name, ""),
|
||||
@@ -28,18 +30,21 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
|
||||
}
|
||||
end
|
||||
|
||||
@spec technology_form(term()) :: term()
|
||||
def technology_form(project_form) do
|
||||
%{
|
||||
"semantic_similarity_enabled" => Map.get(project_form, "semantic_similarity_enabled", false)
|
||||
}
|
||||
end
|
||||
|
||||
@spec update_project_draft(term(), term(), term()) :: term()
|
||||
def update_project_draft(socket, params, reload) do
|
||||
socket
|
||||
|> assign(:settings_editor_project_draft, normalize_project_params(params))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec save_project(term(), term(), term()) :: term()
|
||||
def save_project(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
|
||||
alias BDS.Metadata
|
||||
alias BDS.Desktop.ShellData
|
||||
|
||||
@spec publishing_form(term()) :: term()
|
||||
def publishing_form(metadata) do
|
||||
prefs = Map.get(metadata, :publishing_preferences, %{})
|
||||
|
||||
@@ -17,12 +18,14 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
|
||||
}
|
||||
end
|
||||
|
||||
@spec update_publishing_draft(term(), term(), term()) :: term()
|
||||
def update_publishing_draft(socket, params, reload) do
|
||||
socket
|
||||
|> assign(:settings_editor_publishing_draft, normalize_publishing_params(params))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec save_publishing(term(), term(), term()) :: term()
|
||||
def save_publishing(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
@@ -39,6 +42,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
|
||||
end
|
||||
end
|
||||
|
||||
@spec clear_publishing(term(), term(), term()) :: term()
|
||||
def clear_publishing(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
||||
"zinc"
|
||||
]
|
||||
|
||||
@spec build_style(term()) :: term()
|
||||
def build_style(%{projects: %{active_project_id: nil}}), do: nil
|
||||
|
||||
def build_style(assigns) do
|
||||
@@ -40,22 +41,26 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
||||
selected_theme: selected_theme,
|
||||
applied_theme: current_theme(assigns),
|
||||
preview_mode: preview_mode,
|
||||
preview_url: "http://127.0.0.1:4123/__style-preview?theme=#{selected_theme}&mode=#{preview_mode}"
|
||||
preview_url:
|
||||
"http://127.0.0.1:4123/__style-preview?theme=#{selected_theme}&mode=#{preview_mode}"
|
||||
}
|
||||
end
|
||||
|
||||
@spec select_style_theme(term(), term(), term()) :: term()
|
||||
def select_style_theme(socket, theme, reload) do
|
||||
socket
|
||||
|> assign(:style_editor_theme, to_string(theme || "default"))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec change_style_preview_mode(term(), term(), term()) :: term()
|
||||
def change_style_preview_mode(socket, mode, reload) do
|
||||
socket
|
||||
|> assign(:style_editor_preview_mode, to_string(mode || "auto"))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec apply_style_theme(term(), term(), term()) :: term()
|
||||
def apply_style_theme(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
theme = socket.assigns[:style_editor_theme] || current_theme(socket.assigns)
|
||||
@@ -71,6 +76,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec theme_display_name(term()) :: term()
|
||||
def theme_display_name(theme) do
|
||||
theme
|
||||
|> to_string()
|
||||
@@ -78,6 +84,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
||||
|> String.capitalize()
|
||||
end
|
||||
|
||||
@spec current_theme(term()) :: term()
|
||||
def current_theme(assigns) do
|
||||
case Metadata.get_project_metadata(assigns.projects.active_project_id) do
|
||||
{:ok, metadata} ->
|
||||
|
||||
@@ -22,7 +22,13 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
end
|
||||
|
||||
def create(socket, project_id, "post", callbacks) do
|
||||
case BDS.Posts.create_post(%{project_id: project_id, title: "", content: "", tags: [], categories: []}) do
|
||||
case BDS.Posts.create_post(%{
|
||||
project_id: project_id,
|
||||
title: "",
|
||||
content: "",
|
||||
tags: [],
|
||||
categories: []
|
||||
}) do
|
||||
{:ok, _post} ->
|
||||
callbacks.reload.(socket, socket.assigns.workbench)
|
||||
|
||||
@@ -42,7 +48,12 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(translated("sidebar.importMedia"), inspect(reason), nil, "error")
|
||||
|> callbacks.append_output.(
|
||||
translated("sidebar.importMedia"),
|
||||
inspect(reason),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@@ -68,13 +79,23 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
{:ok, script} ->
|
||||
callbacks.open_sidebar.(
|
||||
socket,
|
||||
%{"route" => "scripts", "id" => script.id, "title" => script.title, "subtitle" => "Automation helpers"},
|
||||
%{
|
||||
"route" => "scripts",
|
||||
"id" => script.id,
|
||||
"title" => script.title,
|
||||
"subtitle" => "Automation helpers"
|
||||
},
|
||||
:pin
|
||||
)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(translated("sidebar.scripts.newScript"), inspect(reason), nil, "error")
|
||||
|> callbacks.append_output.(
|
||||
translated("sidebar.scripts.newScript"),
|
||||
inspect(reason),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
@@ -90,29 +111,52 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
{:ok, template} ->
|
||||
callbacks.open_sidebar.(
|
||||
socket,
|
||||
%{"route" => "templates", "id" => template.id, "title" => template.title, "subtitle" => "Site rendering"},
|
||||
%{
|
||||
"route" => "templates",
|
||||
"id" => template.id,
|
||||
"title" => template.title,
|
||||
"subtitle" => "Site rendering"
|
||||
},
|
||||
:pin
|
||||
)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(translated("sidebar.templates.newTemplate"), inspect(reason), nil, "error")
|
||||
|> callbacks.append_output.(
|
||||
translated("sidebar.templates.newTemplate"),
|
||||
inspect(reason),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
def create(socket, project_id, "import", callbacks) do
|
||||
case ImportDefinitions.create_definition(%{project_id: project_id, name: translated("sidebar.import.newDefinition")}) do
|
||||
case ImportDefinitions.create_definition(%{
|
||||
project_id: project_id,
|
||||
name: translated("sidebar.import.newDefinition")
|
||||
}) do
|
||||
{:ok, definition} ->
|
||||
callbacks.open_sidebar.(
|
||||
socket,
|
||||
%{"route" => "import", "id" => definition.id, "title" => definition.name, "subtitle" => "Import definitions"},
|
||||
%{
|
||||
"route" => "import",
|
||||
"id" => definition.id,
|
||||
"title" => definition.name,
|
||||
"subtitle" => "Import definitions"
|
||||
},
|
||||
:pin
|
||||
)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(translated("sidebar.import.newDefinition"), inspect(reason), nil, "error")
|
||||
|> callbacks.append_output.(
|
||||
translated("sidebar.import.newDefinition"),
|
||||
inspect(reason),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,13 +7,17 @@ defmodule BDS.Desktop.ShellLive.SidebarState do
|
||||
if is_map(filters) and Map.get(filters, :enabled) do
|
||||
panel_state = filter_panel_state(socket, view_id)
|
||||
|
||||
Map.put(sidebar_data, :filters, Map.merge(filters, %{
|
||||
filter_panel_visible: panel_state.visible,
|
||||
archive_collapsed: panel_state.archive_collapsed,
|
||||
tags_collapsed: panel_state.tags_collapsed,
|
||||
categories_collapsed: panel_state.categories_collapsed,
|
||||
expanded_year: panel_state.expanded_year
|
||||
}))
|
||||
Map.put(
|
||||
sidebar_data,
|
||||
:filters,
|
||||
Map.merge(filters, %{
|
||||
filter_panel_visible: panel_state.visible,
|
||||
archive_collapsed: panel_state.archive_collapsed,
|
||||
tags_collapsed: panel_state.tags_collapsed,
|
||||
categories_collapsed: panel_state.categories_collapsed,
|
||||
expanded_year: panel_state.expanded_year
|
||||
})
|
||||
)
|
||||
else
|
||||
sidebar_data
|
||||
end
|
||||
@@ -22,7 +26,12 @@ defmodule BDS.Desktop.ShellLive.SidebarState do
|
||||
def put_filter_panel_state(socket, updater) do
|
||||
view_id = Atom.to_string(socket.assigns.workbench.active_view)
|
||||
state = socket |> filter_panel_state(view_id) |> updater.()
|
||||
Phoenix.Component.assign(socket, :sidebar_filter_panels, Map.put(socket.assigns.sidebar_filter_panels, view_id, state))
|
||||
|
||||
Phoenix.Component.assign(
|
||||
socket,
|
||||
:sidebar_filter_panels,
|
||||
Map.put(socket.assigns.sidebar_filter_panels, view_id, state)
|
||||
)
|
||||
end
|
||||
|
||||
def current_filters(socket, view_id) do
|
||||
@@ -33,8 +42,17 @@ defmodule BDS.Desktop.ShellLive.SidebarState do
|
||||
|
||||
def put_filters(socket, updater) do
|
||||
view_id = Atom.to_string(socket.assigns.workbench.active_view)
|
||||
filters = current_filters(socket, view_id) |> updater.() |> normalize_filters(socket.assigns.sidebar_data)
|
||||
Phoenix.Component.assign(socket, :sidebar_filters_by_view, Map.put(socket.assigns.sidebar_filters_by_view, view_id, filters))
|
||||
|
||||
filters =
|
||||
current_filters(socket, view_id)
|
||||
|> updater.()
|
||||
|> normalize_filters(socket.assigns.sidebar_data)
|
||||
|
||||
Phoenix.Component.assign(
|
||||
socket,
|
||||
:sidebar_filters_by_view,
|
||||
Map.put(socket.assigns.sidebar_filters_by_view, view_id, filters)
|
||||
)
|
||||
end
|
||||
|
||||
def toggle_filter_value(filters, key, value) do
|
||||
|
||||
@@ -11,12 +11,14 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
alias BDS.Tags.Tag
|
||||
alias BDS.Templates.Template
|
||||
|
||||
embed_templates "tags_editor_html/*"
|
||||
embed_templates("tags_editor_html/*")
|
||||
|
||||
@spec assign_socket(term()) :: term()
|
||||
def assign_socket(socket) do
|
||||
assign(socket, :tags_editor, build(socket.assigns))
|
||||
end
|
||||
|
||||
@spec toggle_selection(term(), term(), term()) :: term()
|
||||
def toggle_selection(socket, tag_name, reload) do
|
||||
selected = Map.get(socket.assigns, :tags_editor_selected, [])
|
||||
|
||||
@@ -33,6 +35,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec update_new_tag(term(), term(), term()) :: term()
|
||||
def update_new_tag(socket, params, reload) do
|
||||
socket
|
||||
|> assign(:tags_editor_new_tag, %{
|
||||
@@ -42,11 +45,16 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec create_tag(term(), term(), term()) :: term()
|
||||
def create_tag(socket, reload, append_output) do
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
draft = Map.get(socket.assigns, :tags_editor_new_tag, %{})
|
||||
|
||||
case Tags.create_tag(%{project_id: project_id, name: Map.get(draft, "name"), color: blank_to_nil(Map.get(draft, "color"))}) do
|
||||
case Tags.create_tag(%{
|
||||
project_id: project_id,
|
||||
name: Map.get(draft, "name"),
|
||||
color: blank_to_nil(Map.get(draft, "color"))
|
||||
}) do
|
||||
{:ok, _tag} ->
|
||||
socket
|
||||
|> assign(:tags_editor_new_tag, %{"name" => "", "color" => ""})
|
||||
@@ -59,6 +67,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_edit_tag(term(), term(), term()) :: term()
|
||||
def update_edit_tag(socket, params, reload) do
|
||||
socket
|
||||
|> assign(:tags_editor_edit_draft, %{
|
||||
@@ -69,16 +78,26 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec save_tag(term(), term(), term()) :: term()
|
||||
def save_tag(socket, reload, append_output) do
|
||||
selected = Map.get(socket.assigns, :tags_editor_selected, [])
|
||||
draft = Map.get(socket.assigns, :tags_editor_edit_draft, %{})
|
||||
|
||||
case selected do
|
||||
[tag_name] ->
|
||||
case Repo.get_by(Tag, project_id: socket.assigns.projects.active_project_id, name: tag_name) do
|
||||
nil -> reload.(socket, socket.assigns.workbench)
|
||||
case Repo.get_by(Tag,
|
||||
project_id: socket.assigns.projects.active_project_id,
|
||||
name: tag_name
|
||||
) do
|
||||
nil ->
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
|
||||
%Tag{} = tag ->
|
||||
with {:ok, _updated_tag} <- Tags.update_tag(tag.id, %{color: blank_to_nil(Map.get(draft, "color")), post_template_slug: blank_to_nil(Map.get(draft, "post_template_slug"))}),
|
||||
with {:ok, _updated_tag} <-
|
||||
Tags.update_tag(tag.id, %{
|
||||
color: blank_to_nil(Map.get(draft, "color")),
|
||||
post_template_slug: blank_to_nil(Map.get(draft, "post_template_slug"))
|
||||
}),
|
||||
{:ok, renamed_tag} <- maybe_rename_tag(tag, Map.get(draft, "name", tag.name)) do
|
||||
socket
|
||||
|> assign(:tags_editor_selected, [renamed_tag.name])
|
||||
@@ -92,15 +111,22 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
end
|
||||
end
|
||||
|
||||
_other -> reload.(socket, socket.assigns.workbench)
|
||||
_other ->
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_selected(term(), term(), term()) :: term()
|
||||
def delete_selected(socket, reload, append_output) do
|
||||
case Map.get(socket.assigns, :tags_editor_selected, []) do
|
||||
[tag_name] ->
|
||||
case Repo.get_by(Tag, project_id: socket.assigns.projects.active_project_id, name: tag_name) do
|
||||
nil -> reload.(socket, socket.assigns.workbench)
|
||||
case Repo.get_by(Tag,
|
||||
project_id: socket.assigns.projects.active_project_id,
|
||||
name: tag_name
|
||||
) do
|
||||
nil ->
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
|
||||
%Tag{} = tag ->
|
||||
case Tags.delete_tag(tag.id) do
|
||||
{:ok, _deleted} ->
|
||||
@@ -116,16 +142,19 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
end
|
||||
end
|
||||
|
||||
_other -> reload.(socket, socket.assigns.workbench)
|
||||
_other ->
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_merge_target(term(), term(), term()) :: term()
|
||||
def update_merge_target(socket, target, reload) do
|
||||
socket
|
||||
|> assign(:tags_editor_merge_target, to_string(target || ""))
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec merge_selected(term(), term(), term()) :: term()
|
||||
def merge_selected(socket, reload, append_output) do
|
||||
selected = Map.get(socket.assigns, :tags_editor_selected, [])
|
||||
target_name = Map.get(socket.assigns, :tags_editor_merge_target, "")
|
||||
@@ -136,12 +165,19 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
|
||||
true ->
|
||||
project_id = socket.assigns.projects.active_project_id
|
||||
tags = Repo.all(from tag in Tag, where: tag.project_id == ^project_id and tag.name in ^selected)
|
||||
|
||||
tags =
|
||||
Repo.all(
|
||||
from tag in Tag, where: tag.project_id == ^project_id and tag.name in ^selected
|
||||
)
|
||||
|
||||
target = Enum.find(tags, &(&1.name == target_name))
|
||||
sources = Enum.reject(tags, &(&1.name == target_name))
|
||||
|
||||
case target do
|
||||
nil -> reload.(socket, socket.assigns.workbench)
|
||||
nil ->
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
|
||||
_target ->
|
||||
case Tags.merge_tags(Enum.map(sources, & &1.id), target.id) do
|
||||
{:ok, _merged} ->
|
||||
@@ -160,23 +196,41 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec sync(term(), term(), term()) :: term()
|
||||
def sync(socket, reload, append_output) do
|
||||
_ = append_output
|
||||
:ok = Tags.sync_tags_json(socket.assigns.projects.active_project_id)
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
|
||||
@spec build(term()) :: term()
|
||||
def build(%{current_tab: %{type: :tags}} = assigns) do
|
||||
project_id = assigns.projects.active_project_id
|
||||
tags = Repo.all(from tag in Tag, where: tag.project_id == ^project_id, order_by: [asc: tag.name])
|
||||
|
||||
tags =
|
||||
Repo.all(from tag in Tag, where: tag.project_id == ^project_id, order_by: [asc: tag.name])
|
||||
|
||||
counts = tag_counts(project_id)
|
||||
selected = Map.get(assigns, :tags_editor_selected, [])
|
||||
edit_tag = if length(selected) == 1, do: Enum.find(tags, &(&1.name == hd(selected))), else: nil
|
||||
|
||||
edit_tag =
|
||||
if length(selected) == 1, do: Enum.find(tags, &(&1.name == hd(selected))), else: nil
|
||||
|
||||
edit_draft = Map.get(assigns, :tags_editor_edit_draft, edit_draft(edit_tag))
|
||||
templates = Repo.all(from template in Template, where: template.project_id == ^project_id, order_by: [asc: template.title], select: %{slug: template.slug, title: template.title})
|
||||
|
||||
templates =
|
||||
Repo.all(
|
||||
from template in Template,
|
||||
where: template.project_id == ^project_id,
|
||||
order_by: [asc: template.title],
|
||||
select: %{slug: template.slug, title: template.title}
|
||||
)
|
||||
|
||||
%{
|
||||
tags: Enum.map(tags, fn tag -> %{name: tag.name, color: tag.color, count: Map.get(counts, tag.name, 0)} end),
|
||||
tags:
|
||||
Enum.map(tags, fn tag ->
|
||||
%{name: tag.name, color: tag.color, count: Map.get(counts, tag.name, 0)}
|
||||
end),
|
||||
selected: selected,
|
||||
new_tag: Map.get(assigns, :tags_editor_new_tag, %{"name" => "", "color" => ""}),
|
||||
edit_draft: edit_draft,
|
||||
@@ -187,14 +241,18 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
|
||||
def build(_assigns), do: nil
|
||||
|
||||
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
@spec translated(term(), term()) :: term()
|
||||
def translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
@spec tag_font_size(term(), term()) :: term()
|
||||
def tag_font_size(count, counts) do
|
||||
max_count = Enum.max([1 | Enum.map(counts, & &1.count)])
|
||||
ratio = if max_count <= 1, do: 0.0, else: (count - 1) / max(max_count - 1, 1)
|
||||
Float.round(0.85 + (1.8 - 0.85) * ratio, 2)
|
||||
end
|
||||
|
||||
@spec tag_style(term(), term()) :: term()
|
||||
def tag_style(tag, counts) do
|
||||
size = tag_font_size(tag.count, counts)
|
||||
|
||||
@@ -217,7 +275,13 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
defp maybe_seed_edit_draft(socket, _selected), do: assign(socket, :tags_editor_edit_draft, %{})
|
||||
|
||||
defp edit_draft(nil), do: %{}
|
||||
defp edit_draft(%Tag{} = tag), do: %{"name" => tag.name, "color" => tag.color || "", "post_template_slug" => tag.post_template_slug || ""}
|
||||
|
||||
defp edit_draft(%Tag{} = tag),
|
||||
do: %{
|
||||
"name" => tag.name,
|
||||
"color" => tag.color || "",
|
||||
"post_template_slug" => tag.post_template_slug || ""
|
||||
}
|
||||
|
||||
defp maybe_rename_tag(%Tag{} = tag, next_name) do
|
||||
normalized = String.trim(to_string(next_name || tag.name))
|
||||
@@ -237,6 +301,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
||||
end
|
||||
|
||||
defp blank_to_nil(nil), do: nil
|
||||
|
||||
defp blank_to_nil(value) do
|
||||
case String.trim(to_string(value)) do
|
||||
"" -> nil
|
||||
|
||||
@@ -46,7 +46,10 @@ defmodule BDS.Desktop.ShellLive.TaskLocalization do
|
||||
|> Map.put(:message, localize_task_message(Map.get(task, :message), locale))
|
||||
|> Map.put(:group_name, localize_task_group(Map.get(task, :group_name), locale))
|
||||
|> Map.put(:status_label, localize_task_status_label(task.status, locale))
|
||||
|> Map.put(:progress_label, if(is_number(progress), do: progress_percent(progress), else: nil))
|
||||
|> Map.put(
|
||||
:progress_label,
|
||||
if(is_number(progress), do: progress_percent(progress), else: nil)
|
||||
)
|
||||
end
|
||||
|
||||
defp localize_task_message(nil, _locale), do: nil
|
||||
|
||||
@@ -41,7 +41,9 @@ defmodule BDS.Desktop.ShellLive.TitlebarMenu do
|
||||
|
||||
@spec active_group(map()) :: map() | nil
|
||||
def active_group(assigns) do
|
||||
Enum.find(assigns.menu_groups || [], fn group -> Atom.to_string(group.id) == assigns.titlebar_menu_group end)
|
||||
Enum.find(assigns.menu_groups || [], fn group ->
|
||||
Atom.to_string(group.id) == assigns.titlebar_menu_group
|
||||
end)
|
||||
end
|
||||
|
||||
@spec active_items(map()) :: [map()]
|
||||
@@ -90,7 +92,9 @@ defmodule BDS.Desktop.ShellLive.TitlebarMenu do
|
||||
Handle a keydown event on an open titlebar menu. `invoke_fun` is called
|
||||
with the action id (string) when the user activates an item.
|
||||
"""
|
||||
@spec handle_keydown(Phoenix.LiveView.Socket.t(), String.t(), (Phoenix.LiveView.Socket.t(), String.t() -> Phoenix.LiveView.Socket.t())) ::
|
||||
@spec handle_keydown(Phoenix.LiveView.Socket.t(), String.t(), (Phoenix.LiveView.Socket.t(),
|
||||
String.t() ->
|
||||
Phoenix.LiveView.Socket.t())) ::
|
||||
Phoenix.LiveView.Socket.t()
|
||||
def handle_keydown(socket, key, invoke_fun) do
|
||||
if socket.assigns.titlebar_menu_group do
|
||||
@@ -114,7 +118,9 @@ defmodule BDS.Desktop.ShellLive.TitlebarMenu do
|
||||
defp rotate_group(socket, offset) do
|
||||
groups = socket.assigns.menu_groups || []
|
||||
current_group = socket.assigns.titlebar_menu_group
|
||||
current_index = Enum.find_index(groups, fn group -> Atom.to_string(group.id) == current_group end)
|
||||
|
||||
current_index =
|
||||
Enum.find_index(groups, fn group -> Atom.to_string(group.id) == current_group end)
|
||||
|
||||
if is_nil(current_index) or groups == [] do
|
||||
socket
|
||||
|
||||
Reference in New Issue
Block a user