diff --git a/packages/editor/src/extensions/outline-list-item/__tests__/outline-list-item.test.ts b/packages/editor/src/extensions/outline-list-item/__tests__/outline-list-item.test.ts index 673040c39..3be822fbc 100644 --- a/packages/editor/src/extensions/outline-list-item/__tests__/outline-list-item.test.ts +++ b/packages/editor/src/extensions/outline-list-item/__tests__/outline-list-item.test.ts @@ -73,6 +73,71 @@ describe("outline list item", () => { expect(editor.getJSON()).toMatchSnapshot(); }); + test("tapping the gutter of a nested item toggles it; a flat item ignores it", () => { + const el = outlineList( + outlineListItem(["flat item"]), + li( + [ + h("p", ["Parent"]), + outlineList( + outlineListItem(["Child one"]), + outlineListItem(["Child two"]) + ) + ], + { "data-type": "outlineListItem" } + ) + ); + + const { editor } = createEditor({ + initialContent: el.outerHTML, + extensions: { + outlineList: OutlineList, + outlineListItem: OutlineListItem, + paragraph: Paragraph + } + }); + + const dom = editor.view.dom as HTMLElement; + const nested = dom.querySelector("li.nested"); + const flat = Array.from(dom.querySelectorAll("li")).find( + (item) => !item.classList.contains("nested") + ); + expect(nested).toBeTruthy(); + expect(flat).toBeTruthy(); + + const collapsed = () => { + let value: boolean | undefined; + editor.state.doc.descendants((node) => { + if ( + node.type.name === OutlineListItem.name && + node.lastChild?.type.name === OutlineList.name + ) + value = node.attrs.collapsed; + }); + return value; + }; + + const tapGutter = (item: Element) => + item.dispatchEvent( + new MouseEvent("mousedown", { + button: 0, + clientX: -10, + clientY: 0, + bubbles: true, + cancelable: true + }) + ); + + expect(collapsed()).toBe(false); + tapGutter(nested!); + expect(collapsed()).toBe(true); + tapGutter(nested!); + expect(collapsed()).toBe(false); + + tapGutter(flat!); + expect(collapsed()).toBe(false); + }); + /** * Two changes happened: * 1. Images were converted from inline nodes to block nodes (https://github.com/streetwriters/notesnook/pull/8563) diff --git a/packages/editor/src/extensions/outline-list-item/outline-list-item.ts b/packages/editor/src/extensions/outline-list-item/outline-list-item.ts index b26574b5b..043afc295 100644 --- a/packages/editor/src/extensions/outline-list-item/outline-list-item.ts +++ b/packages/editor/src/extensions/outline-list-item/outline-list-item.ts @@ -24,7 +24,6 @@ import { } from "@tiptap/core"; import { findParentNodeOfTypeClosestToPos, - isClickWithinBounds, ensureLeadingParagraph } from "../../utils/prosemirror.js"; import { OutlineList } from "../outline-list/outline-list.js"; @@ -147,14 +146,24 @@ export const OutlineListItem = Node.create({ function onClick(e: MouseEvent | TouchEvent) { if (e instanceof MouseEvent && e.button !== 0) return; - if (!(e.target instanceof HTMLElement)) return; if (!li.classList.contains("nested")) return; const pos = typeof getPos === "function" ? getPos() : 0; if (typeof pos !== "number") return; - const resolvedPos = editor.state.doc.resolve(pos); - if (isClickWithinBounds(e, resolvedPos, "left")) { + const point = e instanceof MouseEvent ? e : e.touches[0]; + if (!point) return; + + const rect = li.getBoundingClientRect(); + const row = (li.firstElementChild ?? li).getBoundingClientRect(); + const rtl = getComputedStyle(li).direction === "rtl"; + + const withinX = rtl + ? point.clientX >= rect.right && point.clientX <= rect.right + 40 + : point.clientX >= rect.left - 40 && point.clientX <= rect.left; + const withinY = point.clientY >= row.top && point.clientY <= row.bottom; + + if (withinX && withinY) { e.preventDefault(); e.stopImmediatePropagation(); editor.commands.command(({ tr }) => { diff --git a/packages/editor/styles/styles.css b/packages/editor/styles/styles.css index b59a9488b..d20fdc0d7 100644 --- a/packages/editor/styles/styles.css +++ b/packages/editor/styles/styles.css @@ -657,6 +657,21 @@ p > *::selection { border-left: 1px solid var(--hover); } +.outline-list > li.nested::after { + content: ""; + position: absolute; + top: 0; + left: -40px; + width: 40px; + height: 1lh; + cursor: pointer; +} + +.outline-list[dir="rtl"] > li.nested::after { + left: unset; + right: -40px; +} + /* IMAGE */ .image-view-content-wrap.ProseMirror-selectednode .resizer::before {