[WEB-1526] feat: add auto merge behaviour to task lists and fix infinite backspace case (#4703)

* feat: add auto merge behaviour to task lists

* fix: unhandled cases for taskItem and taskList

* fix: css task list such that toggling task list doesn't shift things

* fix: task list jumps around while trying create/delete things in between two task lists

* fix: remove filtering for generic transactions i.e. transactions with some meta data while tying to join things
This commit is contained in:
M. Palanikannan 2024-06-07 12:36:19 +05:30 committed by GitHub
parent f5656111ee
commit b1c7e6ae20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 25 deletions

View File

@ -110,6 +110,11 @@ ul[data-type="taskList"] li > label input[type="checkbox"]:checked:hover {
}
}
/* the p tag just after the ul tag */
ul[data-type="taskList"] + p {
margin-top: 0.4rem !important;
}
ul[data-type="taskList"] li > label input[type="checkbox"] {
position: relative;
-webkit-appearance: none;
@ -152,6 +157,10 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
}
}
ul[data-type="taskList"] li > div > p {
margin-top: 10px;
}
ul[data-type="taskList"] li[data-checked="true"] > div > p {
color: rgb(var(--color-text-400));
text-decoration: line-through;

View File

@ -72,7 +72,7 @@ const getPrevListDepth = (typeOrName: string, state: EditorState) => {
// Traverse up the document structure from the adjusted position
for (let d = resolvedPos.depth; d > 0; d--) {
const node = resolvedPos.node(d);
if (node.type.name === "bulletList" || node.type.name === "orderedList") {
if (node.type.name === "bulletList" || node.type.name === "orderedList" || node.type.name === "taskList") {
// Increment depth for each list ancestor found
depth++;
}
@ -146,6 +146,8 @@ export const handleBackspace = (editor: Editor, name: string, parentListTypes: s
if (!isAtStartOfNode(editor.state)) {
return false;
}
// is the paragraph node inside of the current list item (maybe with a hard break)
const isParaSibling = isCurrentParagraphASibling(editor.state);
const isCurrentListItemSublist = prevListIsHigher(name, editor.state);
const listItemPos = findListItemPos(name, editor.state);
@ -306,7 +308,10 @@ const isCurrentParagraphASibling = (state: EditorState): boolean => {
const currentParagraphNode = $from.parent; // Get the current node where the selection is.
// Ensure we're in a paragraph and the parent is a list item.
if (currentParagraphNode.type.name === "paragraph" && listItemNode.type.name === "listItem") {
if (
currentParagraphNode.type.name === "paragraph" &&
(listItemNode.type.name === "listItem" || listItemNode.type.name === "taskItem")
) {
let paragraphNodesCount = 0;
listItemNode.forEach((child) => {
if (child.type.name === "paragraph") {
@ -327,16 +332,19 @@ export function isCursorInSubList(editor: Editor) {
// Check if the current node is a list item
const listItem = editor.schema.nodes.listItem;
const taskItem = editor.schema.nodes.taskItem;
// Traverse up the document tree from the current position
for (let depth = $from.depth; depth > 0; depth--) {
const node = $from.node(depth);
if (node.type === listItem) {
if (node.type === listItem || node.type === taskItem) {
// If the parent of the list item is also a list, it's a sub-list
const parent = $from.node(depth - 1);
if (
parent &&
(parent.type === editor.schema.nodes.bulletList || parent.type === editor.schema.nodes.orderedList)
(parent.type === editor.schema.nodes.bulletList ||
parent.type === editor.schema.nodes.orderedList ||
parent.type === editor.schema.nodes.taskList)
) {
return true;
}

View File

@ -15,9 +15,7 @@ declare module "@tiptap/core" {
}
}
function autoJoin(tr: Transaction, newTr: Transaction, nodeType: NodeType) {
if (!tr.isGeneric) return false;
function autoJoin(tr: Transaction, newTr: Transaction, nodeTypes: NodeType[]) {
// Find all ranges where we might want to join.
const ranges: Array<number> = [];
for (let i = 0; i < tr.mapping.maps.length; i++) {
@ -28,7 +26,7 @@ function autoJoin(tr: Transaction, newTr: Transaction, nodeType: NodeType) {
// Figure out which joinable points exist inside those ranges,
// by checking all node boundaries in their parent nodes.
const joinable = [];
const joinable: number[] = [];
for (let i = 0; i < ranges.length; i += 2) {
const from = ranges[i],
to = ranges[i + 1];
@ -40,7 +38,7 @@ function autoJoin(tr: Transaction, newTr: Transaction, nodeType: NodeType) {
if (!after) break;
if (index && joinable.indexOf(pos) == -1) {
const before = parent.child(index - 1);
if (before.type == after.type && before.type === nodeType) joinable.push(pos);
if (before.type == after.type && nodeTypes.includes(before.type)) joinable.push(pos);
}
pos += after.nodeSize;
}
@ -88,25 +86,15 @@ export const CustomKeymap = Extension.create({
// Create a new transaction.
const newTr = newState.tr;
let joined = false;
for (const transaction of transactions) {
const anotherJoin = autoJoin(transaction, newTr, newState.schema.nodes["orderedList"]);
joined = anotherJoin || joined;
}
if (joined) {
return newTr;
}
},
}),
new Plugin({
key: new PluginKey("unordered-list-merging"),
appendTransaction(transactions, oldState, newState) {
// Create a new transaction.
const newTr = newState.tr;
const joinableNodes = [
newState.schema.nodes["orderedList"],
newState.schema.nodes["taskList"],
newState.schema.nodes["bulletList"],
];
let joined = false;
for (const transaction of transactions) {
const anotherJoin = autoJoin(transaction, newTr, newState.schema.nodes["bulletList"]);
const anotherJoin = autoJoin(transaction, newTr, joinableNodes);
joined = anotherJoin || joined;
}
if (joined) {