This commit is contained in:
2026-04-11 16:00:05 -06:00
parent 316de93781
commit d2d2bf1477
4 changed files with 329 additions and 66 deletions

View File

@@ -140,3 +140,150 @@
2026-04-11T21:27:43.593415Z INFO docmost_rust::api: search_pages: 1 results
2026-04-11T21:27:43.650468Z INFO docmost_rust: search results: 1 items
2026-04-11T21:28:04.431585Z INFO docmost_rust: --- startup ---
2026-04-11T21:29:39.247050Z INFO docmost_rust: --- startup ---
2026-04-11T21:29:47.431125Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:29:47.706907Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:29:47.706967Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:29:47.984710Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:29:48.893873Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:29:49.325584Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:29:50.049917Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:29:50.442560Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:29:51.037887Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:29:51.830763Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:29:58.166181Z INFO docmost_rust::api: fetch_page_content: POST https://docmost.nakano47.com/api/pages/info page_id=019ab71d-b515-7225-9d91-389a48b7c1db
2026-04-11T21:29:58.410173Z INFO docmost_rust::api: fetch_page_content: status=200 OK body_len=886
2026-04-11T21:29:58.410210Z INFO docmost_rust::api: fetch_page_content: raw body = {"data":{"id":"019ab71d-b515-7225-9d91-389a48b7c1db","slugId":"g7X1eTyFHz","title":null,"icon":null,"coverPhoto":null,"position":"a179C","parentPageId":null,"creatorId":"019a8369-7137-75b6-b171-cb7e11111fa7","lastUpdatedById":"019a8369-7137-75b6-b171-cb7e11111fa7","spaceId":"019aa2cc-4c7b-7408-a43a-6e17f03abc96","workspaceId":"019a8369-713a-7aee-b3d9-45fb478c1f81","isLocked":false,"createdAt":"2025-11-24T18:26:08.785Z","updatedAt":"2025-11-24T18:26:08.785Z","deletedAt":null,"contributorIds":[],"content":null,"creator":{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null},"lastUpdatedBy":{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null},"contributors":[],"space":{"id":"019aa2cc-4c7b-7408-a43a-6e17f03abc96","name":"Útil Links","slug":"TL"},"permissions":{"canEdit":true,"hasRestriction":false}},"success":true,"status":200}
2026-04-11T21:29:58.410322Z INFO docmost_rust::api: fetch_page_content: top-level keys = Some(["data", "status", "success"])
2026-04-11T21:29:58.410344Z INFO docmost_rust::api: fetch_page_content: extracted content len=0
2026-04-11T21:29:58.418609Z INFO docmost_rust: page content loaded: id=019ab71d-b515-7225-9d91-389a48b7c1db title="Untitled" content_len=0
2026-04-11T21:29:58.418653Z INFO docmost_rust: content preview: ""
2026-04-11T21:29:58.418664Z INFO docmost_rust: textarea lines: 0
2026-04-11T21:29:58.418673Z INFO docmost_rust: state → Editor
2026-04-11T21:30:05.112248Z INFO docmost_rust::api: fetch_page_content: POST https://docmost.nakano47.com/api/pages/info page_id=019c3404-7aa7-7571-9ead-6d672d02cb23
2026-04-11T21:30:05.400130Z INFO docmost_rust::api: fetch_page_content: status=200 OK body_len=1403
2026-04-11T21:30:05.400209Z INFO docmost_rust::api: fetch_page_content: raw body = {"data":{"id":"019c3404-7aa7-7571-9ead-6d672d02cb23","slugId":"YjWODAMZOb","title":"Download list","icon":null,"coverPhoto":null,"position":"a26Jb","parentPageId":null,"creatorId":"019a8369-7137-75b6-b171-cb7e11111fa7","lastUpdatedById":"019a8369-7137-75b6-b171-cb7e11111fa7","spaceId":"019aa2cc-4c7b-7408-a43a-6e17f03abc96","workspaceId":"019a8369-713a-7aee-b3d9-45fb478c1f81","isLocked":false,"createdAt":"2026-02-06T17:33:54.663Z","updatedAt":"2026-02-06T17:34:18.052Z","deletedAt":null,"contributorIds":["019a8369-7137-75b6-b171-cb7e11111fa7"],"content":"https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbG5lM3p1b0I5dXdtcXdER2l4T1FZN05sajVuZ3xBQ3Jtc0tuWklOVzRTZ2pXZmpQLWFWNnM5YUJKVXNlOWJCNERGWFUzOVN4bFJjS1RuU0xLRHg2c0tybjctWlFWdlFOTG9YTmRGT01DY2hmakd6REZJbHVtU082MG5qMlNfWGpVZFNWSExSMF92RDJjVF9DWWxOVQ&q=https%3A%2F%2Fdrive.google.com%2Ffile%2Fd%2F1aqUcK3cUB1hheswwrXQkKAWG65swElAA%2Fview%3Fusp%3Dsharing&v=9z2Z4Ul6PmQ","creator":{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null},"lastUpdatedBy":{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null},"contributors":[{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null}],"space":{"id":"019aa2cc-4c7b-7408-a43a-6e17f03abc96","name":"Útil Links","slug":"TL"},"permissions":{"canEdit":true,"hasRestriction":false}},"success":true,"status":200}
2026-04-11T21:30:05.400453Z INFO docmost_rust::api: fetch_page_content: top-level keys = Some(["data", "status", "success"])
2026-04-11T21:30:05.400492Z INFO docmost_rust::api: fetch_page_content: extracted content len=392
2026-04-11T21:30:05.440594Z INFO docmost_rust: page content loaded: id=019c3404-7aa7-7571-9ead-6d672d02cb23 title="Download list" content_len=392
2026-04-11T21:30:05.440682Z INFO docmost_rust: content preview: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbG5lM3p1b0I5dXdtcXdER2l4T1FZN05sajVuZ3xBQ3Jtc0tuWklOVzRTZ2pXZmpQLWFWNnM5YUJKVXNlOWJCNERGWFUzOVN4bFJjS1RuU0xLRHg2c0tybjctWlF"
2026-04-11T21:30:05.440742Z INFO docmost_rust: textarea lines: 1
2026-04-11T21:30:05.440769Z INFO docmost_rust: state → Editor
2026-04-11T21:30:30.910843Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:31.089038Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:32.712319Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:33.363095Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:33.872913Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:34.487430Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:30:35.118020Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:35.987499Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:30:37.374189Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:38.193984Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:39.688846Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:40.681715Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:30:41.394126Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:42.090226Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:42.600549Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:43.634279Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:43.807572Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:44.401381Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:44.682769Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:44.890249Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:30:45.401689Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:45.613548Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:45.727369Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:45.889612Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:30:46.378793Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:30:46.493106Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:30:46.701079Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:30:46.820834Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:35:13.428450Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:35:13.603118Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:35:13.915433Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:35:14.093044Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:35:14.825084Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:35:15.012750Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:35:15.231169Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:38:33.382406Z INFO docmost_rust: --- startup ---
2026-04-11T21:38:45.588348Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:38:45.875223Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:38:45.875293Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:38:46.204359Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:38:47.585723Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:38:47.974573Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:38:50.567544Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:38:51.146561Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:38:51.725761Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:38:52.474459Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:38:55.035281Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:38:57.256724Z INFO docmost_rust::api: fetch_page_content: POST https://docmost.nakano47.com/api/pages/info page_id=019ac0d0-c5d5-7e66-ae14-3014e35fbb92
2026-04-11T21:38:57.551645Z INFO docmost_rust::api: fetch_page_content: status=200 OK body_len=2067
2026-04-11T21:38:57.551731Z INFO docmost_rust::api: fetch_page_content: raw body = {"data":{"id":"019ac0d0-c5d5-7e66-ae14-3014e35fbb92","slugId":"Gyb4Wq5OSs","title":"NextJS 16 midudev","icon":null,"coverPhoto":null,"position":"a19xB","parentPageId":null,"creatorId":"019a8369-7137-75b6-b171-cb7e11111fa7","lastUpdatedById":"019a8369-7137-75b6-b171-cb7e11111fa7","spaceId":"019a948a-071a-7425-b427-78832d0f29f3","workspaceId":"019a8369-713a-7aee-b3d9-45fb478c1f81","isLocked":false,"createdAt":"2025-11-26T15:38:18.962Z","updatedAt":"2025-11-26T17:38:30.842Z","deletedAt":null,"contributorIds":["019a8369-7137-75b6-b171-cb7e11111fa7"],"content":"https://nextjs.org/docs\n\nhttps://www.youtube.com/watch?v=VosHdPQr6nI\n\n```\npnpx create-next-app@latest\n```\n\nesto crea todos los componentes por defecto, al darle que si.\n\nhttp://ui.shadcn.com/docs/components/\n\nen esa pagina se pueden encontrar componentes para no tener que hacerlos\n\n```\npnpx shadcn@latest init\npnpx shadcn@latest add card label input button \npnpm dev\n```\n\nCon este comando agregamos los componentes necesarios para el proyecto.\n\nAhora vamos a crear el backend, con strapi\n\n```\npnpx create-strapi-app@latest backend\npnpm approve-builds\n```\n\nluego contestar las preguntas que hace, no es necesario hacer login.\n\nluego de hacer el run develop se desplegara una pagina para registrarse en localhost. \nluego de llenar el formulario se hay que darle a Content-Type Builder\n\nCreate a single type\n\n![](/api/files/019ac112-ecf7-7208-8994-234185c895fb/image.png)\n\nluego se pueden agregar cosas\n\n_Strapi es una forma facil y rapida de crear APIS, con roles usuarios etc y tambien ofrece diferentes plugins_","creator":{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null},"lastUpdatedBy":{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null},"contributors":[{"id":"019a8369-7137-75b6-b171-cb7e11111fa7","name":"arthur","avatarUrl":null}],"space":{"id":"019a948a-071a-7425-b427-78832d0f29f3","name":"NextJS","slug":"nextjs"},"permissions":{"canEdit":true,"hasRestriction":false}},"success":true,"status":200}
2026-04-11T21:38:57.552092Z INFO docmost_rust::api: fetch_page_content: top-level keys = Some(["data", "status", "success"])
2026-04-11T21:38:57.552194Z INFO docmost_rust::api: fetch_page_content: extracted content len=1013
2026-04-11T21:38:57.554197Z INFO docmost_rust: page content loaded: id=019ac0d0-c5d5-7e66-ae14-3014e35fbb92 title="NextJS 16 midudev" content_len=1013
2026-04-11T21:38:57.554259Z INFO docmost_rust: content preview: "https://nextjs.org/docs\n\nhttps://www.youtube.com/watch?v=VosHdPQr6nI\n\n```\npnpx create-next-app@latest\n```\n\nesto crea todos los componentes por defecto, al darle que si.\n\nhttp://ui.shadcn.com/docs/comp"
2026-04-11T21:38:57.554817Z INFO docmost_rust: textarea lines: 41
2026-04-11T21:38:57.554845Z INFO docmost_rust: state → Editor
2026-04-11T21:40:04.606686Z INFO docmost_rust: --- startup ---
2026-04-11T21:40:10.031362Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:40:10.307498Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:40:10.307547Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:40:10.633254Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:40:11.444021Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:40:11.912537Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:40:12.726953Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:40:13.367085Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:40:15.001191Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:40:15.282960Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:40:15.602258Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:40:15.882600Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:45:15.235276Z INFO docmost_rust: --- startup ---
2026-04-11T21:45:19.977051Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:45:20.313747Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:45:20.313812Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:45:20.650391Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:45:22.308225Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:45:22.746924Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:45:23.559349Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:45:23.914877Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:45:24.804748Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:45:25.039559Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:45:25.316099Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:45:25.667300Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:45:25.978557Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:45:26.154212Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:45:26.437992Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:45:27.079480Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:45:27.634946Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:45:50.121538Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:45:51.897924Z INFO docmost_rust: --- startup ---
2026-04-11T21:45:57.189610Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:45:57.530089Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:45:57.530152Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:45:57.812070Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:46:06.541183Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:46:06.839324Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:46:06.998234Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:46:07.453598Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:55:24.646993Z INFO docmost_rust: --- startup ---
2026-04-11T21:56:00.568474Z INFO docmost_rust: --- startup ---
2026-04-11T21:56:11.943220Z INFO docmost_rust: --- startup ---
2026-04-11T21:56:14.296222Z INFO docmost_rust: login error: Cannot POST /auth/login
2026-04-11T21:56:27.622900Z INFO docmost_rust: login error: Cannot POST /auth/login
2026-04-11T21:56:31.879011Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:56:32.155496Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:56:32.155567Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:56:32.476120Z INFO docmost_rust: pages loaded: 1 pages
2026-04-11T21:56:33.189116Z INFO docmost_rust: pages loaded: 0 pages
2026-04-11T21:56:33.398639Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:56:33.785215Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:56:34.383027Z INFO docmost_rust: pages loaded: 3 pages
2026-04-11T21:56:35.160467Z INFO docmost_rust: pages loaded: 2 pages
2026-04-11T21:56:35.479213Z INFO docmost_rust: pages loaded: 8 pages
2026-04-11T21:56:49.755529Z INFO docmost_rust: --- startup ---
2026-04-11T21:57:00.511114Z INFO docmost_rust: --- startup ---
2026-04-11T21:57:03.860624Z INFO docmost_rust: login success, fetching spaces from https://docmost.nakano47.com/api
2026-04-11T21:57:04.176895Z INFO docmost_rust: spaces loaded: 5 spaces
2026-04-11T21:57:04.176933Z INFO docmost_rust: auto-loading pages for space 'Curso Cobol Platzi' (019b7051-6cde-7cfe-90cf-62787f33a2b0)
2026-04-11T21:57:04.493124Z INFO docmost_rust: pages loaded: 1 pages

