mirror of
https://github.com/m1ngsama/TUT.git
synced 2026-02-08 09:04:04 +00:00
feat: Add interactive form text input editing
- Add FORM_EDIT input mode for editing text fields - Add actions: NEXT_FIELD, PREV_FIELD, EDIT_TEXT, ACTIVATE_FIELD - Support 'i' key to focus first form field - Tab/Shift+Tab to navigate between fields - Enter on text input fields to edit them - Real-time text editing with live preview - Enter/Esc to exit edit mode - Checkbox toggle support (press Enter on checkbox) - Status bar shows "-- INSERT --" mode and current text - Form fields highlighted when active Keyboard shortcuts: - i: Focus first form field - Tab: Next field - Shift+Tab: Previous field - Enter: Activate/edit field or toggle checkbox - Esc: Exit edit mode
This commit is contained in:
parent
55fc7c79f5
commit
7e55ade793
4 changed files with 178 additions and 4 deletions
|
|
@ -456,6 +456,7 @@ public:
|
|||
case InputMode::NORMAL: mode_str = "NORMAL"; break;
|
||||
case InputMode::COMMAND:
|
||||
case InputMode::SEARCH: mode_str = input_handler.get_buffer(); break;
|
||||
case InputMode::FORM_EDIT: mode_str = "-- INSERT -- " + input_handler.get_buffer(); break;
|
||||
default: mode_str = ""; break;
|
||||
}
|
||||
framebuffer->set_text(1, y, mode_str, colors::STATUSBAR_FG, colors::STATUSBAR_BG);
|
||||
|
|
@ -540,7 +541,25 @@ public:
|
|||
break;
|
||||
|
||||
case Action::FOLLOW_LINK:
|
||||
if (active_link >= 0 && active_link < static_cast<int>(current_tree.links.size())) {
|
||||
// If on a form field, activate it instead of following link
|
||||
if (active_field >= 0 && active_field < static_cast<int>(current_tree.form_fields.size())) {
|
||||
auto* field = current_tree.form_fields[active_field];
|
||||
if (field) {
|
||||
if (field->input_type == "text" || field->input_type == "password") {
|
||||
// Enter edit mode
|
||||
input_handler.set_mode(InputMode::FORM_EDIT);
|
||||
input_handler.set_buffer(field->value);
|
||||
status_message = "-- INSERT --";
|
||||
} else if (field->input_type == "checkbox") {
|
||||
// Toggle checkbox
|
||||
field->checked = !field->checked;
|
||||
status_message = field->checked ? "☑ Checked" : "☐ Unchecked";
|
||||
} else if (field->input_type == "submit" || field->element_type == ElementType::BUTTON) {
|
||||
// TODO: Submit form
|
||||
status_message = "Form submit (not yet implemented)";
|
||||
}
|
||||
}
|
||||
} else if (active_link >= 0 && active_link < static_cast<int>(current_tree.links.size())) {
|
||||
start_async_load(current_tree.links[active_link].url);
|
||||
}
|
||||
break;
|
||||
|
|
@ -609,6 +628,66 @@ public:
|
|||
show_history();
|
||||
break;
|
||||
|
||||
case Action::NEXT_FIELD:
|
||||
if (!current_tree.form_fields.empty()) {
|
||||
// Save current text if in edit mode
|
||||
if (input_handler.get_mode() == InputMode::FORM_EDIT &&
|
||||
active_field >= 0 && active_field < static_cast<int>(current_tree.form_fields.size())) {
|
||||
auto* field = current_tree.form_fields[active_field];
|
||||
if (field && (field->input_type == "text" || field->input_type == "password")) {
|
||||
field->value = result.text;
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next field
|
||||
if (active_field < 0) {
|
||||
active_field = 0; // First field
|
||||
} else {
|
||||
active_field = (active_field + 1) % current_tree.form_fields.size();
|
||||
}
|
||||
|
||||
// Auto-scroll to field
|
||||
// TODO: Implement scroll to field
|
||||
status_message = "Field " + std::to_string(active_field + 1) + "/" +
|
||||
std::to_string(current_tree.form_fields.size());
|
||||
}
|
||||
break;
|
||||
|
||||
case Action::PREV_FIELD:
|
||||
if (!current_tree.form_fields.empty()) {
|
||||
// Save current text if in edit mode
|
||||
if (input_handler.get_mode() == InputMode::FORM_EDIT &&
|
||||
active_field >= 0 && active_field < static_cast<int>(current_tree.form_fields.size())) {
|
||||
auto* field = current_tree.form_fields[active_field];
|
||||
if (field && (field->input_type == "text" || field->input_type == "password")) {
|
||||
field->value = result.text;
|
||||
}
|
||||
}
|
||||
|
||||
// Move to previous field
|
||||
if (active_field < 0) {
|
||||
active_field = current_tree.form_fields.size() - 1; // Last field
|
||||
} else {
|
||||
active_field = (active_field - 1 + current_tree.form_fields.size()) %
|
||||
current_tree.form_fields.size();
|
||||
}
|
||||
|
||||
status_message = "Field " + std::to_string(active_field + 1) + "/" +
|
||||
std::to_string(current_tree.form_fields.size());
|
||||
}
|
||||
break;
|
||||
|
||||
case Action::EDIT_TEXT:
|
||||
// Update field value in real-time
|
||||
if (active_field >= 0 && active_field < static_cast<int>(current_tree.form_fields.size())) {
|
||||
auto* field = current_tree.form_fields[active_field];
|
||||
if (field && (field->input_type == "text" || field->input_type == "password")) {
|
||||
field->value = result.text;
|
||||
status_message = "Editing: " + result.text;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Action::QUIT:
|
||||
break; // 在main loop处理
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ public:
|
|||
count_buffer.clear();
|
||||
break;
|
||||
case '\t':
|
||||
// Tab can navigate both links and fields - browser will decide
|
||||
result.action = Action::NEXT_LINK;
|
||||
count_buffer.clear();
|
||||
break;
|
||||
|
|
@ -135,7 +136,7 @@ public:
|
|||
break;
|
||||
case '\n':
|
||||
case '\r':
|
||||
// If count buffer has a number, jump to that link
|
||||
// Enter can follow links or activate fields - browser will decide
|
||||
if (result.has_count) {
|
||||
result.action = Action::GOTO_LINK;
|
||||
result.number = result.count;
|
||||
|
|
@ -144,6 +145,11 @@ public:
|
|||
}
|
||||
count_buffer.clear();
|
||||
break;
|
||||
case 'i':
|
||||
// 'i' to focus on first form field (like vim insert mode)
|
||||
result.action = Action::NEXT_FIELD;
|
||||
count_buffer.clear();
|
||||
break;
|
||||
case 'f':
|
||||
// 'f' command: vimium-style link hints
|
||||
result.action = Action::SHOW_LINK_HINTS;
|
||||
|
|
@ -334,6 +340,52 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
InputResult process_form_edit_mode(int ch) {
|
||||
InputResult result;
|
||||
result.action = Action::NONE;
|
||||
|
||||
if (ch == 27) {
|
||||
// ESC exits form edit mode
|
||||
mode = InputMode::NORMAL;
|
||||
buffer.clear();
|
||||
return result;
|
||||
} else if (ch == '\n' || ch == '\r') {
|
||||
// Enter submits the text
|
||||
result.action = Action::EDIT_TEXT;
|
||||
result.text = buffer;
|
||||
mode = InputMode::NORMAL;
|
||||
buffer.clear();
|
||||
return result;
|
||||
} else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) {
|
||||
// Backspace removes last character
|
||||
if (!buffer.empty()) {
|
||||
buffer.pop_back();
|
||||
}
|
||||
return result;
|
||||
} else if (ch == '\t') {
|
||||
// Tab moves to next field while saving current text
|
||||
result.action = Action::NEXT_FIELD;
|
||||
result.text = buffer;
|
||||
buffer.clear();
|
||||
return result;
|
||||
} else if (ch == KEY_BTAB) {
|
||||
// Shift+Tab moves to previous field while saving current text
|
||||
result.action = Action::PREV_FIELD;
|
||||
result.text = buffer;
|
||||
buffer.clear();
|
||||
return result;
|
||||
} else if (std::isprint(ch)) {
|
||||
// Add printable characters to buffer
|
||||
buffer += static_cast<char>(ch);
|
||||
// Return EDIT_TEXT to update in real-time
|
||||
result.action = Action::EDIT_TEXT;
|
||||
result.text = buffer;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
InputHandler::InputHandler() : pImpl(std::make_unique<Impl>()) {}
|
||||
|
|
@ -352,6 +404,8 @@ InputResult InputHandler::handle_key(int ch) {
|
|||
return pImpl->process_link_mode(ch);
|
||||
case InputMode::LINK_HINTS:
|
||||
return pImpl->process_link_hints_mode(ch);
|
||||
case InputMode::FORM_EDIT:
|
||||
return pImpl->process_form_edit_mode(ch);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -375,6 +429,14 @@ void InputHandler::reset() {
|
|||
pImpl->count_buffer.clear();
|
||||
}
|
||||
|
||||
void InputHandler::set_mode(InputMode mode) {
|
||||
pImpl->mode = mode;
|
||||
}
|
||||
|
||||
void InputHandler::set_buffer(const std::string& buffer) {
|
||||
pImpl->buffer = buffer;
|
||||
}
|
||||
|
||||
void InputHandler::set_status_callback(std::function<void(const std::string&)> callback) {
|
||||
pImpl->status_callback = callback;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ enum class InputMode {
|
|||
COMMAND,
|
||||
SEARCH,
|
||||
LINK,
|
||||
LINK_HINTS // Vimium-style 'f' mode
|
||||
LINK_HINTS, // Vimium-style 'f' mode
|
||||
FORM_EDIT // Form field editing mode
|
||||
};
|
||||
|
||||
enum class Action {
|
||||
|
|
@ -42,7 +43,13 @@ enum class Action {
|
|||
ADD_BOOKMARK, // Add current page to bookmarks (B)
|
||||
REMOVE_BOOKMARK, // Remove current page from bookmarks (D)
|
||||
SHOW_BOOKMARKS, // Show bookmarks page (:bookmarks)
|
||||
SHOW_HISTORY // Show history page (:history)
|
||||
SHOW_HISTORY, // Show history page (:history)
|
||||
NEXT_FIELD, // Move to next form field (Tab)
|
||||
PREV_FIELD, // Move to previous form field (Shift+Tab)
|
||||
ACTIVATE_FIELD, // Activate current field for editing (Enter)
|
||||
TOGGLE_CHECKBOX, // Toggle checkbox state
|
||||
EDIT_TEXT, // Edit text input (updates text buffer)
|
||||
SUBMIT_FORM // Submit form (Enter on submit button)
|
||||
};
|
||||
|
||||
struct InputResult {
|
||||
|
|
@ -62,6 +69,8 @@ public:
|
|||
InputMode get_mode() const;
|
||||
std::string get_buffer() const;
|
||||
void reset();
|
||||
void set_mode(InputMode mode);
|
||||
void set_buffer(const std::string& buffer);
|
||||
void set_status_callback(std::function<void(const std::string&)> callback);
|
||||
|
||||
private:
|
||||
|
|
|
|||
24
test_form.html
Normal file
24
test_form.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Form Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test Form</h1>
|
||||
|
||||
<form action="/submit" method="POST">
|
||||
<p>Name: <input type="text" name="name" placeholder="Enter your name"></p>
|
||||
<p>Email: <input type="text" name="email" placeholder="email@example.com"></p>
|
||||
<p>Password: <input type="password" name="password"></p>
|
||||
|
||||
<p><input type="checkbox" name="subscribe"> Subscribe to newsletter</p>
|
||||
<p><input type="checkbox" name="agree"> I agree to terms</p>
|
||||
|
||||
<p><input type="submit" value="Submit Form"></p>
|
||||
<p><button>Click Me</button></p>
|
||||
</form>
|
||||
|
||||
<h2>Links</h2>
|
||||
<p><a href="https://example.com">Example Link</a></p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue