fixes
This commit is contained in:
11
src/app.rs
11
src/app.rs
@@ -85,10 +85,15 @@ pub struct LoginForm {
|
||||
impl LoginForm {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
url: String::from("https://docmost.nakano47.com"),
|
||||
email: String::from("chamagua1@proton.me"),
|
||||
url: std::env::var("DOCMOST_URL")
|
||||
.unwrap_or_else(|_| "http://localhost:3000".to_string()),
|
||||
email: std::env::var("DOCMOST_EMAIL").unwrap_or_default(),
|
||||
password: String::new(),
|
||||
active_field: LoginField::Email,
|
||||
active_field: if std::env::var("DOCMOST_EMAIL").is_ok() {
|
||||
LoginField::Password
|
||||
} else {
|
||||
LoginField::Email
|
||||
},
|
||||
error: None,
|
||||
submitting: false,
|
||||
}
|
||||
|
||||
169
src/ui.rs
169
src/ui.rs
@@ -7,19 +7,28 @@ use ratatui::{
|
||||
};
|
||||
use tui_textarea::TextArea;
|
||||
|
||||
use crate::app::{EditorFocus, EditorStatus, EditorView, LoginField, LoginForm, MainView, Panel, SearchView};
|
||||
use crate::app::{
|
||||
EditorFocus, EditorStatus, EditorView, LoginField, LoginForm, MainView, Panel, SearchView,
|
||||
};
|
||||
|
||||
// ─── Login screen ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub fn draw_login(f: &mut Frame, login: &LoginForm) {
|
||||
let size = f.size();
|
||||
|
||||
f.render_widget(Block::default().style(Style::default().bg(Color::Black)), size);
|
||||
f.render_widget(
|
||||
Block::default().style(Style::default().bg(Color::Black)),
|
||||
size,
|
||||
);
|
||||
|
||||
let dialog = centered_rect(50, 18, size);
|
||||
f.render_widget(Clear, dialog);
|
||||
|
||||
let title = if login.submitting { "Logging in…" } else { "Login — Docmost" };
|
||||
let title = if login.submitting {
|
||||
"Logging in…"
|
||||
} else {
|
||||
"Login — Docmost"
|
||||
};
|
||||
f.render_widget(
|
||||
Block::default()
|
||||
.title(title)
|
||||
@@ -46,9 +55,30 @@ pub fn draw_login(f: &mut Frame, login: &LoginForm) {
|
||||
])
|
||||
.split(inner);
|
||||
|
||||
render_field(f, "Server URL", &login.url, false, login.active_field == LoginField::Url, rows[0]);
|
||||
render_field(f, "Email", &login.email, false, login.active_field == LoginField::Email, rows[1]);
|
||||
render_field(f, "Password", &login.password, true, login.active_field == LoginField::Password, rows[2]);
|
||||
render_field(
|
||||
f,
|
||||
"Server URL",
|
||||
&login.url,
|
||||
false,
|
||||
login.active_field == LoginField::Url,
|
||||
rows[0],
|
||||
);
|
||||
render_field(
|
||||
f,
|
||||
"Email",
|
||||
&login.email,
|
||||
false,
|
||||
login.active_field == LoginField::Email,
|
||||
rows[1],
|
||||
);
|
||||
render_field(
|
||||
f,
|
||||
"Password",
|
||||
&login.password,
|
||||
true,
|
||||
login.active_field == LoginField::Password,
|
||||
rows[2],
|
||||
);
|
||||
|
||||
let hint = if let Some(err) = &login.error {
|
||||
Paragraph::new(Line::from(Span::styled(
|
||||
@@ -74,7 +104,11 @@ fn render_field(
|
||||
active: bool,
|
||||
area: Rect,
|
||||
) {
|
||||
let display = if masked { "*".repeat(value.len()) } else { value.to_string() };
|
||||
let display = if masked {
|
||||
"*".repeat(value.len())
|
||||
} else {
|
||||
value.to_string()
|
||||
};
|
||||
let content = format!("{display}{}", if active { "▌" } else { "" });
|
||||
let border_style = if active {
|
||||
Style::default().fg(Color::Cyan)
|
||||
@@ -82,8 +116,12 @@ fn render_field(
|
||||
Style::default().fg(Color::DarkGray)
|
||||
};
|
||||
f.render_widget(
|
||||
Paragraph::new(content)
|
||||
.block(Block::default().title(label).borders(Borders::ALL).border_style(border_style)),
|
||||
Paragraph::new(content).block(
|
||||
Block::default()
|
||||
.title(label)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style),
|
||||
),
|
||||
area,
|
||||
);
|
||||
}
|
||||
@@ -105,12 +143,22 @@ pub fn draw_main(f: &mut Frame, main: &MainView) {
|
||||
|
||||
// ── Spaces panel (top-left) ──
|
||||
let spaces_focused = main.focus == Panel::Spaces;
|
||||
let spaces_title = if main.loading_spaces { "Spaces (loading…)" } else { "Spaces" };
|
||||
let spaces_title = if main.loading_spaces {
|
||||
"Spaces (loading…)"
|
||||
} else {
|
||||
"Spaces"
|
||||
};
|
||||
|
||||
let space_items: Vec<ListItem> = if main.spaces.is_empty() && !main.loading_spaces {
|
||||
vec![ListItem::new(Span::styled("No spaces", Style::default().fg(Color::DarkGray)))]
|
||||
vec![ListItem::new(Span::styled(
|
||||
"No spaces",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
))]
|
||||
} else {
|
||||
main.spaces.iter().map(|s| ListItem::new(s.name.as_str())).collect()
|
||||
main.spaces
|
||||
.iter()
|
||||
.map(|s| ListItem::new(s.name.as_str()))
|
||||
.collect()
|
||||
};
|
||||
|
||||
f.render_stateful_widget(
|
||||
@@ -121,7 +169,11 @@ pub fn draw_main(f: &mut Frame, main: &MainView) {
|
||||
.borders(Borders::ALL)
|
||||
.border_style(panel_border(spaces_focused)),
|
||||
)
|
||||
.highlight_style(Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD))
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Blue)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol("▶ "),
|
||||
left_layout[0],
|
||||
&mut list_state(main.selected_space),
|
||||
@@ -129,10 +181,17 @@ pub fn draw_main(f: &mut Frame, main: &MainView) {
|
||||
|
||||
// ── Pages panel (bottom-left) ──
|
||||
let pages_focused = main.focus == Panel::Pages;
|
||||
let pages_title = if main.loading_pages { "Pages (loading…)" } else { "Pages" };
|
||||
let pages_title = if main.loading_pages {
|
||||
"Pages (loading…)"
|
||||
} else {
|
||||
"Pages"
|
||||
};
|
||||
|
||||
let page_items: Vec<ListItem> = if main.pages.is_empty() && !main.loading_pages {
|
||||
vec![ListItem::new(Span::styled("No pages", Style::default().fg(Color::DarkGray)))]
|
||||
vec![ListItem::new(Span::styled(
|
||||
"No pages",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
))]
|
||||
} else {
|
||||
main.pages
|
||||
.iter()
|
||||
@@ -148,7 +207,11 @@ pub fn draw_main(f: &mut Frame, main: &MainView) {
|
||||
.borders(Borders::ALL)
|
||||
.border_style(panel_border(pages_focused)),
|
||||
)
|
||||
.highlight_style(Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD))
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Blue)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol("▶ "),
|
||||
left_layout[1],
|
||||
&mut list_state(main.selected_page),
|
||||
@@ -160,10 +223,18 @@ pub fn draw_main(f: &mut Frame, main: &MainView) {
|
||||
} else if let Some(err) = &main.error {
|
||||
format!("Error: {err}")
|
||||
} else {
|
||||
let space_name = main.spaces.get(main.selected_space).map(|s| s.name.as_str()).unwrap_or("-");
|
||||
let page_title = main.pages.get(main.selected_page).and_then(|p| p.title.as_deref()).unwrap_or("-");
|
||||
let space_name = main
|
||||
.spaces
|
||||
.get(main.selected_space)
|
||||
.map(|s| s.name.as_str())
|
||||
.unwrap_or("-");
|
||||
let page_title = main
|
||||
.pages
|
||||
.get(main.selected_page)
|
||||
.and_then(|p| p.title.as_deref())
|
||||
.unwrap_or("-");
|
||||
format!(
|
||||
"Space: {space_name}\nPage: {page_title}\n\nEnter open editor\nTab switch panel\n↑↓/j k navigate\nq/Esc quit"
|
||||
"Space: {space_name}\nPage: {page_title}\n\n_______________________\nEnter open editor\nTab switch panel\n↑↓/j k navigate\nq/Esc quit\n//Ctrl+F search"
|
||||
)
|
||||
};
|
||||
|
||||
@@ -202,8 +273,12 @@ pub fn draw_editor(f: &mut Frame, editor: &EditorView, textarea: &mut TextArea<'
|
||||
if title_focused { "▌" } else { "" }
|
||||
);
|
||||
f.render_widget(
|
||||
Paragraph::new(title_display)
|
||||
.block(Block::default().title(" Title ").borders(Borders::ALL).border_style(title_border)),
|
||||
Paragraph::new(title_display).block(
|
||||
Block::default()
|
||||
.title(" Title ")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(title_border),
|
||||
),
|
||||
layout[0],
|
||||
);
|
||||
|
||||
@@ -229,15 +304,21 @@ pub fn draw_editor(f: &mut Frame, editor: &EditorView, textarea: &mut TextArea<'
|
||||
// ── Status bar ──
|
||||
let status_line = match &editor.status {
|
||||
Some(EditorStatus::Saved) => Line::from(vec![
|
||||
Span::styled(" Saved! ", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)),
|
||||
Span::styled(" Ctrl+S: Save · Tab: Switch field · Esc: Back", Style::default().fg(Color::DarkGray)),
|
||||
]),
|
||||
Some(EditorStatus::Error(e)) => Line::from(vec![
|
||||
Span::styled(
|
||||
format!(" Error: {e} "),
|
||||
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
|
||||
" Saved! ",
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(
|
||||
" Ctrl+S: Save · Tab: Switch field · Esc: Back",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
),
|
||||
]),
|
||||
Some(EditorStatus::Error(e)) => Line::from(vec![Span::styled(
|
||||
format!(" Error: {e} "),
|
||||
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
None => Line::from(Span::styled(
|
||||
" Ctrl+S: Save · Tab: Switch field · Esc: Back",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
@@ -262,7 +343,11 @@ pub fn draw_search(f: &mut Frame, search: &SearchView) {
|
||||
|
||||
// ── Query input ──
|
||||
let query_display = format!("{}▌", search.query);
|
||||
let query_title = if search.loading { " Search (searching…) " } else { " Search " };
|
||||
let query_title = if search.loading {
|
||||
" Search (searching…) "
|
||||
} else {
|
||||
" Search "
|
||||
};
|
||||
f.render_widget(
|
||||
Paragraph::new(query_display).block(
|
||||
Block::default()
|
||||
@@ -281,8 +366,15 @@ pub fn draw_search(f: &mut Frame, search: &SearchView) {
|
||||
|
||||
// Results list
|
||||
let result_items: Vec<ListItem> = if search.results.is_empty() && !search.loading {
|
||||
let msg = if search.query.is_empty() { "Type to search…" } else { "No results" };
|
||||
vec![ListItem::new(Span::styled(msg, Style::default().fg(Color::DarkGray)))]
|
||||
let msg = if search.query.is_empty() {
|
||||
"Type to search…"
|
||||
} else {
|
||||
"No results"
|
||||
};
|
||||
vec![ListItem::new(Span::styled(
|
||||
msg,
|
||||
Style::default().fg(Color::DarkGray),
|
||||
))]
|
||||
} else {
|
||||
search
|
||||
.results
|
||||
@@ -306,7 +398,11 @@ pub fn draw_search(f: &mut Frame, search: &SearchView) {
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::White)),
|
||||
)
|
||||
.highlight_style(Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD))
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Blue)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol("▶ "),
|
||||
mid[0],
|
||||
&mut list_state(search.selected),
|
||||
@@ -319,7 +415,11 @@ pub fn draw_search(f: &mut Frame, search: &SearchView) {
|
||||
format!("Error: {err}")
|
||||
} else if let Some(result) = search.results.get(search.selected) {
|
||||
let title = result.title.as_deref().unwrap_or("Untitled");
|
||||
let space = result.space.as_ref().map(|s| s.name.as_str()).unwrap_or("-");
|
||||
let space = result
|
||||
.space
|
||||
.as_ref()
|
||||
.map(|s| s.name.as_str())
|
||||
.unwrap_or("-");
|
||||
let highlight = result
|
||||
.highlight
|
||||
.as_deref()
|
||||
@@ -348,7 +448,10 @@ pub fn draw_search(f: &mut Frame, search: &SearchView) {
|
||||
|
||||
// ── Hint bar ──
|
||||
let hint = if search.opening_page {
|
||||
Line::from(Span::styled(" Opening…", Style::default().fg(Color::Yellow)))
|
||||
Line::from(Span::styled(
|
||||
" Opening…",
|
||||
Style::default().fg(Color::Yellow),
|
||||
))
|
||||
} else {
|
||||
Line::from(Span::styled(
|
||||
" ↑↓: navigate · Enter: open · Esc: back",
|
||||
|
||||
Reference in New Issue
Block a user