Skip to content

Commit 1cf9ca5

Browse files
committed
NodeClass: Walk the ancestors in a single place
1 parent 7a003c0 commit 1cf9ca5

File tree

4 files changed

+38
-41
lines changed

4 files changed

+38
-41
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Next version
1010
attributes are actually allowlisted by which extensions.
1111
- Updated the Tiptap dependency and pre-commit hooks.
1212
- Fixed the table style overrides to work better in dark mode.
13+
- Refactored the node class extension to use a single function to walk the
14+
ancestor list.
1315

1416

1517
0.18 (2025-08-27)

django_prose_editor/static/django_prose_editor/editor.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

django_prose_editor/static/django_prose_editor/editor.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nodeClass.js

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@ import { crel } from "./utils.js"
33

44
const cssClass = (c) => (typeof c === "string" ? { className: c, title: c } : c)
55

6+
const getApplicableNodes = (state, cssClasses) => {
7+
const { selection } = state
8+
const { $from } = selection
9+
const applicableNodes = []
10+
11+
let depth = $from.depth
12+
while (depth > 0) {
13+
const node = $from.node(depth)
14+
if (cssClasses[node.type.name]) {
15+
applicableNodes.push({
16+
nodeType: node.type.name,
17+
node: node,
18+
depth: depth,
19+
pos: $from.before(depth),
20+
})
21+
}
22+
depth--
23+
}
24+
25+
return applicableNodes
26+
}
27+
628
export const NodeClass = Extension.create({
729
name: "nodeClass",
830

@@ -43,32 +65,11 @@ export const NodeClass = Extension.create({
4365
},
4466

4567
addMenuItems({ buttons, menu }) {
46-
// Helper function to get all applicable node types in the current selection
47-
const getApplicableNodeTypes = (editor) => {
48-
const { selection } = editor.state
49-
const { $from } = selection
50-
const applicableNodes = []
51-
52-
let depth = $from.depth
53-
while (depth > 0) {
54-
const node = $from.node(depth)
55-
if (this.options.cssClasses[node.type.name]) {
56-
applicableNodes.push({
57-
nodeType: node.type.name,
58-
node: node,
59-
depth: depth,
60-
pos: $from.before(depth),
61-
})
62-
}
63-
depth--
64-
}
65-
66-
return applicableNodes
67-
}
68+
const cssClasses = this.options.cssClasses
6869

6970
// Helper function to get the display title for a node type
7071
const getNodeTypeTitle = (nodeType) => {
71-
const nodeConfig = this.options.cssClasses[nodeType]
72+
const nodeConfig = cssClasses[nodeType]
7273
if (
7374
typeof nodeConfig === "object" &&
7475
nodeConfig.title &&
@@ -81,7 +82,7 @@ export const NodeClass = Extension.create({
8182

8283
// Helper function to get the classes for a node type
8384
const getNodeTypeClasses = (nodeType) => {
84-
const nodeConfig = this.options.cssClasses[nodeType]
85+
const nodeConfig = cssClasses[nodeType]
8586
if (
8687
typeof nodeConfig === "object" &&
8788
nodeConfig.cssClasses &&
@@ -110,7 +111,7 @@ export const NodeClass = Extension.create({
110111
},
111112
command(editor) {
112113
// Remove classes from all applicable ancestor nodes
113-
const applicableNodes = getApplicableNodeTypes(editor)
114+
const applicableNodes = getApplicableNodes(editor.state, cssClasses)
114115
editor
115116
.chain()
116117
.focus()
@@ -125,7 +126,7 @@ export const NodeClass = Extension.create({
125126
})
126127

127128
// Create separate menu items for each node type and its classes
128-
for (const nodeType of Object.keys(this.options.cssClasses)) {
129+
for (const nodeType of Object.keys(cssClasses)) {
129130
const classes = getNodeTypeClasses(nodeType)
130131
if (!classes || classes.length === 0) continue
131132

@@ -144,25 +145,20 @@ export const NodeClass = Extension.create({
144145
}),
145146
active(editor) {
146147
// Active when this specific node type has this class
147-
const applicableNodes = getApplicableNodeTypes(editor)
148+
const applicableNodes = getApplicableNodes(editor.state, cssClasses)
148149
const targetNode = applicableNodes.find(
149150
(n) => n.nodeType === nodeType,
150151
)
151152
return targetNode && targetNode.node.attrs.class === className
152153
},
153154
hidden(editor) {
154-
const applicableNodes = getApplicableNodeTypes(editor)
155+
const applicableNodes = getApplicableNodes(editor.state, cssClasses)
155156
return !applicableNodes.some((n) => n.nodeType === nodeType)
156157
},
157158
command(editor) {
158-
const { selection } = editor.state
159-
const { $from } = selection
160-
161-
let depth = $from.depth
162-
while (depth > 0) {
163-
const node = $from.node(depth)
159+
const applicableNodes = getApplicableNodes(editor.state, cssClasses)
160+
for (const { node, pos } of applicableNodes) {
164161
if (node.type.name === nodeType) {
165-
const pos = $from.before(depth)
166162
editor
167163
.chain()
168164
.focus()
@@ -173,7 +169,6 @@ export const NodeClass = Extension.create({
173169
.run()
174170
return
175171
}
176-
depth--
177172
}
178173
},
179174
})

0 commit comments

Comments
 (0)