chore(deps): 升级依赖并新增正则与位运算相关库

This commit is contained in:
2026-04-14 18:04:27 +08:00
parent ade2b6ba16
commit 885ba280b4
5 changed files with 971 additions and 141 deletions

924
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ description = "A command-line tool to manage memos"
license = "MIT"
[dependencies]
ratatui = "0.28"
crossterm = "0.28"
ratatui = "0.30"
crossterm = "0.29"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,7 +1,6 @@
use crate::models::{CreateMemoRequest, CreateMemoResponse, ListMemosResponse, Memo};
use anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
#[derive(Clone)]
pub struct MemosClient {

View File

@@ -167,6 +167,67 @@ async fn run_tui_mode() -> Result<()> {
match app.mode {
AppMode::MainMenu => {
match key.code {
KeyCode::Up => {
if let Some(selected) = app.list_state.selected() {
if selected > 0 {
app.list_state.select(Some(selected - 1));
}
} else {
app.list_state.select(Some(0));
}
}
KeyCode::Down => {
if let Some(selected) = app.list_state.selected() {
if selected < 3 { // 4 items, 0-3 index
app.list_state.select(Some(selected + 1));
}
} else {
app.list_state.select(Some(0));
}
}
KeyCode::Enter => {
if let Some(selected) = app.list_state.selected() {
match selected {
0 => { // List Memos
if app.config.is_configured() {
app.loading = true;
terminal.draw(|f| tui::render_app(f, &mut app))?;
match load_memos(&app).await {
Ok(memos) => {
app.memos = memos;
app.mode = AppMode::ListMemos;
app.message = Some("Memos loaded successfully".to_string());
}
Err(e) => {
app.message = Some(format!("Error: {}", e));
}
}
app.loading = false;
} else {
app.message = Some("Please configure first".to_string());
}
}
1 => { // Create Memo
if app.config.is_configured() {
app.input_content.clear();
app.input_visibility = "PRIVATE".to_string();
app.mode = AppMode::CreateMemo;
} else {
app.message = Some("Please configure first".to_string());
}
}
2 => { // Configuration
app.input_base_url = app.config.base_url.clone();
app.input_token = app.config.user_token.clone();
app.mode = AppMode::Config;
}
3 => { // Quit
break;
}
_ => {}
}
}
}
KeyCode::Char('1') => {
if app.config.is_configured() {
app.loading = true;

View File

@@ -2,15 +2,14 @@ use crate::config::Config;
use crate::client::MemosClient;
use crate::models::Memo;
use anyhow::Result;
use crossterm::event::{Event, KeyCode, KeyEventKind};
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap};
use ratatui::widgets::{Block, Borders, Clear, List, ListDirection, ListItem, ListState, Paragraph};
use ratatui::Frame;
use std::sync::Arc;
use tokio::sync::Mutex;
use chrono::Local;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppMode {
@@ -35,10 +34,13 @@ pub struct App {
pub loading: bool,
client: Arc<Mutex<Option<MemosClient>>>,
pub list_selected: Option<usize>,
pub list_state: ListState,
}
impl App {
pub fn new(config: Config) -> Self {
let mut list_state = ListState::default();
list_state.select(Some(0));
Self {
mode: AppMode::MainMenu,
config,
@@ -53,6 +55,7 @@ impl App {
loading: false,
client: Arc::new(Mutex::new(None)),
list_selected: None,
list_state,
}
}
@@ -90,7 +93,10 @@ pub fn render_app(frame: &mut Frame, app: &mut App) {
}
if let Some(msg) = &app.message {
let area = Rect::new(10, frame.area().height - 3, frame.area().width - 10, frame.area().height - 1);
let y = frame.area().height.saturating_sub(3);
let height = 2;
if y + height <= frame.area().height {
let area = Rect::new(10, y, frame.area().width - 20, height);
let block = Block::default()
.borders(Borders::ALL)
.style(Style::default().fg(Color::Yellow));
@@ -100,14 +106,15 @@ pub fn render_app(frame: &mut Frame, app: &mut App) {
.alignment(Alignment::Center);
frame.render_widget(text, area);
}
}
if app.loading {
let area = Rect::new(
frame.area().width / 2 - 10,
frame.area().height / 2 - 3,
frame.area().width / 2 + 10,
frame.area().height / 2 + 3,
);
let width = 20;
let height = 6;
let x = (frame.area().width / 2).saturating_sub(width / 2);
let y = (frame.area().height / 2).saturating_sub(height / 2);
if x + width <= frame.area().width && y + height <= frame.area().height {
let area = Rect::new(x, y, width, height);
let block = Block::default()
.borders(Borders::ALL)
.title("Loading...")
@@ -116,23 +123,54 @@ pub fn render_app(frame: &mut Frame, app: &mut App) {
frame.render_widget(block, area);
}
}
}
fn render_main_menu(frame: &mut Frame, app: &App) {
fn render_main_menu(frame: &mut Frame, app: &mut App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(3),
Constraint::Length(6), // 艺术字体标题
Constraint::Length(1), // 产品版本
Constraint::Length(1), // 作者及联系方式
Constraint::Length(1), // 数字时钟
Constraint::Min(0), // 功能选择
Constraint::Length(2), // 特殊提示
])
.split(frame.area());
let title = Paragraph::new("Memos CLI")
// 艺术字体标题 (Memos CLI)
let art_title = vec![
Line::from("███╗ ███╗███████╗███╗ ███╗ ██████╗ ███████╗".to_string()),
Line::from("████╗ ████║██╔════╝████╗ ████║██╔═══██╗██╔════╝".to_string()),
Line::from("██╔████╔██║█████╗ ██╔████╔██║██║ ██║███████╗".to_string()),
Line::from("██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║██║ ██║╚════██║".to_string()),
Line::from("██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝███████║".to_string()),
Line::from("╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝".to_string()),
];
let title = Paragraph::new(art_title)
.style(Style::default().fg(Color::Cyan).bold())
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).title("Welcome"));
.alignment(Alignment::Center);
frame.render_widget(title, chunks[0]);
// 产品版本
let version = Paragraph::new("Version: 1.0.0")
.style(Style::default().fg(Color::White))
.alignment(Alignment::Center);
frame.render_widget(version, chunks[1]);
// 作者及联系方式
let author = Paragraph::new("Author: Your Name <your@email.com>")
.style(Style::default().fg(Color::White))
.alignment(Alignment::Center);
frame.render_widget(author, chunks[2]);
// 数字时钟
let current_time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
let clock = Paragraph::new(format!("🕒 {}", current_time))
.style(Style::default().fg(Color::Yellow));
frame.render_widget(clock, chunks[3]);
// 功能选择
let items = vec![
("1", "List Memos"),
("2", "Create Memo"),
@@ -155,18 +193,16 @@ fn render_main_menu(frame: &mut Frame, app: &App) {
.collect();
let menu = List::new(menu_items)
.block(Block::default().borders(Borders::ALL).title("Menu"));
frame.render_widget(menu, chunks[1]);
.block(Block::default().borders(Borders::ALL).title("Menu"))
.highlight_style(Style::default().bg(Color::DarkGray).fg(Color::Cyan).bold())
.direction(ListDirection::TopToBottom);
frame.render_stateful_widget(menu, chunks[4], &mut app.list_state);
let status = if app.config.is_configured() {
format!("Connected to: {}", app.config.base_url)
} else {
"Not configured".to_string()
};
let status_bar = Paragraph::new(status)
.style(Style::default().fg(Color::Green))
// 特殊提示
let hint = Paragraph::new("↑↓: Navigate | Enter: Select | Q: Quit")
.style(Style::default().fg(Color::DarkGray))
.alignment(Alignment::Center);
frame.render_widget(status_bar, chunks[2]);
frame.render_widget(hint, chunks[5]);
}
fn render_config(frame: &mut Frame, app: &mut App) {