diff --git a/packages/editor/src/extensions/check-list-item/check-list-item.ts b/packages/editor/src/extensions/check-list-item/check-list-item.ts index 6f9b97a24..e7e277d58 100644 --- a/packages/editor/src/extensions/check-list-item/check-list-item.ts +++ b/packages/editor/src/extensions/check-list-item/check-list-item.ts @@ -17,8 +17,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ import { keybindings } from "@notesnook/common"; -import { KeyboardShortcutCommand, mergeAttributes, Node } from "@tiptap/core"; +import { + findParentNodeClosestToPos, + KeyboardShortcutCommand, + mergeAttributes, + Node +} from "@tiptap/core"; import { Node as ProseMirrorNode } from "@tiptap/pm/model"; +import { CheckList } from "../check-list/check-list"; export interface CheckListItemOptions { onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean; @@ -97,94 +103,76 @@ export const CheckListItem = Node.create({ addNodeView() { return ({ node, getPos, editor }) => { - const listItem = document.createElement("li"); - const checkboxWrapper = document.createElement("label"); - const checkboxStyler = document.createElement("span"); - const checkbox = document.createElement("input"); - const content = document.createElement("div"); + const li = document.createElement("li"); + if (node.attrs.checked) li.classList.add("checked"); + else li.classList.remove("checked"); - checkboxWrapper.contentEditable = "false"; - checkbox.type = "checkbox"; + function onClick(e: MouseEvent | TouchEvent) { + if (e instanceof MouseEvent && e.button !== 0) return; + if (!(e.target instanceof HTMLElement)) return; - checkbox.addEventListener("mousedown", (event) => { - if (globalThis.keyboardShown) { - event.preventDefault(); - } - }); + const pos = typeof getPos === "function" ? getPos() : 0; + if (typeof pos !== "number") return; + const resolvedPos = editor.state.doc.resolve(pos); - checkbox.addEventListener("change", (event) => { - event.preventDefault(); - // if the editor isn’t editable and we don't have a handler for - // readonly checks we have to undo the latest change - if (!editor.isEditable && !this.options.onReadOnlyChecked) { - checkbox.checked = !checkbox.checked; + const { x, y, right } = li.getBoundingClientRect(); - return; + const clientX = + e instanceof MouseEvent ? e.clientX : e.touches[0].clientX; + + const clientY = + e instanceof MouseEvent ? e.clientY : e.touches[0].clientY; + + const hitArea = { width: 40, height: 40 }; + + const isRtl = + e.target.dir === "rtl" || + findParentNodeClosestToPos( + resolvedPos, + (node) => !!node.attrs.textDirection + )?.node.attrs.textDirection === "rtl"; + + let xStart = clientX >= x - hitArea.width; + let xEnd = clientX <= x; + const yStart = clientY >= y; + const yEnd = clientY <= y + hitArea.height; + + if (isRtl) { + xEnd = clientX <= right + hitArea.width; + xStart = clientX >= right; } - const { checked } = event.target as any; - - if (editor.isEditable && typeof getPos === "function") { - editor - .chain() - .command(({ tr }) => { - const position = getPos(); - const currentNode = tr.doc.nodeAt(position); - - tr.setNodeMarkup(position, undefined, { - ...currentNode?.attrs, - checked - }); - - return true; - }) - .run(); + if (xStart && xEnd && yStart && yEnd) { + e.preventDefault(); + editor.commands.command(({ tr }) => { + tr.setNodeAttribute( + pos, + "checked", + !li.classList.contains("checked") + ); + return true; + }); } - if (!editor.isEditable && this.options.onReadOnlyChecked) { - // Reset state if onReadOnlyChecked returns false - if (!this.options.onReadOnlyChecked(node, checked)) { - checkbox.checked = !checkbox.checked; - } - } - }); - - if (node.attrs.checked) { - checkbox.setAttribute("checked", "checked"); } - checkboxWrapper.append(checkbox, checkboxStyler); - listItem.append(checkboxWrapper, content); + li.onmousedown = onClick; + li.ontouchstart = onClick; return { - dom: listItem, - contentDOM: content, + dom: li, + contentDOM: li, update: (updatedNode) => { if (updatedNode.type !== this.type) { return false; } + const isNested = updatedNode.lastChild?.type.name === CheckList.name; - listItem.dataset.checked = updatedNode.attrs.checked; - if (updatedNode.attrs.checked) { - checkbox.setAttribute("checked", "checked"); - } else { - checkbox.removeAttribute("checked"); - } + if (updatedNode.attrs.checked) li.classList.add("checked"); + else li.classList.remove("checked"); return true; } }; }; } - - // addInputRules() { - // return [ - // wrappingInputRule({ - // find: inputRegex, - // type: this.type, - // getAttributes: (match) => ({ - // checked: match[match.length - 1] === "x" - // }) - // }) - // ]; - // } }); diff --git a/packages/editor/src/extensions/text-direction/text-direction.ts b/packages/editor/src/extensions/text-direction/text-direction.ts index 9547eb22b..bbc67a1eb 100644 --- a/packages/editor/src/extensions/text-direction/text-direction.ts +++ b/packages/editor/src/extensions/text-direction/text-direction.ts @@ -30,6 +30,7 @@ const TEXT_DIRECTION_TYPES = [ "orderedList", "bulletList", "outlineList", + "checkList", "taskList", "table", "blockquote", diff --git a/packages/editor/styles/styles.css b/packages/editor/styles/styles.css index e676c1ee2..9450f8545 100644 --- a/packages/editor/styles/styles.css +++ b/packages/editor/styles/styles.css @@ -76,6 +76,14 @@ margin-bottom: 5px; } +.ProseMirror li:last-of-type { + margin-bottom: 0px; +} + +.ProseMirror li:first-of-type { + margin-top: 5px; +} + .ProseMirror ul.tasklist-content-wrapper { padding-left: 0px; } @@ -638,40 +646,71 @@ p > *::selection { transform: rotate(90deg); } +.simple-checklist[dir="rtl"] li::after { + left: unset; + right: -24px; +} + +.simple-checklist[dir="rtl"] li.checked::before { + left: unset; + right: -22px; +} + [dir="rtl"] .taskItemTools { right: unset; left: 0 } /* Check list */ .ProseMirror ul.simple-checklist { list-style: none; - padding: 0; + margin-block: 0px !important; + padding-inline: 0px !important; + padding-inline-start: 24px !important; } -.ProseMirror ul.simple-checklist > li { - display: flex; +.ProseMirror li.nested > ul.simple-checklist { + padding-inline-start: 15px !important; } -.ProseMirror ul.simple-checklist > li input { - flex: 0 0 auto; - margin-right: 0.5rem; - user-select: none; - height: 1rem; - width: 1rem; - accent-color: var(--accent); +.simple-checklist li { + position: relative; } -.ProseMirror ul.simple-checklist > li > div { - flex: 1 1 auto; +.simple-checklist > li::after { + position: absolute; + top: 0px; + cursor: pointer; + content: ""; + background-size: 18px; + width: 14px; + height: 14px; + + border: 2px solid var(--icon); + border-radius: 5px; + left: -24px; } -@media screen and (max-width: 480px) { - .ProseMirror ul.simple-checklist > li > input { - height: 21px; - width: 21px; - } +.simple-checklist > li.checked::after { + border: 2px solid var(--accent); +} - .ProseMirror ul.simple-checklist > li > div { - margin-top: 2px; - } +.simple-checklist > li.checked::before { + position: absolute; + top: 2px; + cursor: pointer; + content: ""; + background-size: 18px; + width: 14px; + height: 14px; + left: -22px; + + background-color: var(--accent); + mask: url(data:image/svg+xml;base64,ICA8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0yMSw3TDksMTlMMy41LDEzLjVMNC45MSwxMi4wOUw5LDE2LjE3TDE5LjU5LDUuNTlMMjEsN1oiLz4KICA8L3N2Zz4K) + no-repeat 50% 50%; + mask-size: cover; +} + +.simple-checklist > li.checked > p { + opacity: 0.8; + text-decoration-line: line-through; } /* Callout */