View File

@@ -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
View File

@@ -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",

View File

@@ -1,39 +1,47 @@
can we implement Full-text search across pages ?
/btwhow is the process to install this program as a command on linux
to type "$ docmost-rust" on the console and the program starts,
also how to add env variables for $baseurl $email to
automatically load this values on the login screen
Yes, totally doable. Here's the honest breakdown:
What the Docmost API offers
cargo build --release
sudo cp target/release/docmost-rust /usr/local/bin/docmost-rust
Looking at the sidebar-pages endpoint pattern, Docmost likely has a
search endpoint — probably something like POST /search with a {
query: string, spaceId?: string } body. This would be the right
approach rather than fetching all pages and filtering client-side.
That's it. Any user on the system can now run docmost-rust from any
directory.
TUI implementation
If you prefer a user-local install (no sudo):
cargo install --path .
This puts the binary in ~/.cargo/bin/, which is already in your
$PATH if you installed Rust via rustup.
A search screen would work like this:
---
Environment variables for pre-filling login
┌─ Search ─────────────────────────────────┐
│ query▌ │ ← type to search
├──────────────────────────────────────────┤
│ ▶ Result page title Space name │
│ Another matching page Other space │
│ ... │
└──────────────────────────────────────────┘
Enter: open · Esc: back
Add these to your ~/.bashrc, ~/.zshrc, or
~/.config/fish/config.fish:
- Trigger it with / or Ctrl+F from the main view
- Each keypress fires a debounced API call (e.g. wait 300ms after
last keystroke)
- Results show page title + space name
- Enter opens the selected result in the editor
- Esc goes back
bash/zsh:
export DOCMOST_URL="https://docmost.nakano47.com"
export DOCMOST_EMAIL="chamagua1@proton.me"
The debounce part is the only tricky bit in the async loop — you'd
track a last_typed: Instant and only fire the search if elapsed() >
300ms without new input.
fish:
set -x DOCMOST_URL "https://docmost.nakano47.com"
set -x DOCMOST_EMAIL "chamagua1@proton.me"
Caveat: if Docmost's API doesn't expose a search endpoint, you'd
have to fetch all pages across all spaces and do client-side string
matching, which is slow and not full-text. Worth checking the API
docs first to confirm the endpoint exists before building it.
Then in the Rust code, replace the hardcoded defaults in
LoginForm::new() inside src/app.rs:
impl LoginForm {
pub fn new() -> Self {
Self {
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(),
// ...
}
}
}