mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
Merge pull request #8457 from 01zulfi/editor/tasklist-checklist-styling
editor: improve checklist styling
This commit is contained in:
@@ -17,8 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<CheckListItemOptions>({
|
||||
|
||||
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"
|
||||
// })
|
||||
// })
|
||||
// ];
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ const TEXT_DIRECTION_TYPES = [
|
||||
"orderedList",
|
||||
"bulletList",
|
||||
"outlineList",
|
||||
"checkList",
|
||||
"taskList",
|
||||
"table",
|
||||
"blockquote",
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user