fix(app): apply clipboard syntax at selection boundaries
Selections contained inside formatting copy only their visible content. Preserve Markdown syntax when the range includes or crosses the semantic element, while retaining list and table structure needed to serialize child boundaries.
This commit is contained in:
@@ -155,15 +155,19 @@ describe("assistant selection copy ranges", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("retains strong delimiters when its complete contents are selected", () => {
|
||||
const message = mountFixture();
|
||||
const element = fixtureElement(message, '[data-paseo-markdown-tag="strong"]');
|
||||
const content = createAssistantSelectionClipboardContent(
|
||||
selectText(element, 0, textNode(element).length),
|
||||
);
|
||||
expect(content?.plainText).toBe("**bold text**");
|
||||
expect(content?.html).toContain("<strong>");
|
||||
});
|
||||
it.each(["strong", "em", "s", "h2", "blockquote"])(
|
||||
"copies all content selected from inside a %s element without its syntax",
|
||||
(tag) => {
|
||||
const message = mountFixture();
|
||||
const element = fixtureElement(message, '[data-paseo-markdown-tag="strong"]');
|
||||
element.setAttribute("data-paseo-markdown-tag", tag);
|
||||
const content = createAssistantSelectionClipboardContent(
|
||||
selectText(element, 0, textNode(element).length),
|
||||
);
|
||||
expect(content?.plainText).toBe("bold text");
|
||||
expect(content?.html).not.toContain(`<${tag}>`);
|
||||
},
|
||||
);
|
||||
|
||||
it("copies complete inline code without delimiters when the selection stays inside", () => {
|
||||
const message = mountFixture();
|
||||
|
||||
@@ -278,6 +278,13 @@ function shouldPreserveSemanticElement(range: Range, element: Element): boolean
|
||||
if (tag === "p" || isTableStructure(tag)) {
|
||||
return true;
|
||||
}
|
||||
const isSelectableSemantic = tag !== null && tag !== "ol" && tag !== "ul";
|
||||
if (
|
||||
(isSelectableSemantic || element.tagName === "A") &&
|
||||
selectionStaysInsideElement(range, element)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (tag === "ol" || tag === "ul") {
|
||||
const selectedItems = selectedListItems(element, range);
|
||||
return selectedItems.some((item) => hasSelectedAllContents(range, item, true));
|
||||
@@ -285,6 +292,15 @@ function shouldPreserveSemanticElement(range: Range, element: Element): boolean
|
||||
return hasSelectedAllContents(range, element, tag === "li");
|
||||
}
|
||||
|
||||
function selectionStaysInsideElement(range: Range, element: Element): boolean {
|
||||
return (
|
||||
range.startContainer !== element &&
|
||||
range.endContainer !== element &&
|
||||
element.contains(range.startContainer) &&
|
||||
element.contains(range.endContainer)
|
||||
);
|
||||
}
|
||||
|
||||
function selectedListItems(list: Element, range: Range): Element[] {
|
||||
return Array.from(list.children).filter(
|
||||
(child) =>
|
||||
|
||||
Reference in New Issue
Block a user