Focus the Insert Hyperlink address box (#1610) (#1642)

* chore: reserve draft implementation for #1610

* fix(ui): focus hyperlink address when opening the dialog (#1610)
This commit is contained in:
FredApps
2026-10-18 04:23:24 -04:00
committed by GitHub
parent 8701f4e28d
commit 47c2eda22c
2 changed files with 52 additions and 3 deletions
+13 -3
View File
@@ -269,6 +269,8 @@ pub enum Dialog {
Link {
url: String,
text: String,
/// Focus and select the address when the dialog first opens.
focus: bool,
/// "Place in This Document" instead of "Existing File or Web Page".
place: bool,
/// The document's places (`link.targets`), read when that tab is first shown.
@@ -1008,7 +1010,9 @@ impl Dialog {
valign: sp.valign,
}
}
"link" => Dialog::Link { url: "https://".into(), text: app.session.selected_text(), place: false, targets: None, pick: None },
"link" => {
Dialog::Link { url: "https://".into(), text: app.session.selected_text(), place: false, targets: None, pick: None, focus: true }
}
"bookmark" => Dialog::Bookmark { name: bookmark_at_caret(&app.session), by_location: false, hidden: false, deleted: false },
"wordCount" => Dialog::WordCount { stats: app.session.run("review.wordCount", &json!({})).unwrap_or_default() },
"zoom" => Dialog::Zoom { percent: (app.session.view.zoom * 100.0).round() },
@@ -2011,7 +2015,7 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
}
ok || cancel
}
Dialog::Link { url, text, place, targets, pick } => {
Dialog::Link { url, text, place, targets, pick, focus } => {
// Word's "Link to:" choices; the places are read once, when that tab is first shown.
ui.horizontal(|ui| {
ui.label(tl!("Link to:"));
@@ -2027,7 +2031,13 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
ui.end_row();
if !*place {
ui.label(tl!("Address:"));
ui.text_edit_singleline(url);
let mut out = egui::TextEdit::singleline(url).id(egui::Id::new("link_address")).show(ui);
if std::mem::take(focus) {
out.response.request_focus();
let all = egui::text::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(url.chars().count()));
out.state.cursor.set_char_range(Some(all));
out.state.store(ui.ctx(), out.response.id);
}
ui.end_row();
}
});
+39
View File
@@ -2981,6 +2981,45 @@ mod tests {
assert!(a.session.doc.plain_text(wordcraft_doc::StoryRef::Body).is_empty(), "nothing was typed into the document");
}
/// #1610: opening a hyperlink must direct typing and paste into its address.
#[test]
fn hyperlink_shortcut_focuses_address_and_preserves_document() {
let ctx = egui::Context::default();
let mut a = app();
a.run("text.insert", json!({"text": "Keep this text"})).unwrap();
a.run("select.text", json!({"text": "Keep this text"})).unwrap();
let frame = |a: &mut WordApp, events: Vec<egui::Event>| {
ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| {
a.logic(ui.ctx());
a.ui(ui);
})
.drop_without_applying_deltas();
};
for _ in 0..3 {
frame(&mut a, Vec::new());
}
let key = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers };
for (event, expected) in [
(egui::Event::Paste("https://example.com".into()), "https://example.com"),
(egui::Event::Text("https://example.org".into()), "https://example.org"),
] {
frame(&mut a, vec![key(egui::Key::K, egui::Modifiers::COMMAND)]);
for _ in 0..2 {
frame(&mut a, Vec::new());
}
assert!(ctx.memory(|m| m.has_focus(egui::Id::new("link_address"))), "Address has focus");
frame(&mut a, vec![event]);
let Some(dialogs::Dialog::Link { url, text, .. }) = &a.dialog else { panic!("no Link dialog") };
assert_eq!(url, expected, "input replaces the initial address");
assert_eq!(text, "Keep this text");
assert_eq!(a.session.doc.plain_text(wordcraft_doc::StoryRef::Body), "Keep this text");
frame(&mut a, vec![key(egui::Key::Escape, egui::Modifiers::NONE)]);
frame(&mut a, Vec::new());
assert!(a.dialog.is_none(), "Cancel closes the dialog");
assert_eq!(a.session.selected_text(), "Keep this text", "Cancel preserves selection");
}
}
/// Issue #139: Ctrl+wheel over the page didn't zoom.
#[test]
fn ctrl_wheel_over_canvas_zooms() {