Preserve cursor position for modal insertions
This commit is contained in:
@@ -6897,14 +6897,15 @@ impl BdsApp {
|
||||
}
|
||||
|
||||
fn insert_link_modal(&mut self, post_id: &str) -> Task<Message> {
|
||||
let state = match self.post_editors.get(post_id) {
|
||||
Some(s) => s.clone(),
|
||||
let (title, insertion_point) = match self.post_editors.get(post_id) {
|
||||
Some(state) => (state.title.clone(), state.insertion_point()),
|
||||
None => return Task::none(),
|
||||
};
|
||||
|
||||
self.active_modal = Some(modal::ModalState::PostInsertLink {
|
||||
post_id: post_id.to_string(),
|
||||
title: state.title,
|
||||
title,
|
||||
insertion_point,
|
||||
results: self.query_post_link_results(post_id, ""),
|
||||
search_query: String::new(),
|
||||
active_tab: modal::PostInsertLinkTab::Internal,
|
||||
@@ -6915,14 +6916,15 @@ impl BdsApp {
|
||||
}
|
||||
|
||||
fn insert_media_modal(&mut self, post_id: &str, link_only: bool) -> Task<Message> {
|
||||
let state = match self.post_editors.get(post_id) {
|
||||
Some(s) => s.clone(),
|
||||
let (title, insertion_point) = match self.post_editors.get(post_id) {
|
||||
Some(state) => (state.title.clone(), state.insertion_point()),
|
||||
None => return Task::none(),
|
||||
};
|
||||
|
||||
self.active_modal = Some(modal::ModalState::InsertMedia {
|
||||
post_id: post_id.to_string(),
|
||||
title: state.title,
|
||||
title,
|
||||
insertion_point,
|
||||
media_list: self.query_post_insert_media_results(""),
|
||||
search_query: String::new(),
|
||||
link_only,
|
||||
@@ -7076,10 +7078,27 @@ impl BdsApp {
|
||||
}
|
||||
|
||||
fn insert_markdown_into_post(&mut self, post_id: &str, markdown: &str) -> Task<Message> {
|
||||
let insertion_point = match self.active_modal.as_ref() {
|
||||
Some(modal::ModalState::PostInsertLink {
|
||||
post_id: modal_post_id,
|
||||
insertion_point,
|
||||
..
|
||||
})
|
||||
| Some(modal::ModalState::InsertMedia {
|
||||
post_id: modal_post_id,
|
||||
insertion_point,
|
||||
..
|
||||
}) if modal_post_id == post_id => Some(*insertion_point),
|
||||
_ => None,
|
||||
};
|
||||
let Some(state) = self.post_editors.get_mut(post_id) else {
|
||||
return Task::none();
|
||||
};
|
||||
state.insert_markdown_at_cursor(markdown);
|
||||
if let Some(insertion_point) = insertion_point {
|
||||
state.insert_markdown_at(markdown, insertion_point);
|
||||
} else {
|
||||
state.insert_markdown_at_cursor(markdown);
|
||||
}
|
||||
if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == post_id) {
|
||||
tab.is_dirty = true;
|
||||
}
|
||||
@@ -7228,6 +7247,7 @@ impl BdsApp {
|
||||
let Some(modal::ModalState::PostInsertLink {
|
||||
post_id,
|
||||
title,
|
||||
insertion_point,
|
||||
search_query: current_query,
|
||||
active_tab: current_tab,
|
||||
external_url: current_url,
|
||||
@@ -7246,6 +7266,7 @@ impl BdsApp {
|
||||
self.active_modal = Some(modal::ModalState::PostInsertLink {
|
||||
post_id: post_id.clone(),
|
||||
title,
|
||||
insertion_point,
|
||||
results: self.query_post_link_results(&post_id, &next_query),
|
||||
search_query: next_query,
|
||||
active_tab: next_tab,
|
||||
@@ -7258,6 +7279,7 @@ impl BdsApp {
|
||||
let Some(modal::ModalState::InsertMedia {
|
||||
post_id,
|
||||
title,
|
||||
insertion_point,
|
||||
link_only,
|
||||
..
|
||||
}) = self.active_modal.clone()
|
||||
@@ -7268,6 +7290,7 @@ impl BdsApp {
|
||||
self.active_modal = Some(modal::ModalState::InsertMedia {
|
||||
post_id,
|
||||
title,
|
||||
insertion_point,
|
||||
media_list: self.query_post_insert_media_results(&search_query),
|
||||
search_query,
|
||||
link_only,
|
||||
@@ -13542,6 +13565,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_modal_inserts_at_the_cursor_saved_before_focus_changes() {
|
||||
let (mut app, post_id, _tmp) = auto_translation_test_app(&[]);
|
||||
app.post_editors
|
||||
.get_mut(&post_id)
|
||||
.unwrap()
|
||||
.editor_buffer
|
||||
.borrow_mut()
|
||||
.set_cursor(0, 2);
|
||||
|
||||
let _ = app.insert_link_modal(&post_id);
|
||||
app.post_editors
|
||||
.get_mut(&post_id)
|
||||
.unwrap()
|
||||
.editor_buffer
|
||||
.borrow_mut()
|
||||
.set_cursor(0, 0);
|
||||
let _ = app.insert_markdown_into_post(&post_id, "[Link](/target)");
|
||||
|
||||
assert_eq!(
|
||||
app.post_editors.get(&post_id).unwrap().content,
|
||||
"Bo[Link](/target)dy"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_editor_loads_existing_project_tags_for_partial_suggestions() {
|
||||
let (db, project, tmp) = setup();
|
||||
|
||||
@@ -13,7 +13,7 @@ use bds_core::util::paths::thumbnail_path;
|
||||
use crate::app::Message;
|
||||
use crate::components::inputs;
|
||||
use crate::i18n::t;
|
||||
use crate::views::post_editor::PostEditorMsg;
|
||||
use crate::views::post_editor::{EditorInsertionPoint, PostEditorMsg};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InsertLinkResult {
|
||||
@@ -77,6 +77,7 @@ pub enum ModalState {
|
||||
PostInsertLink {
|
||||
post_id: String,
|
||||
title: String,
|
||||
insertion_point: EditorInsertionPoint,
|
||||
results: Vec<InsertLinkResult>,
|
||||
search_query: String,
|
||||
active_tab: PostInsertLinkTab,
|
||||
@@ -87,6 +88,7 @@ pub enum ModalState {
|
||||
InsertMedia {
|
||||
post_id: String,
|
||||
title: String,
|
||||
insertion_point: EditorInsertionPoint,
|
||||
media_list: Vec<bds_core::model::Media>,
|
||||
search_query: String,
|
||||
link_only: bool,
|
||||
@@ -598,6 +600,7 @@ pub fn view(
|
||||
ModalState::PostInsertLink {
|
||||
post_id: _post_id,
|
||||
title: _title,
|
||||
insertion_point: _,
|
||||
results,
|
||||
search_query,
|
||||
active_tab,
|
||||
@@ -820,6 +823,7 @@ pub fn view(
|
||||
ModalState::InsertMedia {
|
||||
post_id: _post_id,
|
||||
title: _title,
|
||||
insertion_point: _,
|
||||
media_list,
|
||||
search_query,
|
||||
link_only: _,
|
||||
|
||||
@@ -9,7 +9,7 @@ use iced::{Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::{self, UiLocale};
|
||||
use bds_core::model::{Post, PostStatus, PostTranslation};
|
||||
use bds_editor::{CodeEditor, EditorBuffer, EditorMessage, highlighter};
|
||||
use bds_editor::{CodeEditor, EditorBuffer, EditorMessage, Selection, highlighter};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::components::{inputs, popover};
|
||||
@@ -52,6 +52,12 @@ pub struct LinkedMediaItem {
|
||||
pub sort_order: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct EditorInsertionPoint {
|
||||
cursor: (usize, usize),
|
||||
selection: Option<Selection>,
|
||||
}
|
||||
|
||||
/// State for an open post editor.
|
||||
pub struct PostEditorState {
|
||||
pub post_id: String,
|
||||
@@ -269,8 +275,30 @@ impl PostEditorState {
|
||||
}
|
||||
|
||||
pub fn insert_markdown_at_cursor(&mut self, markdown: &str) {
|
||||
self.insert_markdown_at(markdown, self.insertion_point());
|
||||
}
|
||||
|
||||
pub fn insertion_point(&self) -> EditorInsertionPoint {
|
||||
let buffer = self.editor_buffer.borrow();
|
||||
EditorInsertionPoint {
|
||||
cursor: buffer.cursor(),
|
||||
selection: buffer.selection().copied(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_markdown_at(&mut self, markdown: &str, point: EditorInsertionPoint) {
|
||||
let new_content = {
|
||||
let mut buffer = self.editor_buffer.borrow_mut();
|
||||
buffer.clear_selection();
|
||||
buffer.set_cursor(point.cursor.0, point.cursor.1);
|
||||
if let Some(selection) = point.selection {
|
||||
buffer.set_selection(
|
||||
selection.anchor_line,
|
||||
selection.anchor_col,
|
||||
selection.head_line,
|
||||
selection.head_col,
|
||||
);
|
||||
}
|
||||
buffer.insert(markdown);
|
||||
buffer.text()
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user