Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions apps/web/src/markdown-clipboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class FakeText {

class FakeElement {
readonly nodeType = ELEMENT_NODE;
checked = false;
readonly childNodes: Array<FakeElement | FakeText> = [];
readonly classList = {
contains: (name: string) => this.classNames.includes(name),
Expand Down Expand Up @@ -61,18 +62,31 @@ class FakeElement {

/** Supports only the selectors markdown-clipboard actually asks for. */
querySelector(selector: string): FakeElement | null {
if (selector.includes(", ")) {
for (const part of selector.split(", ")) {
const match = this.querySelector(part);
if (match) return match;
}
return null;
}
const childOnly = selector.startsWith(":scope > ");
const target = childOnly ? selector.slice(":scope > ".length) : selector;
const [target, ...rest] = (childOnly ? selector.slice(":scope > ".length) : selector).split(
" > ",
);
const matches = (element: FakeElement): boolean => {
if (target === 'input[type="checkbox"]') {
return element.tagName === "INPUT" && element.getAttribute("type") === "checkbox";
}
return element.tagName === target.toUpperCase();
return element.tagName === target?.toUpperCase();
};
const search = (parent: FakeElement): FakeElement | null => {
for (const child of parent.childNodes) {
if (!(child instanceof FakeElement)) continue;
if (matches(child)) return child;
if (matches(child)) {
if (rest.length === 0) return child;
const nested = child.querySelector(`:scope > ${rest.join(" > ")}`);
if (nested) return nested;
}
if (!childOnly) {
const nested = search(child);
if (nested) return nested;
Expand Down Expand Up @@ -143,6 +157,57 @@ describe("serializeRenderedMarkdownFragment", () => {
expect(serializeRenderedMarkdownFragment(asNode(container))).toBe("run `git status` first");
});

describe.each([
{ parentLayout: "tight", childLayout: "tight" },
{ parentLayout: "tight", childLayout: "loose" },
{ parentLayout: "loose", childLayout: "tight" },
{ parentLayout: "loose", childLayout: "loose" },
])("$parentLayout parent with $childLayout child", ({ parentLayout, childLayout }) => {
it.each([
{ parentChecked: null, childChecked: true, parent: "- Parent", child: " - [x] Child" },
{
parentChecked: false,
childChecked: true,
parent: "- [ ] Parent",
child: " - [x] Child",
},
{
parentChecked: true,
childChecked: false,
parent: "- [x] Parent",
child: " - [ ] Child",
},
])("copies $parent with $child", ({ parentChecked, childChecked, parent, child }) => {
const parentContent = parentLayout === "loose" ? new FakeElement("P") : new FakeElement("LI");
if (parentChecked !== null) {
const checkbox = new FakeElement("INPUT", [], { type: "checkbox" });
checkbox.checked = parentChecked;
parentContent.append(checkbox, new FakeText(" "));
}
parentContent.append(new FakeText("Parent"));
const parentItem =
parentLayout === "loose" ? new FakeElement("LI").append(parentContent) : parentContent;
const checkbox = new FakeElement("INPUT", [], { type: "checkbox" });
checkbox.checked = childChecked;
const childContent = new FakeElement(childLayout === "loose" ? "P" : "LI").append(
checkbox,
new FakeText(" Child"),
);
const childItem =
childLayout === "loose" ? new FakeElement("LI").append(childContent) : childContent;
parentItem.append(new FakeText("\n"), new FakeElement("UL").append(childItem));
const container = new FakeElement("DIV").append(
new FakeElement("P").append(new FakeText("Before")),
new FakeElement("UL").append(parentItem),
new FakeElement("P").append(new FakeText("After")),
);

expect(serializeRenderedMarkdownFragment(asNode(container))).toBe(
`Before\n\n${parent}${parentLayout === "loose" ? "\n\n" : "\n"}${child}\n\nAfter`,
);
});
});

it("copies the complete quote, source, and comment instead of the comment-only chip label", () => {
const citation = {
version: 1 as const,
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/markdown-clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ function serializeTable(table: Element): string {
}

function serializeListItem(item: Element, ordered: boolean, index: number): string {
const checkbox = item.querySelector('input[type="checkbox"]');
const checkbox = item.querySelector(
':scope > input[type="checkbox"], :scope > p > input[type="checkbox"]',
);
const task = checkbox ? `[${(checkbox as HTMLInputElement).checked ? "x" : " "}] ` : "";
const marker = ordered ? `${index}. ${task}` : `- ${task}`;
let content = serializeChildren(item)
Expand Down
Loading