feat: issue #32 markdown editor for TUI

This commit is contained in:
2026-07-17 16:38:46 +02:00
parent 2fd132e827
commit 46c239df56
8 changed files with 1042 additions and 51 deletions

View File

@@ -25,8 +25,8 @@ defmodule BDS.TUI do
`yyyy-mm-dd`; enter keeps the filter, esc clears it),
`:` vi-style command prompt over the Blog-menu shell commands, `?`
command help (commands whose report/apply UI is GUI-only are marked).
Posts open in the rendered markdown preview (new empty posts open in
the editor). Editor focus: `ctrl+e` toggles preview/editor, text keys
Posts open in the syntax-highlighted, word-wrapping editor. Editor
focus: `ctrl+e` toggles editor/rendered-markdown preview, 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
@@ -42,6 +42,7 @@ defmodule BDS.TUI do
alias BDS.Git
alias BDS.Posts
alias BDS.Projects
alias BDS.UI.CodeEditor
alias BDS.UI.PostEditor.{Draft, Metadata, Persistence}
alias BDS.UI.SettingsForm
alias BDS.UI.Sidebar
@@ -49,7 +50,7 @@ defmodule BDS.TUI do
alias ExRatatui.Layout
alias ExRatatui.Layout.Rect
alias ExRatatui.Style
alias ExRatatui.Widgets.{Block, Clear, List, Markdown, Paragraph, Tabs, Textarea}
alias ExRatatui.Widgets.{Block, Clear, List, Markdown, Paragraph, Tabs}
@views ~w(posts media templates scripts tags settings git)
@git_diff_scroll_step 10
@@ -230,8 +231,7 @@ defmodule BDS.TUI do
when is_binary(project_id) do
case Posts.create_post(%{project_id: project_id, title: "", content: ""}) do
{:ok, post} ->
# A fresh, empty post starts in the editor — there is nothing to
# preview yet; existing posts open in the preview instead.
# A fresh, empty post starts in the editor — like every post.
{:noreply, state |> load_sidebar() |> open_post(post.id, false)}
{:error, reason} ->
@@ -1448,10 +1448,9 @@ defmodule BDS.TUI do
[title_widget, body_widget(state, editor, body_rect)]
end
# The editing textarea cannot soft-wrap (upstream ratatui-textarea
# limitation), so ctrl+e flips to a word-wrapped read-only Markdown
# preview of the current draft. Wrap moves into the editor itself with
# the planned MarkdownEditor custom widget.
# ctrl+e flips to a word-wrapped read-only Markdown preview of the
# current draft; the editor itself is a syntax-highlighted, wrapping
# BDS.UI.CodeEditor over the same Rust-owned textarea.
defp body_widget(_state, %{preview?: true} = editor, body_rect) do
preview_title =
"#{dgettext("ui", "Preview (ctrl+e to edit)")} [#{editor.language}]"
@@ -1468,14 +1467,14 @@ defmodule BDS.TUI do
"#{dgettext("ui", "Content")} [#{editor.language}]" <>
if(editor.dirty, do: "", else: "")
{%Textarea{
state: editor.textarea,
focused? = state.focus == :editor and not editor.editing_title?
{%CodeEditor{
textarea: editor.textarea,
language: :markdown_macros,
block: %Block{title: body_title, borders: [:all]},
cursor_style:
if(state.focus == :editor and not editor.editing_title?,
do: %Style{bg: :cyan},
else: %Style{}
)
cursor_style: if(focused?, do: %Style{bg: :cyan}, else: %Style{}),
line_highlight_style: if(focused?, do: %Style{bg: :dark_gray}, else: %Style{})
}, body_rect}
end
@@ -1945,9 +1944,9 @@ defmodule BDS.TUI do
# ── Post editor ──────────────────────────────────────────────────────────
# Posts open in the rendered markdown preview by default — the editor is
# one ctrl+e away. New (empty) posts pass preview?: false.
defp open_post(state, post_id, preview? \\ true) do
# Posts open in the syntax-highlighted editor by default — the rendered
# markdown preview is one ctrl+e away.
defp open_post(state, post_id, preview? \\ false) do
case Posts.get_post(post_id) do
nil ->
toast(state, dgettext("ui", "Posts"), dgettext("ui", "Post not found."))