Skip to content

Instantly share code, notes, and snippets.

@sb8244
Created August 5, 2024 06:40
Show Gist options
  • Save sb8244/c59acf0836eda8c79852e623afe936d1 to your computer and use it in GitHub Desktop.
Save sb8244/c59acf0836eda8c79852e623afe936d1 to your computer and use it in GitHub Desktop.
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