mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
engine: a cut takes the source's notes, hyperlinks and merged areas with it
A cut removed the source's cells and pictures and then re-created the
block's merged areas at the destination and copied its notes there — but
removed nothing from the source, so one merged block became two (an
empty one left behind) and a note ended up on both the old and the new
cell. Excel moves both and leaves the source clean.
The cut now clears the source range's comments, hyperlinks and merges
the same way edit.clear does. A second gap in the same path: paste never
transferred hyperlinks at all, so clearing the source would have lost
them — paste now carries hyperlinks (in What::All and a new
What::Hyperlinks, so edit.pasteSpecial {"what":"hyperlinks"} works
like "comments").
Regression test: a_cut_moves_the_sources_notes_hyperlinks_and_merges —
on main the old merge stays behind and the note is on both cells; with
the fix the merge, note and link all move and the source is clean.
This commit is contained in:
@@ -769,6 +769,7 @@ enum What {
|
||||
Values,
|
||||
Formats,
|
||||
Comments,
|
||||
Hyperlinks,
|
||||
Validation,
|
||||
AllExceptBorders,
|
||||
ColumnWidths,
|
||||
@@ -903,6 +904,7 @@ fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
"values" => What::Values,
|
||||
"formats" => What::Formats,
|
||||
"comments" | "notes" => What::Comments,
|
||||
"hyperlinks" | "links" => What::Hyperlinks,
|
||||
"validation" => What::Validation,
|
||||
"allExceptBorders" => What::AllExceptBorders,
|
||||
"columnWidths" => What::ColumnWidths,
|
||||
@@ -952,6 +954,13 @@ fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let source = cx.sheet_mut(clip.sheet)?;
|
||||
moved_pictures = source.cell_pictures.iter().filter(|(c, _)| src.contains(**c)).map(|(c, p)| (*c, p.clone())).collect();
|
||||
let taken = source.take_cells(src);
|
||||
// Notes, hyperlinks and merged areas sit beside the cells, and a cut takes them
|
||||
// with the block: the destination is written from the clipboard snapshot taken
|
||||
// before the cut, so removing them here loses nothing and leaves the old place
|
||||
// clean instead of leaving one merged block and one note behind.
|
||||
source.comments.retain(|c, _| !src.contains(*c));
|
||||
source.hyperlinks.retain(|c, _| !src.contains(*c));
|
||||
source.merges.retain(|m| !src.contains_range(m));
|
||||
for (c, _) in &taken {
|
||||
cx.touch(clip.sheet, *c);
|
||||
}
|
||||
@@ -1048,7 +1057,7 @@ fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
}
|
||||
}
|
||||
What::Formats => new.style = map_style(sc_cell.style, &mut cx.wb),
|
||||
What::Comments | What::Validation | What::ColumnWidths => {}
|
||||
What::Comments | What::Hyperlinks | What::Validation | What::ColumnWidths => {}
|
||||
}
|
||||
// Operations combine numbers with what is already there.
|
||||
if op != "none"
|
||||
@@ -1121,6 +1130,13 @@ fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
cx.sheet_mut(dest_sheet)?.comments.insert(dest, cm);
|
||||
}
|
||||
}
|
||||
// A cut moves the block's hyperlinks with it, so they must reach the
|
||||
// destination too (only `edit.clear` touched them before).
|
||||
What::Hyperlinks => {
|
||||
if let Some(h) = src_sheet.hyperlinks.get(&sc).cloned() {
|
||||
cx.sheet_mut(dest_sheet)?.hyperlinks.insert(dest, h);
|
||||
}
|
||||
}
|
||||
What::ColumnWidths => {
|
||||
if let Some(info) = src_sheet.cols.get(&sc.col).copied() {
|
||||
cx.sheet_mut(dest_sheet)?.cols.insert(dest.col, info);
|
||||
@@ -1130,6 +1146,9 @@ fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
if let Some(cm) = src_sheet.comments.get(&sc).cloned() {
|
||||
cx.sheet_mut(dest_sheet)?.comments.insert(dest, cm);
|
||||
}
|
||||
if let Some(h) = src_sheet.hyperlinks.get(&sc).cloned() {
|
||||
cx.sheet_mut(dest_sheet)?.hyperlinks.insert(dest, h);
|
||||
}
|
||||
cx.sheet_mut(dest_sheet)?.set_cell(dest, new);
|
||||
}
|
||||
_ => cx.sheet_mut(dest_sheet)?.set_cell(dest, new),
|
||||
|
||||
@@ -2925,6 +2925,36 @@ fn notes_comments_and_links_survive_a_json_save() {
|
||||
assert_eq!(after.cells, before.cells);
|
||||
}
|
||||
|
||||
/// A cut moves the block's notes, hyperlinks and merged areas with it. The source's cells and
|
||||
/// pictures were removed but the block's merges and notes were not, so one merged block became
|
||||
/// two (an empty one left at the old place) and a note ended up on both the old and the new
|
||||
/// cell. Hyperlinks were not transferred at all.
|
||||
#[test]
|
||||
fn a_cut_moves_the_sources_notes_hyperlinks_and_merges() {
|
||||
let mut s = s();
|
||||
s.execute("cell.set", json!({"cell": "A1", "input": "x"})).unwrap();
|
||||
s.execute("cell.set", json!({"cell": "B1", "input": "y"})).unwrap();
|
||||
s.execute("review.newNote", json!({"cell": "A1", "text": "note", "author": "Example"})).unwrap();
|
||||
s.execute("insert.link", json!({"cell": "A1", "target": "https://example.com"})).unwrap();
|
||||
s.execute("home.mergeCells", json!({"range": "A1:B1"})).unwrap();
|
||||
|
||||
s.execute("edit.cut", json!({"range": "A1:B1"})).unwrap();
|
||||
s.execute("edit.paste", json!({"at": "D1"})).unwrap();
|
||||
|
||||
let d = s.doc().unwrap();
|
||||
let sh = d.wb.active().unwrap();
|
||||
let (a1, d1) = (CellRef::parse("A1").unwrap(), CellRef::parse("D1").unwrap());
|
||||
assert_eq!(sh.merges, vec![gridcraft_core::RangeRef::parse("D1:E1").unwrap()], "the old merge must not stay behind");
|
||||
assert!(!sh.comments.contains_key(&a1), "the note moved");
|
||||
assert!(sh.comments.contains_key(&d1), "the note is at the new cell");
|
||||
assert!(!sh.hyperlinks.contains_key(&a1), "the hyperlink moved");
|
||||
assert!(sh.hyperlinks.contains_key(&d1), "the hyperlink is at the new cell");
|
||||
// The cells themselves moved as before; E1 is covered by the pasted merge (merging
|
||||
// A1:B1 cleared B1, so only the anchor's content travels).
|
||||
assert_eq!(v(&s, "D1"), Value::text("x"));
|
||||
assert!(v(&s, "A1").is_empty() && v(&s, "B1").is_empty());
|
||||
}
|
||||
|
||||
/// Insert › Link sorts a target as Excel's dialog does: a sheet reference, a defined name or `#…` is
|
||||
/// a place in this document, anything else an existing file or web page. A file saved as a
|
||||
/// `location` opens nothing in Excel (#800).
|
||||
|
||||
Reference in New Issue
Block a user