Created
August 5, 2024 06:40
-
-
Save sb8244/c59acf0836eda8c79852e623afe936d1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Plugin, PluginKey, Transaction } from "prosemirror-state" | |
import { BlockInfo, getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos" | |
// ProseMirror Plugin which automatically assigns indices to ordered list items per nesting level. | |
const PLUGIN_KEY = new PluginKey(`numbered-list-indexing`) | |
interface Options { | |
getListCharacter?: (positionDetails: { depth: number; index: number }) => string | |
} | |
const defaultGetListCharacter = (position: { index: number }) => position.index.toString() | |
export const NumberedListIndexingPlugin = (opts: Options = {}) => { | |
const getListCharacter = opts.getListCharacter || defaultGetListCharacter | |
return new Plugin({ | |
key: PLUGIN_KEY, | |
appendTransaction: (_transactions, _oldState, newState) => { | |
const tr = newState.tr | |
tr.setMeta("numberedListIndexing", true) | |
let modified = false | |
// Traverses each node the doc using DFS, so blocks which are on the same nesting level will be traversed in the | |
// same order they appear. This means the index of each list item block can be calculated by incrementing the | |
// index of the previous list item block. | |
newState.doc.descendants((node, pos) => { | |
if (node.type.name === "blockContainer" && node.firstChild!.type.name === "numberedListItem") { | |
const blockInfo = getBlockInfoFromPos(tr.doc, pos + 1)! | |
if (blockInfo === undefined) { | |
return | |
} | |
let firstListBlock = blockInfo | |
let blockIndex = 1 | |
const parentBlock = findParentBlockOrSelf(firstListBlock.startPos, blockInfo, tr.doc) | |
// Divide by 2 because each block has a nesting around it | |
const depth = (blockInfo.depth - parentBlock.depth) / 2 + 1 | |
while (firstListBlock) { | |
const prevBlockInfo = getBlockInfoFromPos(tr.doc, firstListBlock.startPos - 2)! | |
if ( | |
prevBlockInfo && | |
prevBlockInfo.id !== firstListBlock.id && | |
prevBlockInfo.depth === firstListBlock.depth && | |
prevBlockInfo.contentType === firstListBlock.contentType | |
) { | |
blockIndex++ | |
firstListBlock = prevBlockInfo | |
} else { | |
break | |
} | |
} | |
const newIndex = getListCharacter({ depth, index: blockIndex }) | |
const contentNode = blockInfo.contentNode | |
const index = contentNode.attrs["index"] | |
if (index !== newIndex) { | |
modified = true | |
tr.setNodeMarkup(pos + 1, undefined, { | |
index: newIndex | |
}) | |
} | |
} | |
}) | |
return modified ? tr : null | |
} | |
}) | |
} | |
function findParentBlockOrSelf(startPos: number, blockInfo: BlockInfo, doc: Transaction["doc"]) { | |
let currIndex = startPos | |
let parentBlock = blockInfo | |
while (currIndex >= 0) { | |
currIndex -= 2 | |
const maybeParent = getBlockInfoFromPos(doc, currIndex)! | |
const isDeeper = maybeParent.depth < parentBlock.depth | |
const isSameType = maybeParent.contentType === blockInfo.contentType | |
if (isDeeper && !isSameType) { | |
break | |
} else if (isDeeper && isSameType) { | |
// If the block depth is less than the current block, it must be the next parent | |
parentBlock = maybeParent | |
} | |
} | |
return parentBlock | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Used like: