From a3ca44640cc6193b249af7d5232dd54e34ca40cc Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Tue, 14 Jul 2026 20:33:30 +0200 Subject: [PATCH] feat: terminal UI over the shared workbench core (issue #26, phase 4) --- README.md | 57 +++ lib/bds/application.ex | 4 + lib/bds/server.ex | 13 +- lib/bds/tui.ex | 591 +++++++++++++++++++++++++++++- priv/gettext/de/LC_MESSAGES/ui.po | 189 +++++++--- priv/gettext/en/LC_MESSAGES/ui.po | 179 ++++++--- priv/gettext/es/LC_MESSAGES/ui.po | 189 +++++++--- priv/gettext/fr/LC_MESSAGES/ui.po | 189 +++++++--- priv/gettext/it/LC_MESSAGES/ui.po | 189 +++++++--- priv/gettext/ui.pot | 177 ++++++--- specs/server.allium | 5 +- specs/tui.allium | 70 ++++ test/bds/server_test.exs | 9 + test/bds/tui_test.exs | 135 +++++++ 14 files changed, 1701 insertions(+), 295 deletions(-) create mode 100644 specs/tui.allium create mode 100644 test/bds/tui_test.exs diff --git a/README.md b/README.md index 5114515..74988f6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The major architectural rework is in place. - Assets use Phoenix-default Tailwind and esbuild tooling from [assets/](/Users/gb/Projects/bDS2/assets) into [priv/static/](/Users/gb/Projects/bDS2/priv/static). - Core editorial flows are implemented in the main application: posts, media, tags, templates, scripts, imports, preview, generation, publishing, maintenance, AI, and MCP. - Localization is now a first-class architectural concern rather than an afterthought: UI chrome and rendered site output have separate locale flows, and post/media translation workflows are built into the domain model. +- The app boots in three modes: the desktop app, a headless server (SSH-served terminal UI + tunneled GUI), and a local TUI — see [Headless Server Mode And Terminal UI](#headless-server-mode-and-terminal-ui). The rewrite still aims to preserve the product behavior of bDS while replacing the technical stack. The contract is product behavior, not the old implementation language or framework choices. @@ -156,6 +157,62 @@ mix dialyzer mix assets.build ``` +## Headless Server Mode And Terminal UI + +Since issue #26 the app has three boot modes, selected with the `BDS_MODE` +environment variable (any release or dev boot; default is the desktop app): + +- `desktop` — the native desktop app, unchanged. +- `server` — headless: no window, loopback-only HTTP endpoint, CLI sync + watcher, and an SSH daemon serving the terminal UI. Works on macOS, + Windows, and Linux (no display needed). +- `tui` — the headless server plus a TUI attached to the launching terminal. + +### Build and run a headless server + +```bash +MIX_ENV=prod mix release bds --overwrite +BDS_MODE=server _build/prod/rel/bds/bin/bds start +``` + +The SSH daemon listens on port `2222` (`BDS_SSH_PORT` or +`config :bds, :server, ssh_port:` to change). Authentication is public-key +only: on first boot the server generates a host key and an empty +`authorized_keys` (mode 600) in `ssh/` next to the database (default +`~/Library/Application Support/BDS2/ssh/`, or under `BDS_DATABASE_PATH`). +Add your public key there, then connect from any terminal: + +```bash +ssh -p 2222 # opens the TUI +``` + +Each SSH connection gets its own TUI session on the server; only terminal +cells cross the wire. All clients stay synchronized through the domain +event bus (`BDS.Events`) — a post created by a pipeline or another client +appears in every open shell. + +### TUI keys + +`↑/↓` or `j/k` navigate · `enter` open · `n` new post · `1-5` switch view +(posts/media/templates/scripts/tags) · `r` refresh · in the editor: +`ctrl+s` save, `ctrl+p` publish, `ctrl+t` title, `ctrl+l` language, +`ctrl+g` AI suggestions (airplane-mode gated), `esc` back · `ctrl+q` quit. +Images preview inline via the terminal image protocol. The UI language is +the server-side setting shared by every client. + +### Remote GUI + +The HTTP endpoint stays bound to `127.0.0.1` and is never exposed; GUI +clients tunnel through the same SSH daemon and keys: + +```bash +ssh -p 2222 -L 4010:127.0.0.1:4010 -N & +open http://127.0.0.1:4010 +``` + +A built-in "Connect to Server…" mode in the desktop app is in progress +(issue #26, phase 5). + ## Monaco Editor Monaco is bundled via esbuild using its ESM entry point (`monaco-editor/esm/vs/editor/editor.main.js`). The diff --git a/lib/bds/application.ex b/lib/bds/application.ex index 8b0ffd7..6818302 100644 --- a/lib/bds/application.ex +++ b/lib/bds/application.ex @@ -12,6 +12,10 @@ defmodule BDS.Application do [{BDS.Desktop.Server, []}, BDS.CliSync.Watcher, BDS.Server] end + # Local TUI: the headless server plus a TUI on the launching terminal, so + # a GUI (via tunnel) and the local terminal can work in parallel. + def mode_children(:tui, env), do: mode_children(:server, env) ++ [{BDS.TUI, []}] + def mode_children(:desktop, env), do: desktop_children(env) def desktop_children(env \\ nil) diff --git a/lib/bds/server.ex b/lib/bds/server.ex index a5e1a00..5df8cc6 100644 --- a/lib/bds/server.ex +++ b/lib/bds/server.ex @@ -11,14 +11,19 @@ defmodule BDS.Server do """ @doc """ - Resolves the boot mode from the `BDS_MODE` environment variable. - Anything other than `"server"` (case-insensitive) is desktop mode. + Resolves the boot mode from the `BDS_MODE` environment variable: + `"server"` (headless + SSH daemon), `"tui"` (headless + SSH daemon + + a TUI attached to the launching terminal), anything else is desktop. """ - @spec mode(String.t() | nil) :: :desktop | :server + @spec mode(String.t() | nil) :: :desktop | :server | :tui def mode(value \\ System.get_env("BDS_MODE")) def mode(value) when is_binary(value) do - if String.downcase(value) == "server", do: :server, else: :desktop + case String.downcase(value) do + "server" -> :server + "tui" -> :tui + _other -> :desktop + end end def mode(nil), do: :desktop diff --git a/lib/bds/tui.ex b/lib/bds/tui.ex index c9a3a5f..149d6a2 100644 --- a/lib/bds/tui.ex +++ b/lib/bds/tui.ex @@ -1,38 +1,593 @@ defmodule BDS.TUI do @moduledoc """ - Terminal UI entry point (issue #26), served per SSH connection by - `ExRatatui.SSH.Daemon` in server mode and runnable locally. + Terminal UI shell (issue #26, phase 4). - Placeholder shell for now: renders the app banner while the workbench - renderer lands in a later phase. The UI locale is the server-side UI - language setting, same as the GUI. + An `ExRatatui.App` over the shared UI core: `BDS.UI.Sidebar` for view + data, `BDS.UI.PostEditor.*` for the draft/save/publish workflow, and + `BDS.Events` for multi-client synchronization. Served per SSH connection + by `ExRatatui.SSH.Daemon` in server mode and runnable locally. + + ## Keys + + Sidebar focus: `↑/↓`/`j/k` navigate, `enter` open, `n` new post, + `1..5` switch view (posts/media/templates/scripts/tags), `r` refresh. + Editor focus: text keys edit, `ctrl+s` save, `ctrl+p` publish, + `ctrl+t` edit title, `ctrl+l` cycle language, `ctrl+g` AI suggestions, + `esc` back to sidebar. `ctrl+q` quits, `esc` also closes a media + preview. The UI locale is the server-side UI language setting. """ use ExRatatui.App use Gettext, backend: BDS.Gettext + alias BDS.AI alias BDS.Desktop.UILocale + alias BDS.Events + alias BDS.Posts + alias BDS.Projects + alias BDS.UI.PostEditor.{Draft, Metadata, Persistence} + alias BDS.UI.Sidebar + alias ExRatatui.Layout alias ExRatatui.Layout.Rect - alias ExRatatui.Widgets.Paragraph + alias ExRatatui.Style + alias ExRatatui.Widgets.{Block, List, Paragraph, Tabs, Textarea} + + @views ~w(posts media templates scripts tags) + + # ── Mount ──────────────────────────────────────────────────────────────── @impl true def mount(_opts) do UILocale.put(BDS.Desktop.ShellData.ui_language()) - {:ok, %{}} + :ok = Events.subscribe() + + project_id = Projects.shell_snapshot().active_project_id + + state = %{ + project_id: project_id, + view: "posts", + sidebar: nil, + items: [], + selected: 0, + focus: :sidebar, + editor: nil, + preview: nil, + image: nil, + busy: false, + status: nil + } + + {:ok, load_sidebar(state)} end - @impl true - def render(_state, frame) do - text = - "bDS2 — " <> - dgettext("ui", "Blogging Desktop Server") <> - "\n\n" <> - dgettext("ui", "Press q to quit.") - - [{%Paragraph{text: text}, %Rect{x: 0, y: 0, width: frame.width, height: frame.height}}] - end + # ── Events: global keys ────────────────────────────────────────────────── @impl true - def handle_event(%ExRatatui.Event.Key{code: "q"}, state), do: {:stop, state} + def handle_event(%ExRatatui.Event.Key{code: "q", modifiers: ["ctrl"]}, state), + do: {:stop, state} + + def handle_event(%ExRatatui.Event.Key{code: "esc"}, %{image: image} = state) + when image != nil, + do: {:noreply, %{state | image: nil}} + + def handle_event(%ExRatatui.Event.Key{kind: "press"} = key, %{focus: :sidebar} = state), + do: sidebar_key(key, state) + + def handle_event(%ExRatatui.Event.Key{kind: "press"} = key, %{focus: :editor} = state), + do: editor_key(key, state) + def handle_event(_event, state), do: {:noreply, state} + + # ── Events: sidebar focus ──────────────────────────────────────────────── + + defp sidebar_key(%{code: code}, state) when code in ["down", "j"], + do: {:noreply, move_selection(state, 1)} + + defp sidebar_key(%{code: code}, state) when code in ["up", "k"], + do: {:noreply, move_selection(state, -1)} + + defp sidebar_key(%{code: code}, state) when code in ~w(1 2 3 4 5) do + view = Enum.at(@views, String.to_integer(code) - 1) + {:noreply, load_sidebar(%{state | view: view, selected: 0, image: nil})} + end + + defp sidebar_key(%{code: "r"}, state), do: {:noreply, load_sidebar(state)} + + defp sidebar_key(%{code: "n"}, %{project_id: project_id} = state) + when is_binary(project_id) do + case Posts.create_post(%{project_id: project_id, title: "", content: ""}) do + {:ok, post} -> + {:noreply, state |> load_sidebar() |> open_post(post.id)} + + {:error, reason} -> + {:noreply, toast(state, dgettext("ui", "New Post"), inspect(reason))} + end + end + + defp sidebar_key(%{code: "enter"}, state) do + case selected_item(state) do + %{route: "post", id: post_id} -> {:noreply, open_post(state, post_id)} + %{route: "media", id: media_id} -> {:noreply, open_media(state, media_id)} + %{id: _id} -> {:noreply, toast(state, view_label(state.view), dgettext("ui", "Editing this item is not available in the terminal UI yet."))} + _other -> {:noreply, state} + end + end + + defp sidebar_key(%{code: "tab"}, %{editor: editor} = state) when editor != nil, + do: {:noreply, %{state | focus: :editor}} + + defp sidebar_key(_key, state), do: {:noreply, state} + + # ── Events: editor focus ───────────────────────────────────────────────── + + defp editor_key(%{code: "esc"}, %{editor: %{editing_title?: true}} = state), + do: {:noreply, put_in(state.editor.editing_title?, false)} + + defp editor_key(%{code: "esc"}, state), do: {:noreply, %{state | focus: :sidebar}} + + defp editor_key(%{code: "s", modifiers: ["ctrl"]}, state), do: {:noreply, persist(state, :save)} + + defp editor_key(%{code: "p", modifiers: ["ctrl"]}, state), + do: {:noreply, persist(state, :publish)} + + defp editor_key(%{code: "t", modifiers: ["ctrl"]}, state), + do: {:noreply, update_in(state.editor.editing_title?, &(not &1))} + + defp editor_key(%{code: "l", modifiers: ["ctrl"]}, state), do: {:noreply, cycle_language(state)} + + defp editor_key(%{code: "g", modifiers: ["ctrl"]}, state), + do: {:noreply, request_ai_suggestions(state)} + + defp editor_key(key, %{editor: %{editing_title?: true}} = state), + do: {:noreply, title_key(key, state)} + + defp editor_key(%{code: code, modifiers: modifiers}, %{editor: editor} = state) do + :ok = ExRatatui.textarea_handle_key(editor.textarea, code, modifiers) + dirty = editor.dirty or code not in ~w(up down left right home end pageup pagedown) + {:noreply, put_in(state.editor.dirty, dirty)} + end + + defp title_key(%{code: "enter"}, state), do: put_in(state.editor.editing_title?, false) + + defp title_key(%{code: "backspace"}, state), + do: state |> update_in([:editor, :title], &String.slice(&1, 0..-2//1)) |> mark_dirty() + + defp title_key(%{code: code, modifiers: modifiers}, state) + when byte_size(code) >= 1 and modifiers == [] do + if String.length(code) == 1 do + state |> update_in([:editor, :title], &(&1 <> code)) |> mark_dirty() + else + state + end + end + + defp title_key(_key, state), do: state + + defp mark_dirty(state), do: put_in(state.editor.dirty, true) + + # ── Info: event bus + AI results ───────────────────────────────────────── + + @impl true + def handle_info({:entity_changed, %{entity: entity, entity_id: id, action: action}}, state) do + state = load_sidebar(state) + + state = + if state.editor && state.editor.post.id == id && entity == "post" do + if action == :deleted, do: %{state | editor: nil, focus: :sidebar}, else: state + else + state + end + + {:noreply, state} + end + + def handle_info({:settings_changed, "ui.language"}, state) do + UILocale.put(BDS.Desktop.ShellData.ui_language()) + {:noreply, load_sidebar(state)} + end + + def handle_info({:ai_suggestions_result, :post, post_id, result}, state) do + state = %{state | busy: false} + + if state.editor && state.editor.post.id == post_id do + attrs = + %{} + |> maybe_put_suggestion(:title, Map.get(result, :title)) + |> maybe_put_suggestion(:excerpt, Map.get(result, :excerpt)) + + case attrs == %{} || Posts.update_post(post_id, attrs) do + {:ok, _post} -> + {:noreply, + state + |> reload_editor() + |> toast(dgettext("ui", "AI Suggestions"), dgettext("ui", "Suggestions applied."))} + + true -> + {:noreply, toast(state, dgettext("ui", "AI Suggestions"), dgettext("ui", "No suggestions returned."))} + + {:error, reason} -> + {:noreply, toast(state, dgettext("ui", "AI Suggestions"), inspect(reason))} + end + else + {:noreply, state} + end + end + + def handle_info({:ai_suggestions_error, :post, _post_id, reason}, state) do + {:noreply, toast(%{state | busy: false}, dgettext("ui", "AI Suggestions"), inspect(reason))} + end + + def handle_info(_message, state), do: {:noreply, state} + + # ── Render ─────────────────────────────────────────────────────────────── + + @impl true + def render(state, frame) do + area = %Rect{x: 0, y: 0, width: frame.width, height: frame.height} + [tabs_rect, body_rect, status_rect] = Layout.split(area, :vertical, [ + {:length, 1}, + {:min, 3}, + {:length, 1} + ]) + + [sidebar_rect, main_rect] = Layout.split(body_rect, :horizontal, [ + {:length, min(40, div(frame.width, 3))}, + {:min, 20} + ]) + + view_tabs(state, tabs_rect) ++ + sidebar_widgets(state, sidebar_rect) ++ + main_widgets(state, main_rect) ++ + status_widgets(state, status_rect) ++ + image_widgets(state, body_rect) + end + + defp view_tabs(state, rect) do + [ + {%Tabs{ + titles: Enum.map(@views, &view_label/1), + selected: Enum.find_index(@views, &(&1 == state.view)), + highlight_style: %Style{fg: :cyan, modifiers: [:bold]} + }, rect} + ] + end + + defp sidebar_widgets(state, rect) do + items = + Enum.map(state.items, fn + {:header, title} -> "── #{title}" + {:item, item} -> " #{item_label(item)}" + end) + + focused? = state.focus == :sidebar + + [ + {%List{ + items: items, + selected: state.selected, + highlight_style: + if(focused?, + do: %Style{fg: :black, bg: :cyan}, + else: %Style{modifiers: [:bold]} + ), + block: %Block{title: view_label(state.view), borders: [:all]} + }, rect} + ] + end + + defp main_widgets(%{editor: nil} = state, rect) do + text = + case selected_item(state) do + %{route: "media"} = item -> + dgettext("ui", "Press enter to preview %{name}.", name: item_label(item)) + + _other -> + dgettext("ui", "Select an entry and press enter to open it.") + end + + [{%Paragraph{text: text, block: %Block{title: "bDS2", borders: [:all]}}, rect}] + end + + defp main_widgets(%{editor: editor} = state, rect) do + [title_rect, body_rect] = Layout.split(rect, :vertical, [{:length, 3}, {:min, 3}]) + + title_block_label = + if editor.editing_title?, + do: dgettext("ui", "Title (editing — enter to confirm)"), + else: dgettext("ui", "Title (ctrl+t to edit)") + + title_widget = + {%Paragraph{ + text: editor.title, + block: %Block{title: title_block_label, borders: [:all]}, + style: + if(editor.editing_title?, do: %Style{fg: :yellow}, else: %Style{}) + }, title_rect} + + body_title = + "#{dgettext("ui", "Content")} [#{editor.language}]" <> + if(editor.dirty, do: " •", else: "") + + body_widget = + {%Textarea{ + state: editor.textarea, + block: %Block{title: body_title, borders: [:all]}, + cursor_style: + if(state.focus == :editor and not editor.editing_title?, + do: %Style{bg: :cyan}, + else: %Style{} + ) + }, body_rect} + + [title_widget, body_widget] + end + + defp status_widgets(state, rect) do + text = + cond do + state.busy -> dgettext("ui", "Working…") + state.status -> state.status + true -> default_status(state) + end + + [{%Paragraph{text: text, style: %Style{fg: :dark_gray}}, rect}] + end + + defp image_widgets(%{image: nil}, _rect), do: [] + + defp image_widgets(%{image: %{widget: widget, title: title}}, rect) do + overlay = %Rect{ + x: rect.x + 2, + y: rect.y + 1, + width: max(rect.width - 4, 10), + height: max(rect.height - 2, 5) + } + + [inner] = Layout.split(overlay, :vertical, [{:min, 3}]) + + [ + {%Block{title: title <> " — " <> dgettext("ui", "esc to close"), borders: [:all]}, overlay}, + {widget, + %Rect{x: inner.x + 1, y: inner.y + 1, width: inner.width - 2, height: inner.height - 2}} + ] + end + + defp default_status(%{focus: :sidebar}), + do: + dgettext( + "ui", + "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" + ) + + defp default_status(%{focus: :editor}), + do: + dgettext( + "ui", + "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" + ) + + # ── Sidebar data ───────────────────────────────────────────────────────── + + defp load_sidebar(state) do + view = Sidebar.view(state.project_id, state.view) + items = flatten_items(view, state.view) + + selected = + case items do + [] -> 0 + _some -> seek_item(items, clamp(state.selected, length(items)), 1) + end + + %{state | sidebar: view, items: items, selected: selected} + end + + defp flatten_items(view, route) do + sections = + cond do + is_list(view[:sections]) -> view[:sections] + is_list(view[:items]) -> [%{title: nil, items: view[:items]}] + true -> [] + end + + Enum.flat_map(sections, fn section -> + headers = if section[:title], do: [{:header, section.title}], else: [] + + headers ++ + Enum.map(section[:items] || [], fn item -> + {:item, Map.put_new(item, :route, route_for(item, route))} + end) + end) + end + + defp route_for(item, route), do: item[:route] || String.trim_trailing(route, "s") + + defp move_selection(state, delta) do + count = length(state.items) + + if count == 0 do + state + else + next = seek_item(state.items, clamp(state.selected + delta, count), delta) + %{state | selected: next} + end + end + + # Selection skips section headers in the given direction; parks on the + # boundary when only headers remain that way. + defp seek_item(items, index, delta) do + case Enum.at(items, index) do + {:item, _} -> + index + + {:header, _} -> + next = clamp(index + sign(delta), length(items)) + if next == index, do: index, else: seek_item(items, next, delta) + + nil -> + index + end + end + + defp sign(delta) when delta < 0, do: -1 + defp sign(_delta), do: 1 + + defp clamp(value, count), do: value |> max(0) |> min(count - 1) + + defp selected_item(state) do + case Enum.at(state.items, state.selected) do + {:item, item} -> item + _other -> nil + end + end + + defp item_label(item) do + title = + item[:title] || item[:name] || item[:original_name] || item[:label] || item[:id] || "?" + + status = item[:status] + if status, do: "#{title} (#{status})", else: to_string(title) + end + + defp view_label("posts"), do: dgettext("ui", "Posts") + defp view_label("media"), do: dgettext("ui", "Media") + defp view_label("templates"), do: dgettext("ui", "Templates") + defp view_label("scripts"), do: dgettext("ui", "Scripts") + defp view_label("tags"), do: dgettext("ui", "Tags") + + # ── Post editor ────────────────────────────────────────────────────────── + + defp open_post(state, post_id) do + case Posts.get_post(post_id) do + nil -> + toast(state, dgettext("ui", "Posts"), dgettext("ui", "Post not found.")) + + post -> + metadata = Metadata.project_metadata(post.project_id) + language = Metadata.canonical_language(post, metadata) + %{state | editor: build_editor(post, metadata, language), focus: :editor, image: nil} + end + end + + defp build_editor(post, metadata, language) do + form = Draft.persisted_form(post, metadata, language) + textarea = ExRatatui.textarea_new() + :ok = ExRatatui.textarea_insert_str(textarea, form["content"] || "") + + %{ + post: post, + metadata: metadata, + language: language, + form: form, + title: form["title"] || "", + textarea: textarea, + editing_title?: false, + dirty: false + } + end + + defp reload_editor(%{editor: nil} = state), do: state + + defp reload_editor(%{editor: editor} = state) do + case Posts.get_post(editor.post.id) do + nil -> %{state | editor: nil, focus: :sidebar} + post -> %{state | editor: build_editor(post, editor.metadata, editor.language)} + end + end + + defp persist(%{editor: editor} = state, action) do + draft = %{ + editor.form + | "title" => editor.title, + "content" => ExRatatui.textarea_get_value(editor.textarea) + } + + case Persistence.persist(editor.post, draft, editor.language, editor.metadata, action) do + {:ok, _result} -> + message = + case action do + :publish -> dgettext("ui", "Published.") + _save -> dgettext("ui", "Saved.") + end + + state + |> reload_editor() + |> load_sidebar() + |> toast(editor_title(editor), message) + + {:error, reason} -> + toast(state, editor_title(editor), inspect(reason)) + end + end + + defp cycle_language(%{editor: editor} = state) do + if editor.dirty do + toast(state, editor_title(editor), dgettext("ui", "Save before switching languages.")) + else + languages = Metadata.languages(editor.metadata) + index = Enum.find_index(languages, &(&1 == editor.language)) || 0 + next = Enum.at(languages, rem(index + 1, length(languages))) + %{state | editor: build_editor(editor.post, editor.metadata, next)} + end + end + + defp request_ai_suggestions(%{editor: editor} = state) do + if AI.airplane_mode?() and not AI.airplane_endpoint_configured?() do + toast( + state, + dgettext("ui", "AI Suggestions"), + dgettext("ui", "AI is unavailable in airplane mode without a local endpoint.") + ) + else + parent = self() + post_id = editor.post.id + language = editor.metadata.main_language || "en" + + {:ok, _pid} = + Task.Supervisor.start_child(BDS.TCP.TaskSupervisor, fn -> + case AI.analyze_post(post_id, language: language) do + {:ok, result} -> send(parent, {:ai_suggestions_result, :post, post_id, result}) + {:error, reason} -> send(parent, {:ai_suggestions_error, :post, post_id, reason}) + end + end) + + %{state | busy: true} + end + end + + defp maybe_put_suggestion(attrs, _key, nil), do: attrs + + defp maybe_put_suggestion(attrs, key, value) do + case String.trim(to_string(value)) do + "" -> attrs + trimmed -> Map.put(attrs, key, trimmed) + end + end + + defp editor_title(editor), + do: Metadata.display_title(editor.title, editor.post.slug, editor.post.id) + + # ── Media preview ──────────────────────────────────────────────────────── + + defp open_media(state, media_id) do + with %{} = media <- BDS.Media.get_media(media_id), + true <- String.starts_with?(to_string(media.mime_type || ""), "image/"), + project when project != nil <- Projects.get_project(media.project_id), + path = Path.join(Projects.project_data_dir(project), media.file_path), + {:ok, bytes} <- File.read(path), + {:ok, widget} <- ExRatatui.Image.new(bytes) do + %{state | image: %{title: item_media_title(media), widget: widget}} + else + false -> + toast(state, dgettext("ui", "Media"), dgettext("ui", "Only images can be previewed.")) + + _other -> + toast(state, dgettext("ui", "Media"), dgettext("ui", "Could not load this file.")) + end + end + + defp item_media_title(media), do: media.title || media.original_name || media.id + + # ── Status line ────────────────────────────────────────────────────────── + + defp toast(state, title, message), do: %{state | status: "#{title}: #{message}"} end diff --git a/priv/gettext/de/LC_MESSAGES/ui.po b/priv/gettext/de/LC_MESSAGES/ui.po index adb7317..2cf5212 100644 --- a/priv/gettext/de/LC_MESSAGES/ui.po +++ b/priv/gettext/de/LC_MESSAGES/ui.po @@ -10,7 +10,7 @@ msgstr "%{canonical} = %{translation}" msgid "%{count} mapped" msgstr "%{count} zugeordnet" -#: lib/bds/desktop/shell_data.ex:171 +#: lib/bds/desktop/shell_data.ex:173 #, elixir-autogen, elixir-format msgid "%{count} post" msgid_plural "%{count} posts" @@ -81,6 +81,11 @@ msgstr "KI-Einstellungen" #: lib/bds/desktop/shell_live/overlay_manager.ex:72 #: lib/bds/desktop/shell_live/post_editor.ex:920 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 +#: lib/bds/tui.ex:203 +#: lib/bds/tui.ex:206 +#: lib/bds/tui.ex:209 +#: lib/bds/tui.ex:217 +#: lib/bds/tui.ex:537 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "KI-Vorschlaege" @@ -301,7 +306,7 @@ msgstr "Tag-Archive gerendert" msgid "Validation apply prepared" msgstr "Anwenden der Validierung vorbereitet" -#: lib/bds/desktop/shell_data.ex:164 +#: lib/bds/desktop/shell_data.ex:166 #: lib/bds/ui/sidebar.ex:331 #, elixir-autogen, elixir-format msgid "Archived" @@ -336,7 +341,7 @@ msgstr "Autor" msgid "Auto" msgstr "Automatisch" -#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:443 #: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:364 @@ -486,7 +491,7 @@ msgstr "Kategorie-Standards, Render-Flags und Template-Zuordnung" msgid "Category name is required" msgstr "Kategoriename ist erforderlich" -#: lib/bds/desktop/shell_live.ex:780 +#: lib/bds/desktop/shell_live.ex:789 #: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:323 @@ -584,6 +589,7 @@ msgstr "Bestaetigen" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 +#: lib/bds/tui.ex:308 #: lib/bds/ui/sidebar.ex:764 #, elixir-autogen, elixir-format msgid "Content" @@ -753,7 +759,7 @@ msgstr "Übersetzung löschen" msgid "Delete conversation" msgstr "Unterhaltung löschen" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:65 +#: lib/bds/ui/post_editor/persistence.ex:69 #, elixir-autogen, elixir-format msgid "Delete this unpublished draft" msgstr "Diesen unveröffentlichten Entwurf löschen" @@ -810,17 +816,17 @@ msgstr "Denkausgabe für das Offline-Chat-Modell deaktivieren" msgid "Disable reasoning output for the online chat model" msgstr "Denkausgabe für das Online-Chat-Modell deaktivieren" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:57 +#: lib/bds/ui/post_editor/persistence.ex:61 #, elixir-autogen, elixir-format msgid "Discard Changes" msgstr "Änderungen verwerfen" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:58 +#: lib/bds/ui/post_editor/persistence.ex:62 #, elixir-autogen, elixir-format msgid "Discard Draft" msgstr "Entwurf verwerfen" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:64 +#: lib/bds/ui/post_editor/persistence.ex:68 #, elixir-autogen, elixir-format msgid "Discard changes and restore the published version" msgstr "Änderungen verwerfen und veröffentlichte Version wiederherstellen" @@ -863,7 +869,7 @@ msgstr "Nicht übersetzen" msgid "Documentation" msgstr "Dokumentation" -#: lib/bds/desktop/shell_data.ex:162 +#: lib/bds/desktop/shell_data.ex:164 #, elixir-autogen, elixir-format msgid "Draft" msgstr "Entwurf" @@ -1063,7 +1069,7 @@ msgstr "Galerie" msgid "Generate Site" msgstr "Website generieren" -#: lib/bds/desktop/shell_live.ex:781 +#: lib/bds/desktop/shell_live.ex:790 #: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/ui/sidebar.ex:789 #, elixir-autogen, elixir-format @@ -1076,8 +1082,8 @@ msgstr "Git" msgid "Git Diff" msgstr "Git-Diff" -#: lib/bds/desktop/shell_data.ex:226 -#: lib/bds/desktop/shell_live.ex:777 +#: lib/bds/desktop/shell_data.ex:228 +#: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:171 #, elixir-autogen, elixir-format msgid "Git Log" @@ -1103,7 +1109,7 @@ msgstr "Startseite" msgid "Host" msgstr "Host" -#: lib/bds/desktop/shell_data.ex:98 +#: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 #: lib/bds/desktop/shell_live/post_editor.ex:1038 @@ -1200,9 +1206,9 @@ msgstr "Importdefinitionen" msgid "Import failed: %{error}" msgstr "Import fehlgeschlagen: %{error}" -#: lib/bds/desktop/shell_live.ex:605 -#: lib/bds/desktop/shell_live.ex:893 -#: lib/bds/desktop/shell_live.ex:899 +#: lib/bds/desktop/shell_live.ex:606 +#: lib/bds/desktop/shell_live.ex:902 +#: lib/bds/desktop/shell_live.ex:908 #: lib/bds/desktop/shell_live/sidebar_create.ex:47 #, elixir-autogen, elixir-format msgid "Import media" @@ -1337,7 +1343,7 @@ msgstr "MIME-Typ" msgid "Macros (%{count})" msgstr "Makros (%{count})" -#: lib/bds/desktop/shell_data.ex:101 +#: lib/bds/desktop/shell_data.ex:103 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:55 #, elixir-autogen, elixir-format msgid "Main Language" @@ -1379,6 +1385,9 @@ msgstr "Maximale Beiträge pro Seite" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 +#: lib/bds/tui.ex:452 +#: lib/bds/tui.ex:581 +#: lib/bds/tui.ex:584 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:558 @@ -1431,7 +1440,7 @@ msgstr "Metadaten-Diff" msgid "Missing URLs" msgstr "Fehlende URLs" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #, elixir-autogen, elixir-format msgid "Mode" msgstr "Modus" @@ -1466,6 +1475,7 @@ msgstr "Neue Seite" #: lib/bds/desktop/menu_bar.ex:185 #: lib/bds/desktop/shell_live/sidebar_create.ex:41 +#: lib/bds/tui.ex:103 #, elixir-autogen, elixir-format msgid "New Post" msgstr "Neuer Beitrag" @@ -1644,7 +1654,7 @@ msgstr "In der Neufassung noch nicht unterstützt" msgid "Nothing to Import" msgstr "Nichts zu importieren" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #: lib/bds/desktop/shell_live/index.html.heex:503 #, elixir-autogen, elixir-format msgid "Offline" @@ -1784,7 +1794,7 @@ msgstr "Sonstige" msgid "Other (%{count})" msgstr "Andere (%{count})" -#: lib/bds/desktop/shell_live.ex:776 +#: lib/bds/desktop/shell_live.ex:785 #: lib/bds/desktop/shell_live/panel_renderer.ex:83 #, elixir-autogen, elixir-format msgid "Output" @@ -1866,7 +1876,7 @@ msgstr "Die erkannte Sprache für dieses Medium speichern" msgid "Post" msgstr "Beitrag" -#: lib/bds/desktop/shell_data.ex:229 +#: lib/bds/desktop/shell_data.ex:231 #: lib/bds/desktop/shell_live/panel_renderer.ex:118 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:310 #, elixir-autogen, elixir-format @@ -1901,6 +1911,8 @@ msgstr "Beitrag gespeichert" #: lib/bds/desktop/menu_bar.ex:202 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 +#: lib/bds/tui.ex:451 +#: lib/bds/tui.ex:462 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1985,7 +1997,7 @@ msgstr "Veröffentlichen" msgid "Publish Selected" msgstr "Ausgewähltes veröffentlichen" -#: lib/bds/desktop/shell_data.ex:163 +#: lib/bds/desktop/shell_data.ex:165 #: lib/bds/desktop/shell_live/post_editor.ex:1036 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 @@ -2282,6 +2294,7 @@ msgstr "Scripting-Funktionen werden in der Neufassung auf Anwendungsebene konfig #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 +#: lib/bds/tui.ex:454 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2456,7 +2469,7 @@ msgstr "Chat starten" msgid "Starting..." msgstr "Starte..." -#: lib/bds/desktop/shell_data.ex:97 +#: lib/bds/desktop/shell_data.ex:99 #: lib/bds/desktop/shell_live/import_editor.ex:1182 #: lib/bds/desktop/shell_live/import_editor.ex:1233 #, elixir-autogen, elixir-format @@ -2529,6 +2542,7 @@ msgstr "Schlagwortname" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 +#: lib/bds/tui.ex:455 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2538,7 +2552,7 @@ msgstr "Schlagwortname" msgid "Tags" msgstr "Tags" -#: lib/bds/desktop/shell_live.ex:775 +#: lib/bds/desktop/shell_live.ex:784 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 #, elixir-autogen, elixir-format msgid "Tasks" @@ -2584,6 +2598,7 @@ msgstr "Template-Syntax ist gültig" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 +#: lib/bds/tui.ex:453 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2780,7 +2795,7 @@ msgid "Unsaved" msgstr "Nicht gespeichert" #: lib/bds/desktop/shell_live/import_editor.ex:871 -#: lib/bds/desktop/shell_live/post_editor/post_metadata.ex:166 +#: lib/bds/ui/post_editor/metadata.ex:166 #: lib/bds/ui/sidebar.ex:1108 #, elixir-autogen, elixir-format msgid "Untitled" @@ -3161,9 +3176,9 @@ msgid "I can help you manage your blog with rich visualizations. Try asking me t msgstr "Ich kann dir mit interaktiven Visualisierungen bei deinem Blog helfen. Frag mich zum Beispiel nach:" #: lib/bds/desktop/shell_live/index.html.heex:289 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Media Files" -msgstr "Medien" +msgstr "Mediendateien" #: lib/bds/desktop/shell_live/chat_editor_html/chat_editor.html.heex:21 #, elixir-autogen, elixir-format @@ -3218,12 +3233,12 @@ msgstr "Willkommen beim KI-Assistenten" msgid "Comparing database and filesystem metadata" msgstr "Vergleicht Datenbank- und Dateisystem-Metadaten" -#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:682 #, elixir-autogen, elixir-format msgid "Added %{count} images to post" msgstr "%{count} Bilder zum Beitrag hinzugefügt" -#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:650 #, elixir-autogen, elixir-format msgid "Added %{title}" msgstr "%{title} hinzugefügt" @@ -3243,18 +3258,18 @@ msgstr "Endbenutzer-Anleitung für redaktionelle Arbeitsabläufe, Medien, Vorlag msgid "Image Import Concurrency" msgstr "Gleichzeitige Bildimporte" -#: lib/bds/desktop/shell_live.ex:441 -#: lib/bds/desktop/shell_live.ex:454 -#: lib/bds/desktop/shell_live.ex:648 -#: lib/bds/desktop/shell_live.ex:680 -#: lib/bds/desktop/shell_live.ex:691 -#: lib/bds/desktop/shell_live.ex:702 +#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:455 +#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:692 +#: lib/bds/desktop/shell_live.ex:703 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #, elixir-autogen, elixir-format msgid "Add Gallery Images" msgstr "Galerie-Bilder hinzufügen" -#: lib/bds/desktop/shell_live.ex:703 +#: lib/bds/desktop/shell_live.ex:704 #, elixir-autogen, elixir-format msgid "Failed to process %{path}: %{reason}" msgstr "%{path} konnte nicht verarbeitet werden: %{reason}" @@ -3445,12 +3460,12 @@ msgstr "umbenannt" msgid "untracked" msgstr "nicht verfolgt" -#: lib/bds/desktop/shell_live.ex:801 +#: lib/bds/desktop/shell_live.ex:810 #, elixir-autogen, elixir-format msgid "Blogmark" msgstr "Blogmark" -#: lib/bds/desktop/shell_live.ex:844 +#: lib/bds/desktop/shell_live.ex:853 #, elixir-autogen, elixir-format msgid "Open a project before importing a blogmark." msgstr "Öffnen Sie ein Projekt, bevor Sie ein Blogmark importieren." @@ -3482,16 +3497,16 @@ msgid "Copy Bookmarklet to Clipboard" msgstr "Bookmarklet in die Zwischenablage kopieren" #: lib/bds/desktop/shell_live/overlay_manager.ex:300 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Delete Tag" -msgstr "Loeschen" +msgstr "Tag löschen" #: lib/bds/desktop/shell_live/settings_editor.ex:66 #, elixir-autogen, elixir-format msgid "Failed to copy bookmarklet to clipboard" msgstr "Bookmarklet konnte nicht in die Zwischenablage kopiert werden" -#: lib/bds/desktop/shell_live.ex:813 +#: lib/bds/desktop/shell_live.ex:822 #, elixir-autogen, elixir-format msgid "The project this blogmark targets does not exist here." msgstr "Das Projekt, auf das dieses Blogmark verweist, existiert hier nicht." @@ -3598,12 +3613,92 @@ msgstr "OK" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "Der Endpunkt hat keine Modelle zurückgegeben. Sie können einen Modellnamen weiterhin manuell eingeben." -#: lib/bds/tui.ex:28 +#: lib/bds/tui.ex:538 #, elixir-autogen, elixir-format -msgid "Blogging Desktop Server" -msgstr "Blogging Desktop Server" +msgid "AI is unavailable in airplane mode without a local endpoint." +msgstr "KI ist im Flugmodus ohne lokalen Endpunkt nicht verfügbar." -#: lib/bds/tui.ex:30 +#: lib/bds/tui.ex:584 #, elixir-autogen, elixir-format -msgid "Press q to quit." -msgstr "Drücken Sie q zum Beenden." +msgid "Could not load this file." +msgstr "Diese Datei konnte nicht geladen werden." + +#: lib/bds/tui.ex:111 +#, elixir-autogen, elixir-format +msgid "Editing this item is not available in the terminal UI yet." +msgstr "Die Bearbeitung dieses Eintrags ist in der Terminal-Oberfläche noch nicht verfügbar." + +#: lib/bds/tui.ex:206 +#, elixir-autogen, elixir-format +msgid "No suggestions returned." +msgstr "Keine Vorschläge erhalten." + +#: lib/bds/tui.ex:581 +#, elixir-autogen, elixir-format +msgid "Only images can be previewed." +msgstr "Nur Bilder können in der Vorschau angezeigt werden." + +#: lib/bds/tui.ex:462 +#, elixir-autogen, elixir-format +msgid "Post not found." +msgstr "Beitrag nicht gefunden." + +#: lib/bds/tui.ex:282 +#, elixir-autogen, elixir-format +msgid "Press enter to preview %{name}." +msgstr "Drücken Sie Enter, um %{name} in der Vorschau anzuzeigen." + +#: lib/bds/tui.ex:508 +#, elixir-autogen, elixir-format +msgid "Published." +msgstr "Veröffentlicht." + +#: lib/bds/tui.ex:524 +#, elixir-autogen, elixir-format +msgid "Save before switching languages." +msgstr "Speichern Sie vor dem Sprachwechsel." + +#: lib/bds/tui.ex:509 +#, elixir-autogen, elixir-format +msgid "Saved." +msgstr "Gespeichert." + +#: lib/bds/tui.ex:285 +#, elixir-autogen, elixir-format +msgid "Select an entry and press enter to open it." +msgstr "Wählen Sie einen Eintrag aus und öffnen Sie ihn mit Enter." + +#: lib/bds/tui.ex:203 +#, elixir-autogen, elixir-format +msgid "Suggestions applied." +msgstr "Vorschläge übernommen." + +#: lib/bds/tui.ex:297 +#, elixir-autogen, elixir-format +msgid "Title (ctrl+t to edit)" +msgstr "Titel (Strg+T zum Bearbeiten)" + +#: lib/bds/tui.ex:296 +#, elixir-autogen, elixir-format +msgid "Title (editing — enter to confirm)" +msgstr "Titel (Bearbeitung — Enter zum Bestätigen)" + +#: lib/bds/tui.ex:328 +#, elixir-autogen, elixir-format +msgid "Working…" +msgstr "Wird bearbeitet…" + +#: lib/bds/tui.ex:364 +#, elixir-autogen, elixir-format +msgid "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" +msgstr "Strg+S Speichern · Strg+P Veröffentlichen · Strg+T Titel · Strg+L Sprache · Strg+G KI · Esc Zurück" + +#: lib/bds/tui.ex:357 +#, elixir-autogen, elixir-format +msgid "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" +msgstr "Enter Öffnen · N Neuer Beitrag · 1-5 Ansichten · R Aktualisieren · Strg+Q Beenden" + +#: lib/bds/tui.ex:349 +#, elixir-autogen, elixir-format +msgid "esc to close" +msgstr "Esc zum Schließen" diff --git a/priv/gettext/en/LC_MESSAGES/ui.po b/priv/gettext/en/LC_MESSAGES/ui.po index c99d896..0324374 100644 --- a/priv/gettext/en/LC_MESSAGES/ui.po +++ b/priv/gettext/en/LC_MESSAGES/ui.po @@ -10,7 +10,7 @@ msgstr "" msgid "%{count} mapped" msgstr "" -#: lib/bds/desktop/shell_data.ex:171 +#: lib/bds/desktop/shell_data.ex:173 #, elixir-autogen, elixir-format msgid "%{count} post" msgid_plural "%{count} posts" @@ -81,6 +81,11 @@ msgstr "" #: lib/bds/desktop/shell_live/overlay_manager.ex:72 #: lib/bds/desktop/shell_live/post_editor.ex:920 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 +#: lib/bds/tui.ex:203 +#: lib/bds/tui.ex:206 +#: lib/bds/tui.ex:209 +#: lib/bds/tui.ex:217 +#: lib/bds/tui.ex:537 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "" @@ -301,7 +306,7 @@ msgstr "" msgid "Validation apply prepared" msgstr "" -#: lib/bds/desktop/shell_data.ex:164 +#: lib/bds/desktop/shell_data.ex:166 #: lib/bds/ui/sidebar.ex:331 #, elixir-autogen, elixir-format msgid "Archived" @@ -336,7 +341,7 @@ msgstr "" msgid "Auto" msgstr "" -#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:443 #: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:364 @@ -486,7 +491,7 @@ msgstr "" msgid "Category name is required" msgstr "" -#: lib/bds/desktop/shell_live.ex:780 +#: lib/bds/desktop/shell_live.ex:789 #: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:323 @@ -584,6 +589,7 @@ msgstr "" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 +#: lib/bds/tui.ex:308 #: lib/bds/ui/sidebar.ex:764 #, elixir-autogen, elixir-format msgid "Content" @@ -753,7 +759,7 @@ msgstr "" msgid "Delete conversation" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:65 +#: lib/bds/ui/post_editor/persistence.ex:69 #, elixir-autogen, elixir-format msgid "Delete this unpublished draft" msgstr "" @@ -810,17 +816,17 @@ msgstr "" msgid "Disable reasoning output for the online chat model" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:57 +#: lib/bds/ui/post_editor/persistence.ex:61 #, elixir-autogen, elixir-format msgid "Discard Changes" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:58 +#: lib/bds/ui/post_editor/persistence.ex:62 #, elixir-autogen, elixir-format msgid "Discard Draft" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:64 +#: lib/bds/ui/post_editor/persistence.ex:68 #, elixir-autogen, elixir-format msgid "Discard changes and restore the published version" msgstr "" @@ -863,7 +869,7 @@ msgstr "" msgid "Documentation" msgstr "" -#: lib/bds/desktop/shell_data.ex:162 +#: lib/bds/desktop/shell_data.ex:164 #, elixir-autogen, elixir-format msgid "Draft" msgstr "" @@ -1063,7 +1069,7 @@ msgstr "" msgid "Generate Site" msgstr "" -#: lib/bds/desktop/shell_live.ex:781 +#: lib/bds/desktop/shell_live.ex:790 #: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/ui/sidebar.ex:789 #, elixir-autogen, elixir-format @@ -1076,8 +1082,8 @@ msgstr "" msgid "Git Diff" msgstr "" -#: lib/bds/desktop/shell_data.ex:226 -#: lib/bds/desktop/shell_live.ex:777 +#: lib/bds/desktop/shell_data.ex:228 +#: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:171 #, elixir-autogen, elixir-format msgid "Git Log" @@ -1103,7 +1109,7 @@ msgstr "" msgid "Host" msgstr "" -#: lib/bds/desktop/shell_data.ex:98 +#: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 #: lib/bds/desktop/shell_live/post_editor.ex:1038 @@ -1200,9 +1206,9 @@ msgstr "" msgid "Import failed: %{error}" msgstr "" -#: lib/bds/desktop/shell_live.ex:605 -#: lib/bds/desktop/shell_live.ex:893 -#: lib/bds/desktop/shell_live.ex:899 +#: lib/bds/desktop/shell_live.ex:606 +#: lib/bds/desktop/shell_live.ex:902 +#: lib/bds/desktop/shell_live.ex:908 #: lib/bds/desktop/shell_live/sidebar_create.ex:47 #, elixir-autogen, elixir-format msgid "Import media" @@ -1337,7 +1343,7 @@ msgstr "" msgid "Macros (%{count})" msgstr "" -#: lib/bds/desktop/shell_data.ex:101 +#: lib/bds/desktop/shell_data.ex:103 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:55 #, elixir-autogen, elixir-format msgid "Main Language" @@ -1379,6 +1385,9 @@ msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 +#: lib/bds/tui.ex:452 +#: lib/bds/tui.ex:581 +#: lib/bds/tui.ex:584 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:558 @@ -1431,7 +1440,7 @@ msgstr "" msgid "Missing URLs" msgstr "" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #, elixir-autogen, elixir-format msgid "Mode" msgstr "" @@ -1466,6 +1475,7 @@ msgstr "" #: lib/bds/desktop/menu_bar.ex:185 #: lib/bds/desktop/shell_live/sidebar_create.ex:41 +#: lib/bds/tui.ex:103 #, elixir-autogen, elixir-format msgid "New Post" msgstr "" @@ -1644,7 +1654,7 @@ msgstr "" msgid "Nothing to Import" msgstr "" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #: lib/bds/desktop/shell_live/index.html.heex:503 #, elixir-autogen, elixir-format msgid "Offline" @@ -1784,7 +1794,7 @@ msgstr "" msgid "Other (%{count})" msgstr "" -#: lib/bds/desktop/shell_live.ex:776 +#: lib/bds/desktop/shell_live.ex:785 #: lib/bds/desktop/shell_live/panel_renderer.ex:83 #, elixir-autogen, elixir-format msgid "Output" @@ -1866,7 +1876,7 @@ msgstr "" msgid "Post" msgstr "" -#: lib/bds/desktop/shell_data.ex:229 +#: lib/bds/desktop/shell_data.ex:231 #: lib/bds/desktop/shell_live/panel_renderer.ex:118 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:310 #, elixir-autogen, elixir-format @@ -1901,6 +1911,8 @@ msgstr "" #: lib/bds/desktop/menu_bar.ex:202 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 +#: lib/bds/tui.ex:451 +#: lib/bds/tui.ex:462 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1985,7 +1997,7 @@ msgstr "" msgid "Publish Selected" msgstr "" -#: lib/bds/desktop/shell_data.ex:163 +#: lib/bds/desktop/shell_data.ex:165 #: lib/bds/desktop/shell_live/post_editor.ex:1036 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 @@ -2282,6 +2294,7 @@ msgstr "" #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 +#: lib/bds/tui.ex:454 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2456,7 +2469,7 @@ msgstr "" msgid "Starting..." msgstr "" -#: lib/bds/desktop/shell_data.ex:97 +#: lib/bds/desktop/shell_data.ex:99 #: lib/bds/desktop/shell_live/import_editor.ex:1182 #: lib/bds/desktop/shell_live/import_editor.ex:1233 #, elixir-autogen, elixir-format @@ -2529,6 +2542,7 @@ msgstr "" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 +#: lib/bds/tui.ex:455 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2538,7 +2552,7 @@ msgstr "" msgid "Tags" msgstr "" -#: lib/bds/desktop/shell_live.ex:775 +#: lib/bds/desktop/shell_live.ex:784 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 #, elixir-autogen, elixir-format msgid "Tasks" @@ -2584,6 +2598,7 @@ msgstr "" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 +#: lib/bds/tui.ex:453 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2780,7 +2795,7 @@ msgid "Unsaved" msgstr "" #: lib/bds/desktop/shell_live/import_editor.ex:871 -#: lib/bds/desktop/shell_live/post_editor/post_metadata.ex:166 +#: lib/bds/ui/post_editor/metadata.ex:166 #: lib/bds/ui/sidebar.ex:1108 #, elixir-autogen, elixir-format msgid "Untitled" @@ -3218,12 +3233,12 @@ msgstr "" msgid "Comparing database and filesystem metadata" msgstr "Comparing database and filesystem metadata" -#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:682 #, elixir-autogen, elixir-format msgid "Added %{count} images to post" msgstr "Added %{count} images to post" -#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:650 #, elixir-autogen, elixir-format msgid "Added %{title}" msgstr "Added %{title}" @@ -3243,18 +3258,18 @@ msgstr "" msgid "Image Import Concurrency" msgstr "Image Import Concurrency" -#: lib/bds/desktop/shell_live.ex:441 -#: lib/bds/desktop/shell_live.ex:454 -#: lib/bds/desktop/shell_live.ex:648 -#: lib/bds/desktop/shell_live.ex:680 -#: lib/bds/desktop/shell_live.ex:691 -#: lib/bds/desktop/shell_live.ex:702 +#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:455 +#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:692 +#: lib/bds/desktop/shell_live.ex:703 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #, elixir-autogen, elixir-format msgid "Add Gallery Images" msgstr "Add Gallery Images" -#: lib/bds/desktop/shell_live.ex:703 +#: lib/bds/desktop/shell_live.ex:704 #, elixir-autogen, elixir-format msgid "Failed to process %{path}: %{reason}" msgstr "Failed to process %{path}: %{reason}" @@ -3445,12 +3460,12 @@ msgstr "" msgid "untracked" msgstr "" -#: lib/bds/desktop/shell_live.ex:801 +#: lib/bds/desktop/shell_live.ex:810 #, elixir-autogen, elixir-format, fuzzy msgid "Blogmark" msgstr "Blogmark" -#: lib/bds/desktop/shell_live.ex:844 +#: lib/bds/desktop/shell_live.ex:853 #, elixir-autogen, elixir-format msgid "Open a project before importing a blogmark." msgstr "" @@ -3491,7 +3506,7 @@ msgstr "" msgid "Failed to copy bookmarklet to clipboard" msgstr "" -#: lib/bds/desktop/shell_live.ex:813 +#: lib/bds/desktop/shell_live.ex:822 #, elixir-autogen, elixir-format msgid "The project this blogmark targets does not exist here." msgstr "" @@ -3598,12 +3613,92 @@ msgstr "" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "" -#: lib/bds/tui.ex:28 +#: lib/bds/tui.ex:538 #, elixir-autogen, elixir-format -msgid "Blogging Desktop Server" +msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "" -#: lib/bds/tui.ex:30 -#, elixir-autogen, elixir-format -msgid "Press q to quit." +#: lib/bds/tui.ex:584 +#, elixir-autogen, elixir-format, fuzzy +msgid "Could not load this file." +msgstr "" + +#: lib/bds/tui.ex:111 +#, elixir-autogen, elixir-format +msgid "Editing this item is not available in the terminal UI yet." +msgstr "" + +#: lib/bds/tui.ex:206 +#, elixir-autogen, elixir-format +msgid "No suggestions returned." +msgstr "" + +#: lib/bds/tui.ex:581 +#, elixir-autogen, elixir-format +msgid "Only images can be previewed." +msgstr "" + +#: lib/bds/tui.ex:462 +#, elixir-autogen, elixir-format +msgid "Post not found." +msgstr "" + +#: lib/bds/tui.ex:282 +#, elixir-autogen, elixir-format +msgid "Press enter to preview %{name}." +msgstr "" + +#: lib/bds/tui.ex:508 +#, elixir-autogen, elixir-format, fuzzy +msgid "Published." +msgstr "" + +#: lib/bds/tui.ex:524 +#, elixir-autogen, elixir-format +msgid "Save before switching languages." +msgstr "" + +#: lib/bds/tui.ex:509 +#, elixir-autogen, elixir-format, fuzzy +msgid "Saved." +msgstr "" + +#: lib/bds/tui.ex:285 +#, elixir-autogen, elixir-format +msgid "Select an entry and press enter to open it." +msgstr "" + +#: lib/bds/tui.ex:203 +#, elixir-autogen, elixir-format +msgid "Suggestions applied." +msgstr "" + +#: lib/bds/tui.ex:297 +#, elixir-autogen, elixir-format +msgid "Title (ctrl+t to edit)" +msgstr "" + +#: lib/bds/tui.ex:296 +#, elixir-autogen, elixir-format +msgid "Title (editing — enter to confirm)" +msgstr "" + +#: lib/bds/tui.ex:328 +#, elixir-autogen, elixir-format, fuzzy +msgid "Working…" +msgstr "" + +#: lib/bds/tui.ex:364 +#, elixir-autogen, elixir-format +msgid "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" +msgstr "" + +#: lib/bds/tui.ex:357 +#, elixir-autogen, elixir-format +msgid "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" +msgstr "" + +#: lib/bds/tui.ex:349 +#, elixir-autogen, elixir-format +msgid "esc to close" msgstr "" diff --git a/priv/gettext/es/LC_MESSAGES/ui.po b/priv/gettext/es/LC_MESSAGES/ui.po index de822ba..c09e3f0 100644 --- a/priv/gettext/es/LC_MESSAGES/ui.po +++ b/priv/gettext/es/LC_MESSAGES/ui.po @@ -10,7 +10,7 @@ msgstr "%{canonical} = %{translation}" msgid "%{count} mapped" msgstr "%{count} mapeados" -#: lib/bds/desktop/shell_data.ex:171 +#: lib/bds/desktop/shell_data.ex:173 #, elixir-autogen, elixir-format msgid "%{count} post" msgid_plural "%{count} posts" @@ -81,6 +81,11 @@ msgstr "Configuración de IA" #: lib/bds/desktop/shell_live/overlay_manager.ex:72 #: lib/bds/desktop/shell_live/post_editor.ex:920 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 +#: lib/bds/tui.ex:203 +#: lib/bds/tui.ex:206 +#: lib/bds/tui.ex:209 +#: lib/bds/tui.ex:217 +#: lib/bds/tui.ex:537 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "Sugerencias de IA" @@ -301,7 +306,7 @@ msgstr "Archivos de etiquetas renderizados" msgid "Validation apply prepared" msgstr "Aplicación de validación preparada" -#: lib/bds/desktop/shell_data.ex:164 +#: lib/bds/desktop/shell_data.ex:166 #: lib/bds/ui/sidebar.ex:331 #, elixir-autogen, elixir-format msgid "Archived" @@ -336,7 +341,7 @@ msgstr "Autor" msgid "Auto" msgstr "Automático" -#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:443 #: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:364 @@ -486,7 +491,7 @@ msgstr "Valores predeterminados de categoría, opciones de renderizado y conexi msgid "Category name is required" msgstr "El nombre de la categoría es obligatorio" -#: lib/bds/desktop/shell_live.ex:780 +#: lib/bds/desktop/shell_live.ex:789 #: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:323 @@ -584,6 +589,7 @@ msgstr "Confirmar" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 +#: lib/bds/tui.ex:308 #: lib/bds/ui/sidebar.ex:764 #, elixir-autogen, elixir-format msgid "Content" @@ -753,7 +759,7 @@ msgstr "Eliminar traducción" msgid "Delete conversation" msgstr "Eliminar conversación" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:65 +#: lib/bds/ui/post_editor/persistence.ex:69 #, elixir-autogen, elixir-format msgid "Delete this unpublished draft" msgstr "Eliminar este borrador no publicado" @@ -810,17 +816,17 @@ msgstr "Desactivar la salida de razonamiento del modelo de chat sin conexión" msgid "Disable reasoning output for the online chat model" msgstr "Desactivar la salida de razonamiento del modelo de chat en línea" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:57 +#: lib/bds/ui/post_editor/persistence.ex:61 #, elixir-autogen, elixir-format msgid "Discard Changes" msgstr "Descartar cambios" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:58 +#: lib/bds/ui/post_editor/persistence.ex:62 #, elixir-autogen, elixir-format msgid "Discard Draft" msgstr "Descartar borrador" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:64 +#: lib/bds/ui/post_editor/persistence.ex:68 #, elixir-autogen, elixir-format msgid "Discard changes and restore the published version" msgstr "Descartar cambios y restaurar la versión publicada" @@ -863,7 +869,7 @@ msgstr "No traducir" msgid "Documentation" msgstr "Documentación" -#: lib/bds/desktop/shell_data.ex:162 +#: lib/bds/desktop/shell_data.ex:164 #, elixir-autogen, elixir-format msgid "Draft" msgstr "Borrador" @@ -1063,7 +1069,7 @@ msgstr "Galeria" msgid "Generate Site" msgstr "Generar sitio" -#: lib/bds/desktop/shell_live.ex:781 +#: lib/bds/desktop/shell_live.ex:790 #: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/ui/sidebar.ex:789 #, elixir-autogen, elixir-format @@ -1076,8 +1082,8 @@ msgstr "Git" msgid "Git Diff" msgstr "Diff de Git" -#: lib/bds/desktop/shell_data.ex:226 -#: lib/bds/desktop/shell_live.ex:777 +#: lib/bds/desktop/shell_data.ex:228 +#: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:171 #, elixir-autogen, elixir-format msgid "Git Log" @@ -1103,7 +1109,7 @@ msgstr "Inicio" msgid "Host" msgstr "Host" -#: lib/bds/desktop/shell_data.ex:98 +#: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 #: lib/bds/desktop/shell_live/post_editor.ex:1038 @@ -1200,9 +1206,9 @@ msgstr "Definiciones de importación" msgid "Import failed: %{error}" msgstr "La importación falló: %{error}" -#: lib/bds/desktop/shell_live.ex:605 -#: lib/bds/desktop/shell_live.ex:893 -#: lib/bds/desktop/shell_live.ex:899 +#: lib/bds/desktop/shell_live.ex:606 +#: lib/bds/desktop/shell_live.ex:902 +#: lib/bds/desktop/shell_live.ex:908 #: lib/bds/desktop/shell_live/sidebar_create.ex:47 #, elixir-autogen, elixir-format msgid "Import media" @@ -1337,7 +1343,7 @@ msgstr "Tipo MIME" msgid "Macros (%{count})" msgstr "Macros (%{count})" -#: lib/bds/desktop/shell_data.ex:101 +#: lib/bds/desktop/shell_data.ex:103 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:55 #, elixir-autogen, elixir-format msgid "Main Language" @@ -1379,6 +1385,9 @@ msgstr "Máximo de publicaciones por página" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 +#: lib/bds/tui.ex:452 +#: lib/bds/tui.ex:581 +#: lib/bds/tui.ex:584 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:558 @@ -1431,7 +1440,7 @@ msgstr "Diff de metadatos" msgid "Missing URLs" msgstr "URLs faltantes" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #, elixir-autogen, elixir-format msgid "Mode" msgstr "Modo" @@ -1466,6 +1475,7 @@ msgstr "Nueva página" #: lib/bds/desktop/menu_bar.ex:185 #: lib/bds/desktop/shell_live/sidebar_create.ex:41 +#: lib/bds/tui.ex:103 #, elixir-autogen, elixir-format msgid "New Post" msgstr "Nueva entrada" @@ -1644,7 +1654,7 @@ msgstr "Todavía no compatible en la reescritura" msgid "Nothing to Import" msgstr "Nada para importar" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #: lib/bds/desktop/shell_live/index.html.heex:503 #, elixir-autogen, elixir-format msgid "Offline" @@ -1784,7 +1794,7 @@ msgstr "Otros" msgid "Other (%{count})" msgstr "Otros (%{count})" -#: lib/bds/desktop/shell_live.ex:776 +#: lib/bds/desktop/shell_live.ex:785 #: lib/bds/desktop/shell_live/panel_renderer.ex:83 #, elixir-autogen, elixir-format msgid "Output" @@ -1866,7 +1876,7 @@ msgstr "Guardar el idioma detectado para este medio" msgid "Post" msgstr "Publicación" -#: lib/bds/desktop/shell_data.ex:229 +#: lib/bds/desktop/shell_data.ex:231 #: lib/bds/desktop/shell_live/panel_renderer.ex:118 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:310 #, elixir-autogen, elixir-format @@ -1901,6 +1911,8 @@ msgstr "Artículo guardado" #: lib/bds/desktop/menu_bar.ex:202 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 +#: lib/bds/tui.ex:451 +#: lib/bds/tui.ex:462 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1985,7 +1997,7 @@ msgstr "Publicar" msgid "Publish Selected" msgstr "Publicar seleccionados" -#: lib/bds/desktop/shell_data.ex:163 +#: lib/bds/desktop/shell_data.ex:165 #: lib/bds/desktop/shell_live/post_editor.ex:1036 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 @@ -2282,6 +2294,7 @@ msgstr "Las capacidades de scripts se configuran en la capa de aplicación en la #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 +#: lib/bds/tui.ex:454 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2456,7 +2469,7 @@ msgstr "Iniciar chat" msgid "Starting..." msgstr "Iniciando..." -#: lib/bds/desktop/shell_data.ex:97 +#: lib/bds/desktop/shell_data.ex:99 #: lib/bds/desktop/shell_live/import_editor.ex:1182 #: lib/bds/desktop/shell_live/import_editor.ex:1233 #, elixir-autogen, elixir-format @@ -2529,6 +2542,7 @@ msgstr "Nombre de la etiqueta" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 +#: lib/bds/tui.ex:455 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2538,7 +2552,7 @@ msgstr "Nombre de la etiqueta" msgid "Tags" msgstr "Etiquetas" -#: lib/bds/desktop/shell_live.ex:775 +#: lib/bds/desktop/shell_live.ex:784 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 #, elixir-autogen, elixir-format msgid "Tasks" @@ -2584,6 +2598,7 @@ msgstr "La sintaxis de la plantilla es válida" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 +#: lib/bds/tui.ex:453 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2780,7 +2795,7 @@ msgid "Unsaved" msgstr "Sin guardar" #: lib/bds/desktop/shell_live/import_editor.ex:871 -#: lib/bds/desktop/shell_live/post_editor/post_metadata.ex:166 +#: lib/bds/ui/post_editor/metadata.ex:166 #: lib/bds/ui/sidebar.ex:1108 #, elixir-autogen, elixir-format msgid "Untitled" @@ -3161,9 +3176,9 @@ msgid "I can help you manage your blog with rich visualizations. Try asking me t msgstr "Puedo ayudarte a gestionar tu blog con visualizaciones interactivas. Prueba a pedirme que:" #: lib/bds/desktop/shell_live/index.html.heex:289 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Media Files" -msgstr "Medios" +msgstr "Archivos multimedia" #: lib/bds/desktop/shell_live/chat_editor_html/chat_editor.html.heex:21 #, elixir-autogen, elixir-format @@ -3218,12 +3233,12 @@ msgstr "Bienvenido al asistente de IA" msgid "Comparing database and filesystem metadata" msgstr "Comparando metadatos de la base de datos y del sistema de archivos" -#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:682 #, elixir-autogen, elixir-format msgid "Added %{count} images to post" msgstr "%{count} imágenes añadidas a la publicación" -#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:650 #, elixir-autogen, elixir-format msgid "Added %{title}" msgstr "%{title} añadido" @@ -3243,18 +3258,18 @@ msgstr "Guía del usuario para flujos editoriales, medios, plantillas, traducci msgid "Image Import Concurrency" msgstr "Importación simultánea de imágenes" -#: lib/bds/desktop/shell_live.ex:441 -#: lib/bds/desktop/shell_live.ex:454 -#: lib/bds/desktop/shell_live.ex:648 -#: lib/bds/desktop/shell_live.ex:680 -#: lib/bds/desktop/shell_live.ex:691 -#: lib/bds/desktop/shell_live.ex:702 +#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:455 +#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:692 +#: lib/bds/desktop/shell_live.ex:703 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #, elixir-autogen, elixir-format msgid "Add Gallery Images" msgstr "Añadir imágenes a la galería" -#: lib/bds/desktop/shell_live.ex:703 +#: lib/bds/desktop/shell_live.ex:704 #, elixir-autogen, elixir-format msgid "Failed to process %{path}: %{reason}" msgstr "No se pudo procesar %{path}: %{reason}" @@ -3445,12 +3460,12 @@ msgstr "renombrado" msgid "untracked" msgstr "sin seguimiento" -#: lib/bds/desktop/shell_live.ex:801 +#: lib/bds/desktop/shell_live.ex:810 #, elixir-autogen, elixir-format msgid "Blogmark" msgstr "Blogmark" -#: lib/bds/desktop/shell_live.ex:844 +#: lib/bds/desktop/shell_live.ex:853 #, elixir-autogen, elixir-format msgid "Open a project before importing a blogmark." msgstr "Abre un proyecto antes de importar un blogmark." @@ -3482,16 +3497,16 @@ msgid "Copy Bookmarklet to Clipboard" msgstr "Copiar bookmarklet al portapapeles" #: lib/bds/desktop/shell_live/overlay_manager.ex:300 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Delete Tag" -msgstr "Eliminar" +msgstr "Eliminar etiqueta" #: lib/bds/desktop/shell_live/settings_editor.ex:66 #, elixir-autogen, elixir-format msgid "Failed to copy bookmarklet to clipboard" msgstr "Error al copiar el bookmarklet al portapapeles" -#: lib/bds/desktop/shell_live.ex:813 +#: lib/bds/desktop/shell_live.ex:822 #, elixir-autogen, elixir-format msgid "The project this blogmark targets does not exist here." msgstr "El proyecto al que apunta este blogmark no existe aquí." @@ -3598,12 +3613,92 @@ msgstr "OK" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "El endpoint no devolvió ningún modelo. Aún puedes escribir el nombre de un modelo manualmente." -#: lib/bds/tui.ex:28 +#: lib/bds/tui.ex:538 #, elixir-autogen, elixir-format -msgid "Blogging Desktop Server" -msgstr "Blogging Desktop Server" +msgid "AI is unavailable in airplane mode without a local endpoint." +msgstr "La IA no está disponible en modo avión sin un punto de conexión local." -#: lib/bds/tui.ex:30 +#: lib/bds/tui.ex:584 #, elixir-autogen, elixir-format -msgid "Press q to quit." -msgstr "Pulsa q para salir." +msgid "Could not load this file." +msgstr "No se pudo cargar este archivo." + +#: lib/bds/tui.ex:111 +#, elixir-autogen, elixir-format +msgid "Editing this item is not available in the terminal UI yet." +msgstr "La edición de este elemento aún no está disponible en la interfaz de terminal." + +#: lib/bds/tui.ex:206 +#, elixir-autogen, elixir-format +msgid "No suggestions returned." +msgstr "No se recibieron sugerencias." + +#: lib/bds/tui.ex:581 +#, elixir-autogen, elixir-format +msgid "Only images can be previewed." +msgstr "Solo se pueden previsualizar imágenes." + +#: lib/bds/tui.ex:462 +#, elixir-autogen, elixir-format +msgid "Post not found." +msgstr "Entrada no encontrada." + +#: lib/bds/tui.ex:282 +#, elixir-autogen, elixir-format +msgid "Press enter to preview %{name}." +msgstr "Pulsa Intro para previsualizar %{name}." + +#: lib/bds/tui.ex:508 +#, elixir-autogen, elixir-format +msgid "Published." +msgstr "Publicado." + +#: lib/bds/tui.ex:524 +#, elixir-autogen, elixir-format +msgid "Save before switching languages." +msgstr "Guarda antes de cambiar de idioma." + +#: lib/bds/tui.ex:509 +#, elixir-autogen, elixir-format +msgid "Saved." +msgstr "Guardado." + +#: lib/bds/tui.ex:285 +#, elixir-autogen, elixir-format +msgid "Select an entry and press enter to open it." +msgstr "Selecciona una entrada y pulsa Intro para abrirla." + +#: lib/bds/tui.ex:203 +#, elixir-autogen, elixir-format +msgid "Suggestions applied." +msgstr "Sugerencias aplicadas." + +#: lib/bds/tui.ex:297 +#, elixir-autogen, elixir-format +msgid "Title (ctrl+t to edit)" +msgstr "Título (ctrl+t para editar)" + +#: lib/bds/tui.ex:296 +#, elixir-autogen, elixir-format +msgid "Title (editing — enter to confirm)" +msgstr "Título (edición — Intro para confirmar)" + +#: lib/bds/tui.ex:328 +#, elixir-autogen, elixir-format +msgid "Working…" +msgstr "Procesando…" + +#: lib/bds/tui.ex:364 +#, elixir-autogen, elixir-format +msgid "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" +msgstr "ctrl+s guardar · ctrl+p publicar · ctrl+t título · ctrl+l idioma · ctrl+g IA · esc volver" + +#: lib/bds/tui.ex:357 +#, elixir-autogen, elixir-format +msgid "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" +msgstr "intro abrir · n nueva entrada · 1-5 vistas · r actualizar · ctrl+q salir" + +#: lib/bds/tui.ex:349 +#, elixir-autogen, elixir-format +msgid "esc to close" +msgstr "esc para cerrar" diff --git a/priv/gettext/fr/LC_MESSAGES/ui.po b/priv/gettext/fr/LC_MESSAGES/ui.po index f9a4f8e..a14a69e 100644 --- a/priv/gettext/fr/LC_MESSAGES/ui.po +++ b/priv/gettext/fr/LC_MESSAGES/ui.po @@ -10,7 +10,7 @@ msgstr "%{canonical} = %{translation}" msgid "%{count} mapped" msgstr "%{count} mappé(s)" -#: lib/bds/desktop/shell_data.ex:171 +#: lib/bds/desktop/shell_data.ex:173 #, elixir-autogen, elixir-format msgid "%{count} post" msgid_plural "%{count} posts" @@ -81,6 +81,11 @@ msgstr "Paramètres IA" #: lib/bds/desktop/shell_live/overlay_manager.ex:72 #: lib/bds/desktop/shell_live/post_editor.ex:920 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 +#: lib/bds/tui.ex:203 +#: lib/bds/tui.ex:206 +#: lib/bds/tui.ex:209 +#: lib/bds/tui.ex:217 +#: lib/bds/tui.ex:537 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "Suggestions IA" @@ -301,7 +306,7 @@ msgstr "Archives de tags rendues" msgid "Validation apply prepared" msgstr "Application de la validation préparée" -#: lib/bds/desktop/shell_data.ex:164 +#: lib/bds/desktop/shell_data.ex:166 #: lib/bds/ui/sidebar.ex:331 #, elixir-autogen, elixir-format msgid "Archived" @@ -336,7 +341,7 @@ msgstr "Auteur" msgid "Auto" msgstr "Automatique" -#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:443 #: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:364 @@ -486,7 +491,7 @@ msgstr "Valeurs par défaut des catégories, options de rendu et liaison des mod msgid "Category name is required" msgstr "Le nom de la catégorie est requis" -#: lib/bds/desktop/shell_live.ex:780 +#: lib/bds/desktop/shell_live.ex:789 #: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:323 @@ -584,6 +589,7 @@ msgstr "Confirmer" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 +#: lib/bds/tui.ex:308 #: lib/bds/ui/sidebar.ex:764 #, elixir-autogen, elixir-format msgid "Content" @@ -753,7 +759,7 @@ msgstr "Supprimer la traduction" msgid "Delete conversation" msgstr "Supprimer la conversation" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:65 +#: lib/bds/ui/post_editor/persistence.ex:69 #, elixir-autogen, elixir-format msgid "Delete this unpublished draft" msgstr "Supprimer ce brouillon non publié" @@ -810,17 +816,17 @@ msgstr "Désactiver la sortie de raisonnement pour le modèle de chat hors ligne msgid "Disable reasoning output for the online chat model" msgstr "Désactiver la sortie de raisonnement pour le modèle de chat en ligne" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:57 +#: lib/bds/ui/post_editor/persistence.ex:61 #, elixir-autogen, elixir-format msgid "Discard Changes" msgstr "Annuler les modifications" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:58 +#: lib/bds/ui/post_editor/persistence.ex:62 #, elixir-autogen, elixir-format msgid "Discard Draft" msgstr "Supprimer le brouillon" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:64 +#: lib/bds/ui/post_editor/persistence.ex:68 #, elixir-autogen, elixir-format msgid "Discard changes and restore the published version" msgstr "Annuler les modifications et restaurer la version publiée" @@ -863,7 +869,7 @@ msgstr "Ne pas traduire" msgid "Documentation" msgstr "Documentation" -#: lib/bds/desktop/shell_data.ex:162 +#: lib/bds/desktop/shell_data.ex:164 #, elixir-autogen, elixir-format msgid "Draft" msgstr "Brouillon" @@ -1063,7 +1069,7 @@ msgstr "Galerie" msgid "Generate Site" msgstr "Générer le site" -#: lib/bds/desktop/shell_live.ex:781 +#: lib/bds/desktop/shell_live.ex:790 #: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/ui/sidebar.ex:789 #, elixir-autogen, elixir-format @@ -1076,8 +1082,8 @@ msgstr "Git" msgid "Git Diff" msgstr "Diff Git" -#: lib/bds/desktop/shell_data.ex:226 -#: lib/bds/desktop/shell_live.ex:777 +#: lib/bds/desktop/shell_data.ex:228 +#: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:171 #, elixir-autogen, elixir-format msgid "Git Log" @@ -1103,7 +1109,7 @@ msgstr "Accueil" msgid "Host" msgstr "Hôte" -#: lib/bds/desktop/shell_data.ex:98 +#: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 #: lib/bds/desktop/shell_live/post_editor.ex:1038 @@ -1200,9 +1206,9 @@ msgstr "Définitions d’import" msgid "Import failed: %{error}" msgstr "Échec de l’import : %{error}" -#: lib/bds/desktop/shell_live.ex:605 -#: lib/bds/desktop/shell_live.ex:893 -#: lib/bds/desktop/shell_live.ex:899 +#: lib/bds/desktop/shell_live.ex:606 +#: lib/bds/desktop/shell_live.ex:902 +#: lib/bds/desktop/shell_live.ex:908 #: lib/bds/desktop/shell_live/sidebar_create.ex:47 #, elixir-autogen, elixir-format msgid "Import media" @@ -1337,7 +1343,7 @@ msgstr "Type MIME" msgid "Macros (%{count})" msgstr "Macros (%{count})" -#: lib/bds/desktop/shell_data.ex:101 +#: lib/bds/desktop/shell_data.ex:103 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:55 #, elixir-autogen, elixir-format msgid "Main Language" @@ -1379,6 +1385,9 @@ msgstr "Nombre maximal d’articles par page" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 +#: lib/bds/tui.ex:452 +#: lib/bds/tui.ex:581 +#: lib/bds/tui.ex:584 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:558 @@ -1431,7 +1440,7 @@ msgstr "Diff des métadonnées" msgid "Missing URLs" msgstr "URLs manquantes" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #, elixir-autogen, elixir-format msgid "Mode" msgstr "Mode" @@ -1466,6 +1475,7 @@ msgstr "Nouvelle page" #: lib/bds/desktop/menu_bar.ex:185 #: lib/bds/desktop/shell_live/sidebar_create.ex:41 +#: lib/bds/tui.ex:103 #, elixir-autogen, elixir-format msgid "New Post" msgstr "Nouvel article" @@ -1644,7 +1654,7 @@ msgstr "Pas encore pris en charge dans la réécriture" msgid "Nothing to Import" msgstr "Rien à importer" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #: lib/bds/desktop/shell_live/index.html.heex:503 #, elixir-autogen, elixir-format msgid "Offline" @@ -1784,7 +1794,7 @@ msgstr "Autre" msgid "Other (%{count})" msgstr "Autres (%{count})" -#: lib/bds/desktop/shell_live.ex:776 +#: lib/bds/desktop/shell_live.ex:785 #: lib/bds/desktop/shell_live/panel_renderer.ex:83 #, elixir-autogen, elixir-format msgid "Output" @@ -1866,7 +1876,7 @@ msgstr "Enregistrer la langue détectée pour ce média" msgid "Post" msgstr "Article" -#: lib/bds/desktop/shell_data.ex:229 +#: lib/bds/desktop/shell_data.ex:231 #: lib/bds/desktop/shell_live/panel_renderer.ex:118 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:310 #, elixir-autogen, elixir-format @@ -1901,6 +1911,8 @@ msgstr "Article enregistré" #: lib/bds/desktop/menu_bar.ex:202 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 +#: lib/bds/tui.ex:451 +#: lib/bds/tui.ex:462 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1985,7 +1997,7 @@ msgstr "Publier" msgid "Publish Selected" msgstr "Publier la sélection" -#: lib/bds/desktop/shell_data.ex:163 +#: lib/bds/desktop/shell_data.ex:165 #: lib/bds/desktop/shell_live/post_editor.ex:1036 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 @@ -2282,6 +2294,7 @@ msgstr "Les capacités de script sont configurées au niveau de l’application #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 +#: lib/bds/tui.ex:454 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2456,7 +2469,7 @@ msgstr "Démarrer la conversation" msgid "Starting..." msgstr "Démarrage..." -#: lib/bds/desktop/shell_data.ex:97 +#: lib/bds/desktop/shell_data.ex:99 #: lib/bds/desktop/shell_live/import_editor.ex:1182 #: lib/bds/desktop/shell_live/import_editor.ex:1233 #, elixir-autogen, elixir-format @@ -2529,6 +2542,7 @@ msgstr "Nom du mot-clé" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 +#: lib/bds/tui.ex:455 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2538,7 +2552,7 @@ msgstr "Nom du mot-clé" msgid "Tags" msgstr "Tags" -#: lib/bds/desktop/shell_live.ex:775 +#: lib/bds/desktop/shell_live.ex:784 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 #, elixir-autogen, elixir-format msgid "Tasks" @@ -2584,6 +2598,7 @@ msgstr "La syntaxe du template est valide" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 +#: lib/bds/tui.ex:453 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2780,7 +2795,7 @@ msgid "Unsaved" msgstr "Non enregistré" #: lib/bds/desktop/shell_live/import_editor.ex:871 -#: lib/bds/desktop/shell_live/post_editor/post_metadata.ex:166 +#: lib/bds/ui/post_editor/metadata.ex:166 #: lib/bds/ui/sidebar.ex:1108 #, elixir-autogen, elixir-format msgid "Untitled" @@ -3161,9 +3176,9 @@ msgid "I can help you manage your blog with rich visualizations. Try asking me t msgstr "Je peux vous aider à gérer votre blog avec des visualisations riches. Essayez par exemple :" #: lib/bds/desktop/shell_live/index.html.heex:289 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Media Files" -msgstr "Médias" +msgstr "Fichiers multimédias" #: lib/bds/desktop/shell_live/chat_editor_html/chat_editor.html.heex:21 #, elixir-autogen, elixir-format @@ -3218,12 +3233,12 @@ msgstr "Bienvenue dans l’assistant IA" msgid "Comparing database and filesystem metadata" msgstr "Comparaison des métadonnées entre la base et le système de fichiers" -#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:682 #, elixir-autogen, elixir-format msgid "Added %{count} images to post" msgstr "%{count} images ajoutées à l'article" -#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:650 #, elixir-autogen, elixir-format msgid "Added %{title}" msgstr "%{title} ajouté" @@ -3243,18 +3258,18 @@ msgstr "Guide utilisateur pour les flux éditoriaux, médias, modèles, traducti msgid "Image Import Concurrency" msgstr "Importation simultanée d'images" -#: lib/bds/desktop/shell_live.ex:441 -#: lib/bds/desktop/shell_live.ex:454 -#: lib/bds/desktop/shell_live.ex:648 -#: lib/bds/desktop/shell_live.ex:680 -#: lib/bds/desktop/shell_live.ex:691 -#: lib/bds/desktop/shell_live.ex:702 +#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:455 +#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:692 +#: lib/bds/desktop/shell_live.ex:703 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #, elixir-autogen, elixir-format msgid "Add Gallery Images" msgstr "Ajouter des images à la galerie" -#: lib/bds/desktop/shell_live.ex:703 +#: lib/bds/desktop/shell_live.ex:704 #, elixir-autogen, elixir-format msgid "Failed to process %{path}: %{reason}" msgstr "Impossible de traiter %{path} : %{reason}" @@ -3445,12 +3460,12 @@ msgstr "renommé" msgid "untracked" msgstr "non suivi" -#: lib/bds/desktop/shell_live.ex:801 +#: lib/bds/desktop/shell_live.ex:810 #, elixir-autogen, elixir-format msgid "Blogmark" msgstr "Blogmark" -#: lib/bds/desktop/shell_live.ex:844 +#: lib/bds/desktop/shell_live.ex:853 #, elixir-autogen, elixir-format msgid "Open a project before importing a blogmark." msgstr "Ouvrez un projet avant d’importer un blogmark." @@ -3482,16 +3497,16 @@ msgid "Copy Bookmarklet to Clipboard" msgstr "Copier le bookmarklet dans le presse-papiers" #: lib/bds/desktop/shell_live/overlay_manager.ex:300 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Delete Tag" -msgstr "Supprimer" +msgstr "Supprimer le tag" #: lib/bds/desktop/shell_live/settings_editor.ex:66 #, elixir-autogen, elixir-format msgid "Failed to copy bookmarklet to clipboard" msgstr "Échec de la copie du bookmarklet dans le presse-papiers" -#: lib/bds/desktop/shell_live.ex:813 +#: lib/bds/desktop/shell_live.ex:822 #, elixir-autogen, elixir-format msgid "The project this blogmark targets does not exist here." msgstr "Le projet ciblé par ce blogmark n'existe pas ici." @@ -3598,12 +3613,92 @@ msgstr "OK" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "Le point de terminaison n'a renvoyé aucun modèle. Vous pouvez toujours saisir un nom de modèle manuellement." -#: lib/bds/tui.ex:28 +#: lib/bds/tui.ex:538 #, elixir-autogen, elixir-format -msgid "Blogging Desktop Server" -msgstr "Blogging Desktop Server" +msgid "AI is unavailable in airplane mode without a local endpoint." +msgstr "L'IA n'est pas disponible en mode avion sans point de terminaison local." -#: lib/bds/tui.ex:30 +#: lib/bds/tui.ex:584 #, elixir-autogen, elixir-format -msgid "Press q to quit." -msgstr "Appuyez sur q pour quitter." +msgid "Could not load this file." +msgstr "Impossible de charger ce fichier." + +#: lib/bds/tui.ex:111 +#, elixir-autogen, elixir-format +msgid "Editing this item is not available in the terminal UI yet." +msgstr "La modification de cet élément n'est pas encore disponible dans l'interface du terminal." + +#: lib/bds/tui.ex:206 +#, elixir-autogen, elixir-format +msgid "No suggestions returned." +msgstr "Aucune suggestion reçue." + +#: lib/bds/tui.ex:581 +#, elixir-autogen, elixir-format +msgid "Only images can be previewed." +msgstr "Seules les images peuvent être prévisualisées." + +#: lib/bds/tui.ex:462 +#, elixir-autogen, elixir-format +msgid "Post not found." +msgstr "Article introuvable." + +#: lib/bds/tui.ex:282 +#, elixir-autogen, elixir-format +msgid "Press enter to preview %{name}." +msgstr "Appuyez sur Entrée pour prévisualiser %{name}." + +#: lib/bds/tui.ex:508 +#, elixir-autogen, elixir-format +msgid "Published." +msgstr "Publié." + +#: lib/bds/tui.ex:524 +#, elixir-autogen, elixir-format +msgid "Save before switching languages." +msgstr "Enregistrez avant de changer de langue." + +#: lib/bds/tui.ex:509 +#, elixir-autogen, elixir-format +msgid "Saved." +msgstr "Enregistré." + +#: lib/bds/tui.ex:285 +#, elixir-autogen, elixir-format +msgid "Select an entry and press enter to open it." +msgstr "Sélectionnez une entrée et appuyez sur Entrée pour l'ouvrir." + +#: lib/bds/tui.ex:203 +#, elixir-autogen, elixir-format +msgid "Suggestions applied." +msgstr "Suggestions appliquées." + +#: lib/bds/tui.ex:297 +#, elixir-autogen, elixir-format +msgid "Title (ctrl+t to edit)" +msgstr "Titre (ctrl+t pour modifier)" + +#: lib/bds/tui.ex:296 +#, elixir-autogen, elixir-format +msgid "Title (editing — enter to confirm)" +msgstr "Titre (édition — Entrée pour confirmer)" + +#: lib/bds/tui.ex:328 +#, elixir-autogen, elixir-format +msgid "Working…" +msgstr "Traitement en cours…" + +#: lib/bds/tui.ex:364 +#, elixir-autogen, elixir-format +msgid "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" +msgstr "ctrl+s enregistrer · ctrl+p publier · ctrl+t titre · ctrl+l langue · ctrl+g IA · échap retour" + +#: lib/bds/tui.ex:357 +#, elixir-autogen, elixir-format +msgid "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" +msgstr "entrée ouvrir · n nouvel article · 1-5 vues · r actualiser · ctrl+q quitter" + +#: lib/bds/tui.ex:349 +#, elixir-autogen, elixir-format +msgid "esc to close" +msgstr "échap pour fermer" diff --git a/priv/gettext/it/LC_MESSAGES/ui.po b/priv/gettext/it/LC_MESSAGES/ui.po index 35d592d..fd5379a 100644 --- a/priv/gettext/it/LC_MESSAGES/ui.po +++ b/priv/gettext/it/LC_MESSAGES/ui.po @@ -10,7 +10,7 @@ msgstr "%{canonical} = %{translation}" msgid "%{count} mapped" msgstr "%{count} mappati" -#: lib/bds/desktop/shell_data.ex:171 +#: lib/bds/desktop/shell_data.ex:173 #, elixir-autogen, elixir-format msgid "%{count} post" msgid_plural "%{count} posts" @@ -81,6 +81,11 @@ msgstr "Impostazioni IA" #: lib/bds/desktop/shell_live/overlay_manager.ex:72 #: lib/bds/desktop/shell_live/post_editor.ex:920 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 +#: lib/bds/tui.ex:203 +#: lib/bds/tui.ex:206 +#: lib/bds/tui.ex:209 +#: lib/bds/tui.ex:217 +#: lib/bds/tui.ex:537 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "Suggerimenti IA" @@ -301,7 +306,7 @@ msgstr "Archivi tag renderizzati" msgid "Validation apply prepared" msgstr "Applicazione della validazione preparata" -#: lib/bds/desktop/shell_data.ex:164 +#: lib/bds/desktop/shell_data.ex:166 #: lib/bds/ui/sidebar.ex:331 #, elixir-autogen, elixir-format msgid "Archived" @@ -336,7 +341,7 @@ msgstr "Autore" msgid "Auto" msgstr "Automatico" -#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:443 #: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:364 @@ -486,7 +491,7 @@ msgstr "Valori predefiniti delle categorie, opzioni di rendering e collegamento msgid "Category name is required" msgstr "Il nome della categoria è obbligatorio" -#: lib/bds/desktop/shell_live.ex:780 +#: lib/bds/desktop/shell_live.ex:789 #: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:323 @@ -584,6 +589,7 @@ msgstr "Conferma" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 +#: lib/bds/tui.ex:308 #: lib/bds/ui/sidebar.ex:764 #, elixir-autogen, elixir-format msgid "Content" @@ -753,7 +759,7 @@ msgstr "Elimina traduzione" msgid "Delete conversation" msgstr "Elimina conversazione" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:65 +#: lib/bds/ui/post_editor/persistence.ex:69 #, elixir-autogen, elixir-format msgid "Delete this unpublished draft" msgstr "Elimina questa bozza non pubblicata" @@ -810,17 +816,17 @@ msgstr "Disattiva l'output di ragionamento per il modello di chat offline" msgid "Disable reasoning output for the online chat model" msgstr "Disattiva l'output di ragionamento per il modello di chat online" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:57 +#: lib/bds/ui/post_editor/persistence.ex:61 #, elixir-autogen, elixir-format msgid "Discard Changes" msgstr "Annulla le modifiche" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:58 +#: lib/bds/ui/post_editor/persistence.ex:62 #, elixir-autogen, elixir-format msgid "Discard Draft" msgstr "Elimina bozza" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:64 +#: lib/bds/ui/post_editor/persistence.ex:68 #, elixir-autogen, elixir-format msgid "Discard changes and restore the published version" msgstr "Annulla le modifiche e ripristina la versione pubblicata" @@ -863,7 +869,7 @@ msgstr "Non tradurre" msgid "Documentation" msgstr "Documentazione" -#: lib/bds/desktop/shell_data.ex:162 +#: lib/bds/desktop/shell_data.ex:164 #, elixir-autogen, elixir-format msgid "Draft" msgstr "Bozza" @@ -1063,7 +1069,7 @@ msgstr "Galleria" msgid "Generate Site" msgstr "Genera sito" -#: lib/bds/desktop/shell_live.ex:781 +#: lib/bds/desktop/shell_live.ex:790 #: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/ui/sidebar.ex:789 #, elixir-autogen, elixir-format @@ -1076,8 +1082,8 @@ msgstr "Git" msgid "Git Diff" msgstr "Diff Git" -#: lib/bds/desktop/shell_data.ex:226 -#: lib/bds/desktop/shell_live.ex:777 +#: lib/bds/desktop/shell_data.ex:228 +#: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:171 #, elixir-autogen, elixir-format msgid "Git Log" @@ -1103,7 +1109,7 @@ msgstr "Home" msgid "Host" msgstr "Host" -#: lib/bds/desktop/shell_data.ex:98 +#: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 #: lib/bds/desktop/shell_live/post_editor.ex:1038 @@ -1200,9 +1206,9 @@ msgstr "Definizioni di importazione" msgid "Import failed: %{error}" msgstr "Importazione non riuscita: %{error}" -#: lib/bds/desktop/shell_live.ex:605 -#: lib/bds/desktop/shell_live.ex:893 -#: lib/bds/desktop/shell_live.ex:899 +#: lib/bds/desktop/shell_live.ex:606 +#: lib/bds/desktop/shell_live.ex:902 +#: lib/bds/desktop/shell_live.ex:908 #: lib/bds/desktop/shell_live/sidebar_create.ex:47 #, elixir-autogen, elixir-format msgid "Import media" @@ -1337,7 +1343,7 @@ msgstr "Tipo MIME" msgid "Macros (%{count})" msgstr "Macro (%{count})" -#: lib/bds/desktop/shell_data.ex:101 +#: lib/bds/desktop/shell_data.ex:103 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:55 #, elixir-autogen, elixir-format msgid "Main Language" @@ -1379,6 +1385,9 @@ msgstr "Numero massimo di post per pagina" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 +#: lib/bds/tui.ex:452 +#: lib/bds/tui.ex:581 +#: lib/bds/tui.ex:584 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:558 @@ -1431,7 +1440,7 @@ msgstr "Diff metadati" msgid "Missing URLs" msgstr "URL mancanti" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #, elixir-autogen, elixir-format msgid "Mode" msgstr "Modalità" @@ -1466,6 +1475,7 @@ msgstr "Nuova pagina" #: lib/bds/desktop/menu_bar.ex:185 #: lib/bds/desktop/shell_live/sidebar_create.ex:41 +#: lib/bds/tui.ex:103 #, elixir-autogen, elixir-format msgid "New Post" msgstr "Nuovo post" @@ -1644,7 +1654,7 @@ msgstr "Non ancora supportato nella riscrittura" msgid "Nothing to Import" msgstr "Niente da importare" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #: lib/bds/desktop/shell_live/index.html.heex:503 #, elixir-autogen, elixir-format msgid "Offline" @@ -1784,7 +1794,7 @@ msgstr "Altro" msgid "Other (%{count})" msgstr "Altro (%{count})" -#: lib/bds/desktop/shell_live.ex:776 +#: lib/bds/desktop/shell_live.ex:785 #: lib/bds/desktop/shell_live/panel_renderer.ex:83 #, elixir-autogen, elixir-format msgid "Output" @@ -1866,7 +1876,7 @@ msgstr "Salva la lingua rilevata per questo media" msgid "Post" msgstr "Post" -#: lib/bds/desktop/shell_data.ex:229 +#: lib/bds/desktop/shell_data.ex:231 #: lib/bds/desktop/shell_live/panel_renderer.ex:118 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:310 #, elixir-autogen, elixir-format @@ -1901,6 +1911,8 @@ msgstr "Articolo salvato" #: lib/bds/desktop/menu_bar.ex:202 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 +#: lib/bds/tui.ex:451 +#: lib/bds/tui.ex:462 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1985,7 +1997,7 @@ msgstr "Pubblica" msgid "Publish Selected" msgstr "Pubblica selezionati" -#: lib/bds/desktop/shell_data.ex:163 +#: lib/bds/desktop/shell_data.ex:165 #: lib/bds/desktop/shell_live/post_editor.ex:1036 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 @@ -2282,6 +2294,7 @@ msgstr "Le capacità di scripting sono configurate a livello applicativo nella r #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 +#: lib/bds/tui.ex:454 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2456,7 +2469,7 @@ msgstr "Avvia chat" msgid "Starting..." msgstr "Avvio..." -#: lib/bds/desktop/shell_data.ex:97 +#: lib/bds/desktop/shell_data.ex:99 #: lib/bds/desktop/shell_live/import_editor.ex:1182 #: lib/bds/desktop/shell_live/import_editor.ex:1233 #, elixir-autogen, elixir-format @@ -2529,6 +2542,7 @@ msgstr "Nome del tag" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 +#: lib/bds/tui.ex:455 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2538,7 +2552,7 @@ msgstr "Nome del tag" msgid "Tags" msgstr "Tag" -#: lib/bds/desktop/shell_live.ex:775 +#: lib/bds/desktop/shell_live.ex:784 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 #, elixir-autogen, elixir-format msgid "Tasks" @@ -2584,6 +2598,7 @@ msgstr "La sintassi del template è valida" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 +#: lib/bds/tui.ex:453 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2780,7 +2795,7 @@ msgid "Unsaved" msgstr "Non salvato" #: lib/bds/desktop/shell_live/import_editor.ex:871 -#: lib/bds/desktop/shell_live/post_editor/post_metadata.ex:166 +#: lib/bds/ui/post_editor/metadata.ex:166 #: lib/bds/ui/sidebar.ex:1108 #, elixir-autogen, elixir-format msgid "Untitled" @@ -3161,9 +3176,9 @@ msgid "I can help you manage your blog with rich visualizations. Try asking me t msgstr "Posso aiutarti a gestire il tuo blog con visualizzazioni interattive. Prova a chiedermi di:" #: lib/bds/desktop/shell_live/index.html.heex:289 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Media Files" -msgstr "Media" +msgstr "File multimediali" #: lib/bds/desktop/shell_live/chat_editor_html/chat_editor.html.heex:21 #, elixir-autogen, elixir-format @@ -3218,12 +3233,12 @@ msgstr "Benvenuto nell’assistente IA" msgid "Comparing database and filesystem metadata" msgstr "Confronto tra i metadati del database e del filesystem" -#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:682 #, elixir-autogen, elixir-format msgid "Added %{count} images to post" msgstr "%{count} immagini aggiunte al post" -#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:650 #, elixir-autogen, elixir-format msgid "Added %{title}" msgstr "%{title} aggiunto" @@ -3243,18 +3258,18 @@ msgstr "Guida per l'utente finale per flussi editoriali, media, modelli, traduzi msgid "Image Import Concurrency" msgstr "Importazione simultanea immagini" -#: lib/bds/desktop/shell_live.ex:441 -#: lib/bds/desktop/shell_live.ex:454 -#: lib/bds/desktop/shell_live.ex:648 -#: lib/bds/desktop/shell_live.ex:680 -#: lib/bds/desktop/shell_live.ex:691 -#: lib/bds/desktop/shell_live.ex:702 +#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:455 +#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:692 +#: lib/bds/desktop/shell_live.ex:703 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #, elixir-autogen, elixir-format msgid "Add Gallery Images" msgstr "Aggiungi immagini alla galleria" -#: lib/bds/desktop/shell_live.ex:703 +#: lib/bds/desktop/shell_live.ex:704 #, elixir-autogen, elixir-format msgid "Failed to process %{path}: %{reason}" msgstr "Impossibile elaborare %{path}: %{reason}" @@ -3445,12 +3460,12 @@ msgstr "rinominato" msgid "untracked" msgstr "non tracciato" -#: lib/bds/desktop/shell_live.ex:801 +#: lib/bds/desktop/shell_live.ex:810 #, elixir-autogen, elixir-format msgid "Blogmark" msgstr "Blogmark" -#: lib/bds/desktop/shell_live.ex:844 +#: lib/bds/desktop/shell_live.ex:853 #, elixir-autogen, elixir-format msgid "Open a project before importing a blogmark." msgstr "Apri un progetto prima di importare un blogmark." @@ -3482,16 +3497,16 @@ msgid "Copy Bookmarklet to Clipboard" msgstr "Copia bookmarklet negli appunti" #: lib/bds/desktop/shell_live/overlay_manager.ex:300 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Delete Tag" -msgstr "Elimina" +msgstr "Elimina tag" #: lib/bds/desktop/shell_live/settings_editor.ex:66 #, elixir-autogen, elixir-format msgid "Failed to copy bookmarklet to clipboard" msgstr "Impossibile copiare il bookmarklet negli appunti" -#: lib/bds/desktop/shell_live.ex:813 +#: lib/bds/desktop/shell_live.ex:822 #, elixir-autogen, elixir-format msgid "The project this blogmark targets does not exist here." msgstr "Il progetto a cui punta questo blogmark non esiste qui." @@ -3598,12 +3613,92 @@ msgstr "OK" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "L'endpoint non ha restituito alcun modello. Puoi comunque digitare manualmente il nome di un modello." -#: lib/bds/tui.ex:28 +#: lib/bds/tui.ex:538 #, elixir-autogen, elixir-format -msgid "Blogging Desktop Server" -msgstr "Blogging Desktop Server" +msgid "AI is unavailable in airplane mode without a local endpoint." +msgstr "L'IA non è disponibile in modalità aereo senza un endpoint locale." -#: lib/bds/tui.ex:30 +#: lib/bds/tui.ex:584 #, elixir-autogen, elixir-format -msgid "Press q to quit." -msgstr "Premi q per uscire." +msgid "Could not load this file." +msgstr "Impossibile caricare questo file." + +#: lib/bds/tui.ex:111 +#, elixir-autogen, elixir-format +msgid "Editing this item is not available in the terminal UI yet." +msgstr "La modifica di questo elemento non è ancora disponibile nell'interfaccia del terminale." + +#: lib/bds/tui.ex:206 +#, elixir-autogen, elixir-format +msgid "No suggestions returned." +msgstr "Nessun suggerimento ricevuto." + +#: lib/bds/tui.ex:581 +#, elixir-autogen, elixir-format +msgid "Only images can be previewed." +msgstr "Solo le immagini possono essere visualizzate in anteprima." + +#: lib/bds/tui.ex:462 +#, elixir-autogen, elixir-format +msgid "Post not found." +msgstr "Post non trovato." + +#: lib/bds/tui.ex:282 +#, elixir-autogen, elixir-format +msgid "Press enter to preview %{name}." +msgstr "Premi Invio per l'anteprima di %{name}." + +#: lib/bds/tui.ex:508 +#, elixir-autogen, elixir-format +msgid "Published." +msgstr "Pubblicato." + +#: lib/bds/tui.ex:524 +#, elixir-autogen, elixir-format +msgid "Save before switching languages." +msgstr "Salva prima di cambiare lingua." + +#: lib/bds/tui.ex:509 +#, elixir-autogen, elixir-format +msgid "Saved." +msgstr "Salvato." + +#: lib/bds/tui.ex:285 +#, elixir-autogen, elixir-format +msgid "Select an entry and press enter to open it." +msgstr "Seleziona una voce e premi Invio per aprirla." + +#: lib/bds/tui.ex:203 +#, elixir-autogen, elixir-format +msgid "Suggestions applied." +msgstr "Suggerimenti applicati." + +#: lib/bds/tui.ex:297 +#, elixir-autogen, elixir-format +msgid "Title (ctrl+t to edit)" +msgstr "Titolo (ctrl+t per modificare)" + +#: lib/bds/tui.ex:296 +#, elixir-autogen, elixir-format +msgid "Title (editing — enter to confirm)" +msgstr "Titolo (modifica — Invio per confermare)" + +#: lib/bds/tui.ex:328 +#, elixir-autogen, elixir-format +msgid "Working…" +msgstr "Elaborazione…" + +#: lib/bds/tui.ex:364 +#, elixir-autogen, elixir-format +msgid "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" +msgstr "ctrl+s salva · ctrl+p pubblica · ctrl+t titolo · ctrl+l lingua · ctrl+g IA · esc indietro" + +#: lib/bds/tui.ex:357 +#, elixir-autogen, elixir-format +msgid "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" +msgstr "invio apri · n nuovo post · 1-5 viste · r aggiorna · ctrl+q esci" + +#: lib/bds/tui.ex:349 +#, elixir-autogen, elixir-format +msgid "esc to close" +msgstr "esc per chiudere" diff --git a/priv/gettext/ui.pot b/priv/gettext/ui.pot index d1c5628..c60b666 100644 --- a/priv/gettext/ui.pot +++ b/priv/gettext/ui.pot @@ -23,7 +23,7 @@ msgstr "" msgid "%{count} mapped" msgstr "" -#: lib/bds/desktop/shell_data.ex:171 +#: lib/bds/desktop/shell_data.ex:173 #, elixir-autogen, elixir-format msgid "%{count} post" msgid_plural "%{count} posts" @@ -94,6 +94,11 @@ msgstr "" #: lib/bds/desktop/shell_live/overlay_manager.ex:72 #: lib/bds/desktop/shell_live/post_editor.ex:920 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 +#: lib/bds/tui.ex:203 +#: lib/bds/tui.ex:206 +#: lib/bds/tui.ex:209 +#: lib/bds/tui.ex:217 +#: lib/bds/tui.ex:537 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "" @@ -314,7 +319,7 @@ msgstr "" msgid "Validation apply prepared" msgstr "" -#: lib/bds/desktop/shell_data.ex:164 +#: lib/bds/desktop/shell_data.ex:166 #: lib/bds/ui/sidebar.ex:331 #, elixir-autogen, elixir-format msgid "Archived" @@ -349,7 +354,7 @@ msgstr "" msgid "Auto" msgstr "" -#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:443 #: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:364 @@ -499,7 +504,7 @@ msgstr "" msgid "Category name is required" msgstr "" -#: lib/bds/desktop/shell_live.ex:780 +#: lib/bds/desktop/shell_live.ex:789 #: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:323 @@ -597,6 +602,7 @@ msgstr "" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 +#: lib/bds/tui.ex:308 #: lib/bds/ui/sidebar.ex:764 #, elixir-autogen, elixir-format msgid "Content" @@ -766,7 +772,7 @@ msgstr "" msgid "Delete conversation" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:65 +#: lib/bds/ui/post_editor/persistence.ex:69 #, elixir-autogen, elixir-format msgid "Delete this unpublished draft" msgstr "" @@ -823,17 +829,17 @@ msgstr "" msgid "Disable reasoning output for the online chat model" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:57 +#: lib/bds/ui/post_editor/persistence.ex:61 #, elixir-autogen, elixir-format msgid "Discard Changes" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:58 +#: lib/bds/ui/post_editor/persistence.ex:62 #, elixir-autogen, elixir-format msgid "Discard Draft" msgstr "" -#: lib/bds/desktop/shell_live/post_editor/persistence.ex:64 +#: lib/bds/ui/post_editor/persistence.ex:68 #, elixir-autogen, elixir-format msgid "Discard changes and restore the published version" msgstr "" @@ -876,7 +882,7 @@ msgstr "" msgid "Documentation" msgstr "" -#: lib/bds/desktop/shell_data.ex:162 +#: lib/bds/desktop/shell_data.ex:164 #, elixir-autogen, elixir-format msgid "Draft" msgstr "" @@ -1076,7 +1082,7 @@ msgstr "" msgid "Generate Site" msgstr "" -#: lib/bds/desktop/shell_live.ex:781 +#: lib/bds/desktop/shell_live.ex:790 #: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/ui/sidebar.ex:789 #, elixir-autogen, elixir-format @@ -1089,8 +1095,8 @@ msgstr "" msgid "Git Diff" msgstr "" -#: lib/bds/desktop/shell_data.ex:226 -#: lib/bds/desktop/shell_live.ex:777 +#: lib/bds/desktop/shell_data.ex:228 +#: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:171 #, elixir-autogen, elixir-format msgid "Git Log" @@ -1116,7 +1122,7 @@ msgstr "" msgid "Host" msgstr "" -#: lib/bds/desktop/shell_data.ex:98 +#: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 #: lib/bds/desktop/shell_live/post_editor.ex:1038 @@ -1213,9 +1219,9 @@ msgstr "" msgid "Import failed: %{error}" msgstr "" -#: lib/bds/desktop/shell_live.ex:605 -#: lib/bds/desktop/shell_live.ex:893 -#: lib/bds/desktop/shell_live.ex:899 +#: lib/bds/desktop/shell_live.ex:606 +#: lib/bds/desktop/shell_live.ex:902 +#: lib/bds/desktop/shell_live.ex:908 #: lib/bds/desktop/shell_live/sidebar_create.ex:47 #, elixir-autogen, elixir-format msgid "Import media" @@ -1350,7 +1356,7 @@ msgstr "" msgid "Macros (%{count})" msgstr "" -#: lib/bds/desktop/shell_data.ex:101 +#: lib/bds/desktop/shell_data.ex:103 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:55 #, elixir-autogen, elixir-format msgid "Main Language" @@ -1392,6 +1398,9 @@ msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 +#: lib/bds/tui.ex:452 +#: lib/bds/tui.ex:581 +#: lib/bds/tui.ex:584 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:558 @@ -1444,7 +1453,7 @@ msgstr "" msgid "Missing URLs" msgstr "" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #, elixir-autogen, elixir-format msgid "Mode" msgstr "" @@ -1479,6 +1488,7 @@ msgstr "" #: lib/bds/desktop/menu_bar.ex:185 #: lib/bds/desktop/shell_live/sidebar_create.ex:41 +#: lib/bds/tui.ex:103 #, elixir-autogen, elixir-format msgid "New Post" msgstr "" @@ -1657,7 +1667,7 @@ msgstr "" msgid "Nothing to Import" msgstr "" -#: lib/bds/desktop/shell_data.ex:100 +#: lib/bds/desktop/shell_data.ex:102 #: lib/bds/desktop/shell_live/index.html.heex:503 #, elixir-autogen, elixir-format msgid "Offline" @@ -1797,7 +1807,7 @@ msgstr "" msgid "Other (%{count})" msgstr "" -#: lib/bds/desktop/shell_live.ex:776 +#: lib/bds/desktop/shell_live.ex:785 #: lib/bds/desktop/shell_live/panel_renderer.ex:83 #, elixir-autogen, elixir-format msgid "Output" @@ -1879,7 +1889,7 @@ msgstr "" msgid "Post" msgstr "" -#: lib/bds/desktop/shell_data.ex:229 +#: lib/bds/desktop/shell_data.ex:231 #: lib/bds/desktop/shell_live/panel_renderer.ex:118 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:310 #, elixir-autogen, elixir-format @@ -1914,6 +1924,8 @@ msgstr "" #: lib/bds/desktop/menu_bar.ex:202 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 +#: lib/bds/tui.ex:451 +#: lib/bds/tui.ex:462 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1998,7 +2010,7 @@ msgstr "" msgid "Publish Selected" msgstr "" -#: lib/bds/desktop/shell_data.ex:163 +#: lib/bds/desktop/shell_data.ex:165 #: lib/bds/desktop/shell_live/post_editor.ex:1036 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 @@ -2295,6 +2307,7 @@ msgstr "" #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 +#: lib/bds/tui.ex:454 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2469,7 +2482,7 @@ msgstr "" msgid "Starting..." msgstr "" -#: lib/bds/desktop/shell_data.ex:97 +#: lib/bds/desktop/shell_data.ex:99 #: lib/bds/desktop/shell_live/import_editor.ex:1182 #: lib/bds/desktop/shell_live/import_editor.ex:1233 #, elixir-autogen, elixir-format @@ -2542,6 +2555,7 @@ msgstr "" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 +#: lib/bds/tui.ex:455 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2551,7 +2565,7 @@ msgstr "" msgid "Tags" msgstr "" -#: lib/bds/desktop/shell_live.ex:775 +#: lib/bds/desktop/shell_live.ex:784 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 #, elixir-autogen, elixir-format msgid "Tasks" @@ -2597,6 +2611,7 @@ msgstr "" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 +#: lib/bds/tui.ex:453 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2793,7 +2808,7 @@ msgid "Unsaved" msgstr "" #: lib/bds/desktop/shell_live/import_editor.ex:871 -#: lib/bds/desktop/shell_live/post_editor/post_metadata.ex:166 +#: lib/bds/ui/post_editor/metadata.ex:166 #: lib/bds/ui/sidebar.ex:1108 #, elixir-autogen, elixir-format msgid "Untitled" @@ -3231,12 +3246,12 @@ msgstr "" msgid "Comparing database and filesystem metadata" msgstr "" -#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:682 #, elixir-autogen, elixir-format msgid "Added %{count} images to post" msgstr "" -#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:650 #, elixir-autogen, elixir-format msgid "Added %{title}" msgstr "" @@ -3256,18 +3271,18 @@ msgstr "" msgid "Image Import Concurrency" msgstr "" -#: lib/bds/desktop/shell_live.ex:441 -#: lib/bds/desktop/shell_live.ex:454 -#: lib/bds/desktop/shell_live.ex:648 -#: lib/bds/desktop/shell_live.ex:680 -#: lib/bds/desktop/shell_live.ex:691 -#: lib/bds/desktop/shell_live.ex:702 +#: lib/bds/desktop/shell_live.ex:442 +#: lib/bds/desktop/shell_live.ex:455 +#: lib/bds/desktop/shell_live.ex:649 +#: lib/bds/desktop/shell_live.ex:681 +#: lib/bds/desktop/shell_live.ex:692 +#: lib/bds/desktop/shell_live.ex:703 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #, elixir-autogen, elixir-format msgid "Add Gallery Images" msgstr "" -#: lib/bds/desktop/shell_live.ex:703 +#: lib/bds/desktop/shell_live.ex:704 #, elixir-autogen, elixir-format msgid "Failed to process %{path}: %{reason}" msgstr "" @@ -3458,12 +3473,12 @@ msgstr "" msgid "untracked" msgstr "" -#: lib/bds/desktop/shell_live.ex:801 +#: lib/bds/desktop/shell_live.ex:810 #, elixir-autogen, elixir-format msgid "Blogmark" msgstr "" -#: lib/bds/desktop/shell_live.ex:844 +#: lib/bds/desktop/shell_live.ex:853 #, elixir-autogen, elixir-format msgid "Open a project before importing a blogmark." msgstr "" @@ -3504,7 +3519,7 @@ msgstr "" msgid "Failed to copy bookmarklet to clipboard" msgstr "" -#: lib/bds/desktop/shell_live.ex:813 +#: lib/bds/desktop/shell_live.ex:822 #, elixir-autogen, elixir-format msgid "The project this blogmark targets does not exist here." msgstr "" @@ -3611,12 +3626,92 @@ msgstr "" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "" -#: lib/bds/tui.ex:28 +#: lib/bds/tui.ex:538 #, elixir-autogen, elixir-format -msgid "Blogging Desktop Server" +msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "" -#: lib/bds/tui.ex:30 +#: lib/bds/tui.ex:584 #, elixir-autogen, elixir-format -msgid "Press q to quit." +msgid "Could not load this file." +msgstr "" + +#: lib/bds/tui.ex:111 +#, elixir-autogen, elixir-format +msgid "Editing this item is not available in the terminal UI yet." +msgstr "" + +#: lib/bds/tui.ex:206 +#, elixir-autogen, elixir-format +msgid "No suggestions returned." +msgstr "" + +#: lib/bds/tui.ex:581 +#, elixir-autogen, elixir-format +msgid "Only images can be previewed." +msgstr "" + +#: lib/bds/tui.ex:462 +#, elixir-autogen, elixir-format +msgid "Post not found." +msgstr "" + +#: lib/bds/tui.ex:282 +#, elixir-autogen, elixir-format +msgid "Press enter to preview %{name}." +msgstr "" + +#: lib/bds/tui.ex:508 +#, elixir-autogen, elixir-format +msgid "Published." +msgstr "" + +#: lib/bds/tui.ex:524 +#, elixir-autogen, elixir-format +msgid "Save before switching languages." +msgstr "" + +#: lib/bds/tui.ex:509 +#, elixir-autogen, elixir-format +msgid "Saved." +msgstr "" + +#: lib/bds/tui.ex:285 +#, elixir-autogen, elixir-format +msgid "Select an entry and press enter to open it." +msgstr "" + +#: lib/bds/tui.ex:203 +#, elixir-autogen, elixir-format +msgid "Suggestions applied." +msgstr "" + +#: lib/bds/tui.ex:297 +#, elixir-autogen, elixir-format +msgid "Title (ctrl+t to edit)" +msgstr "" + +#: lib/bds/tui.ex:296 +#, elixir-autogen, elixir-format +msgid "Title (editing — enter to confirm)" +msgstr "" + +#: lib/bds/tui.ex:328 +#, elixir-autogen, elixir-format +msgid "Working…" +msgstr "" + +#: lib/bds/tui.ex:364 +#, elixir-autogen, elixir-format +msgid "ctrl+s save · ctrl+p publish · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" +msgstr "" + +#: lib/bds/tui.ex:357 +#, elixir-autogen, elixir-format +msgid "enter open · n new post · 1-5 views · r refresh · ctrl+q quit" +msgstr "" + +#: lib/bds/tui.ex:349 +#, elixir-autogen, elixir-format +msgid "esc to close" msgstr "" diff --git a/specs/server.allium b/specs/server.allium index eb4b157..6966a62 100644 --- a/specs/server.allium +++ b/specs/server.allium @@ -4,9 +4,10 @@ -- Distilled from: lib/bds/server.ex, lib/bds/application.ex, lib/bds/tui.ex entity BootMode { - kind: desktop | server + kind: desktop | server | tui -- Resolved from the BDS_MODE environment variable at application start. - -- Anything other than "server" (case-insensitive) is desktop. + -- "server" is headless; "tui" is the headless server plus a TUI on the + -- launching terminal; anything else is desktop. } entity SshKeyMaterial { diff --git a/specs/tui.allium b/specs/tui.allium new file mode 100644 index 0000000..58ca55e --- /dev/null +++ b/specs/tui.allium @@ -0,0 +1,70 @@ +-- allium: 1 +-- Terminal UI (issue #26, phase 4) +-- Scope: extension (second renderer over the shared UI core) +-- Distilled from: lib/bds/tui.ex, lib/bds/ui/sidebar.ex, lib/bds/ui/post_editor/*.ex + +entity TuiState { + view: posts | media | templates | scripts | tags + focus: sidebar | editor + selected_index: Integer + editing_post: Boolean + -- The editor drives the same BDS.UI.PostEditor workflow as the GUI: + -- canonical-language edits update the post, other languages update + -- translations; publish routes through the same publishing pipeline. +} + +surface TuiSurface { + facing _: TuiRuntime + + provides: + TuiKeyPressed(code, modifiers) + TuiEventReceived(entity, entity_id, action) + TuiSettingsChanged(key) +} + +rule SidebarNavigation { + when: TuiKeyPressed(code: "up" | "down" | "j" | "k") + -- Selection moves over the flattened sidebar items and skips + -- section headers; data comes from BDS.UI.Sidebar.view, identical + -- to the GUI sidebar content. + ensures: TuiState.selected_index.updated() +} + +rule OpenEntry { + when: TuiKeyPressed(code: "enter") + -- Posts open in the editor (title + textarea seeded from the + -- persisted form). Images open in the terminal image preview. + -- Other entities report that terminal editing is not available yet. + ensures: TuiState.editing_post.updated() +} + +rule SaveAndPublish { + when: TuiKeyPressed(code: "s" | "p", modifiers: "ctrl") + -- ctrl+s saves the draft, ctrl+p saves and publishes — both through + -- BDS.UI.PostEditor.Persistence, so files, embeddings, search, and + -- the event bus behave exactly as a GUI save. + ensures: PostPersisted() +} + +rule AiQuickAction { + when: TuiKeyPressed(code: "g", modifiers: "ctrl") + -- One-shot AI post analysis (title/excerpt suggestions). Gated by + -- airplane mode: without a local endpoint the TUI shows a status + -- message instead of calling out. + ensures: AiSuggestionsRequestedOrRefused() +} + +rule MultiClientSync { + when: TuiEventReceived(entity, entity_id, action) + -- Domain events refresh the sidebar; a post deleted elsewhere closes + -- the open editor. Keeps TUI sessions synchronized with GUI clients + -- and pipeline ingestion. + ensures: TuiState.updated() +} + +rule ServerSideLocale { + when: TuiSettingsChanged(key: "ui.language") + -- The TUI re-reads the server-side UI language and re-renders; all + -- clients always share one language. + ensures: TuiRelocalized() +} diff --git a/test/bds/server_test.exs b/test/bds/server_test.exs index 6291479..5fa8069 100644 --- a/test/bds/server_test.exs +++ b/test/bds/server_test.exs @@ -14,6 +14,10 @@ defmodule BDS.ServerTest do assert Server.mode("SERVER") == :server end + test "recognizes tui mode" do + assert Server.mode("tui") == :tui + end + test "unknown values fall back to :desktop" do assert Server.mode("garbage") == :desktop end @@ -103,6 +107,11 @@ defmodule BDS.ServerTest do end) end + test "tui mode is the server plus a local TUI" do + assert BDS.Application.mode_children(:tui, :prod) == + BDS.Application.mode_children(:server, :prod) ++ [{BDS.TUI, []}] + end + test "desktop mode delegates to desktop_children" do assert BDS.Application.mode_children(:desktop, :test) == BDS.Application.desktop_children(:test) diff --git a/test/bds/tui_test.exs b/test/bds/tui_test.exs new file mode 100644 index 0000000..35a05f4 --- /dev/null +++ b/test/bds/tui_test.exs @@ -0,0 +1,135 @@ +defmodule BDS.TUITest do + use ExUnit.Case, async: false + + import Ecto.Query + + alias ExRatatui.CellSession + alias ExRatatui.Event.Key + + defp post_count(project_id) do + BDS.Repo.aggregate(from(p in BDS.Posts.Post, where: p.project_id == ^project_id), :count) + end + + setup do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo) + Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()}) + on_exit(fn -> Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, :manual) end) + + temp_dir = Path.join(System.tmp_dir!(), "bds-tui-#{System.unique_integer([:positive])}") + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf(temp_dir) end) + + {:ok, project} = BDS.Projects.create_project(%{name: "TUI", data_path: temp_dir}) + {:ok, _} = BDS.Projects.set_active_project(project.id) + + {:ok, post} = + BDS.Posts.create_post(%{ + project_id: project.id, + title: "Hello TUI", + content: "terminal body", + language: "en" + }) + + %{project: project, post: post} + end + + defp mount!(opts \\ []) do + {:ok, state} = BDS.TUI.mount(opts) + state + end + + defp screen_text(state, width \\ 100, height \\ 32) do + session = CellSession.new(width, height) + frame = %ExRatatui.Frame{width: width, height: height} + :ok = CellSession.draw(session, BDS.TUI.render(state, frame)) + snapshot = CellSession.take_cells(session) + :ok = CellSession.close(session) + + snapshot.cells + |> Enum.group_by(& &1.row) + |> Enum.sort_by(fn {row, _} -> row end) + |> Enum.map_join("\n", fn {_row, cells} -> + cells |> Enum.sort_by(& &1.col) |> Enum.map_join("", & &1.symbol) + end) + end + + defp key(code, modifiers \\ []), do: %Key{code: code, kind: "press", modifiers: modifiers} + + defp press(state, code, modifiers \\ []) do + {:noreply, state} = BDS.TUI.handle_event(key(code, modifiers), state) + state + end + + test "mounts against the active project and lists posts", %{post: post} do + state = mount!() + + assert state.project_id == post.project_id + text = screen_text(state) + assert text =~ "Hello TUI" + end + + test "navigates and opens a post in the editor", %{post: post} do + state = mount!() |> press("enter") + + assert state.focus == :editor + assert state.editor.post.id == post.id + assert ExRatatui.textarea_get_value(state.editor.textarea) == "terminal body" + assert screen_text(state) =~ "terminal body" + end + + test "ctrl+s persists textarea edits", %{post: post} do + state = mount!() |> press("enter") + + :ok = ExRatatui.textarea_insert_str(state.editor.textarea, "more words ") + state = press(state, "s", ["ctrl"]) + + assert state.editor.dirty == false + assert BDS.Posts.get_post(post.id).content =~ "more words" + end + + test "ctrl+p publishes the open post", %{post: post} do + state = mount!() |> press("enter") |> press("p", ["ctrl"]) + + published = BDS.Posts.get_post(post.id) + assert published.status == :published + assert state.editor.post.status == :published + end + + test "n creates a new draft post", %{project: project} do + count_before = post_count(project.id) + state = mount!() |> press("n") + + assert post_count(project.id) == count_before + 1 + assert state.focus == :editor + assert state.editor.post.status == :draft + end + + test "esc returns focus to the sidebar" do + state = mount!() |> press("enter") |> press("esc") + assert state.focus == :sidebar + end + + test "entity_changed refreshes the sidebar", %{project: project} do + state = mount!() + + {:ok, other} = + BDS.Posts.create_post(%{project_id: project.id, title: "From Pipeline", content: "x"}) + + {:noreply, state} = + BDS.TUI.handle_info( + {:entity_changed, %{entity: "post", entity_id: other.id, action: :created}}, + state + ) + + assert screen_text(state) =~ "From Pipeline" + end + + test "view switching shows media view" do + state = mount!() |> press("2") + assert state.view == "media" + end + + test "quit key stops the app" do + assert {:stop, _state} = BDS.TUI.handle_event(key("q", ["ctrl"]), mount!()) + end +end