Data merge: an explicit QR role in text is refused, and the caret follows each field

data.placeholder.add with role qr and the insertion point in text bound
the QR code to the first selected frame, which the caret was not in. It
now returns "qr placeholders bind to a frame", as a QR field there does.

A field inserted at the insertion point left the caret before its
marker, so a second field went in front of the first. The caret now
moves past the marker, as after typing.

Docs: docs/agents.md.
This commit is contained in:
dotCooCoo committed 2026-11-12 07:45:10 -08:00
1 parent fad00dd646
commit 329806477e
3 files changed
+49 -3

No files matched your search

+8 -1
View File
@@ -485,7 +485,9 @@ fn add_placeholder(d: &mut Document, sel: &mut Selection, p: &Value, name: &str)
} else if explicit_story || role_param == Some("text") {
false
} else if role_param == Some("qr") {
true
// A QR role with the insertion point in text is refused below rather than bound to a
// selected frame the caret is not in.
sel.text.is_none()
} else if role_param == Some("image") {
// An image role with the insertion point in text places an inline image there.
sel.text.is_none()
@@ -585,6 +587,11 @@ fn add_placeholder(d: &mut Document, sel: &mut Selection, p: &Value, name: &str)
st.replace(at..at, &marker);
}
d.adjust_text_ranges(story, at..at, marker.len());
// The caret moves past the field, as after typing, so the next field goes after it.
if let Some(t) = sel.text.as_mut().filter(|t| t.story == story && t.cell.is_none()) {
let shift = |pos: usize| if pos >= at { pos + marker.len() } else { pos };
(t.anchor, t.focus) = (shift(t.anchor), shift(t.focus));
}
PlaceholderAnchor::Text { story, start: at, end: at.saturating_add(marker.len()) }
} else {
let text = d.story(story).map(|st| st.text.clone()).unwrap_or_default();
+38
View File
@@ -831,6 +831,44 @@ fn a_qr_field_at_the_insertion_point_is_refused() {
assert!(s.documents()[0].doc.data_merge.placeholders.is_empty());
}
/// An explicit QR role at a text insertion point is refused too, rather than binding the QR code
/// to a selected frame the caret is not in.
#[test]
fn an_explicit_qr_role_at_the_insertion_point_is_refused() {
let mut s = Session::new();
new_doc(&mut s, 1);
select_csv(&mut s, "Name,#Code\nAda,hello\n");
let made = text_frame(&mut s, [36.0, 36.0, 300.0, 300.0], "Hi ", None);
let other = text_frame(&mut s, [36.0, 400.0, 300.0, 600.0], "", None);
let other_id = designcraft_doc::ItemId(other["id"].as_u64().unwrap());
let sid = StoryId(made["story"].as_u64().unwrap());
let doc = s.doc_mut().unwrap();
doc.selection = Selection::text(TextSel { story: sid, anchor: 3, focus: 3, frame: None, cell: None });
doc.selection.items = vec![other_id];
let msg = err(&mut s, "data.placeholder.add", json!({"field": "Code", "role": "qr"}));
assert!(msg.contains("bind to a frame"), "{msg}");
let doc = &s.documents()[0].doc;
assert!(doc.data_merge.placeholders.is_empty());
assert_eq!(doc.item(other_id).unwrap().label, "", "the other frame is not bound");
}
/// Fields inserted one after another at the insertion point keep their order: the caret moves
/// past each marker, as after typing.
#[test]
fn fields_inserted_at_the_insertion_point_keep_their_order() {
let mut s = Session::new();
new_doc(&mut s, 1);
select_csv(&mut s, "Name,City,@Photo\nAda,Paris,/x.png\n");
let made = text_frame(&mut s, [36.0, 36.0, 300.0, 300.0], "Hi ", None);
exec(&mut s, "text.select", json!({"story": made["story"], "anchor": 3, "focus": 3}));
exec(&mut s, "data.placeholder.add", json!({"field": "Name", "role": "text"}));
exec(&mut s, "data.placeholder.add", json!({"field": "City", "role": "text"}));
exec(&mut s, "data.placeholder.add", json!({"field": "Photo"}));
assert_eq!(texts(&s), vec!["Hi <<Name>><<City>><<Photo>>".to_string()]);
let t = s.documents()[0].selection.text.unwrap();
assert_eq!((t.anchor, t.focus), (28, 28));
}
/// A typed `<<name>>` marker for an image column places the image too, from an absolute path;
/// an explicit text role still writes the path as text.
#[test]
+3 -2
View File
@@ -91,8 +91,9 @@ external PDF/X certification.
marks text or a frame as a field (`role`: text, image, qr, hyperlink). An image field (a `@` column) inserted at a text
insertion point, or typed there as `<<name>>`, merges as an inline image at its natural size, scaled down to fit the
frame. The merge writes only text into a table cell or a note: an image field inserted there is refused unless `role`
is `text`, and its `<<name>>` marker, inserted or typed, merges as the path. A QR field (a `#` column) inserted at a
text insertion point is refused; bind it to a frame instead. An image
is `text`, and its `<<name>>` marker, inserted or typed, merges as the path. A QR field (a `#` column) or `role: "qr"`
at a text insertion point is refused; bind it to a frame with `item` instead. A field inserted at the insertion point
puts the caret after its marker. An image
path is absolute or relative to the data file's folder, then the document's; a path that starts with one separator
(`\Photos\a.jpg`) is tried from the drive's root first. `data.options` sets records, tiling and image fitting;
`data.preview {record}` / `data.preview.stop` show one record without saving it.