Skip to content

Commit 7d64b17

Browse files
committed
fix: list items
1 parent 5939de1 commit 7d64b17

2 files changed

Lines changed: 82 additions & 27 deletions

File tree

packages/super-editor/src/core/helpers/list-numbering-helpers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,12 @@ export const getListItemStyleDefinitions = ({ styleId, numId, level, editor, tri
526526

527527
const numDefinition = getDefinitionForLevel(abstractDefinition, level);
528528
const numDefPpr = numDefinition?.elements.find((el) => el.name === 'w:pPr');
529+
const numLvlJs = numDefinition?.elements.find((el) => el.name === 'w:lvlJc');
529530

530531
return {
531532
stylePpr,
532533
numDefPpr,
534+
numLvlJs,
533535
};
534536
};
535537

packages/super-editor/src/extensions/list-item/ListItemNodeView.js

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { generateOrderedListIndex } from '@helpers/orderedListUtils.js';
66
import { getListItemStyleDefinitions } from '@helpers/list-numbering-helpers.js';
77
import { docxNumberigHelpers } from '@/core/super-converter/v2/importer/listImporter.js';
88

9+
const MARKER_PADDING = 6;
10+
const MARKER_OFFSET_RIGHT = 4;
11+
const MIN_MARKER_WIDTH = 20;
12+
913
const IS_DEBUGGING = false;
1014

1115
/**
@@ -60,7 +64,7 @@ export class ListItemNodeView {
6064
}
6165

6266
const pos = this.getPos();
63-
const { fontSize, fontFamily } = getTextStyleMarksFromLinkedStyles({
67+
const { fontSize, fontFamily, lineHeight } = getTextStyleMarksFromLinkedStyles({
6468
node: this.node,
6569
pos,
6670
editor: this.editor,
@@ -71,6 +75,7 @@ export class ListItemNodeView {
7175
this.dom.className = 'sd-editor-list-item-node-view';
7276
this.dom.style.fontSize = fontSize;
7377
this.dom.style.fontFamily = fontFamily ? fontFamily : 'inherit';
78+
this.dom.style.lineHeight = lineHeight || '';
7479
this.dom.setAttribute('data-marker-type', orderMarker);
7580
this.dom.setAttribute('data-num-id', numId);
7681
this.dom.setAttribute('data-list-level', JSON.stringify(listLevel));
@@ -99,27 +104,41 @@ export class ListItemNodeView {
99104
const { attrs } = this.node;
100105
const { styleId, numId, level, indent: inlineIndent } = attrs;
101106

102-
// Gather visible indents
103107
const defs = getListItemStyleDefinitions({ styleId, node: this.node, numId, level, editor: this.editor });
104108
const visibleIndent = getVisibleIndent(defs.stylePpr, defs.numDefPpr, inlineIndent);
105-
106-
let absoluteLeft = visibleIndent.left - (visibleIndent.hanging || 0);
107-
if (!absoluteLeft && absoluteLeft !== 0) absoluteLeft = 0;
108-
109-
// Place the content at the visible indent left
110-
let contentLeft = visibleIndent.left;
111-
if (IS_DEBUGGING) {
112-
console.debug('[ListItemNodeView] absoluteLeft', absoluteLeft);
113-
console.debug('[ListItemNodeView] contentLeft', contentLeft);
114-
}
115-
116-
if (visibleIndent.left === absoluteLeft) {
117-
absoluteLeft -= 24;
118-
}
119-
120-
// Apply the styling
121-
this.contentDOM.style.marginLeft = `${contentLeft}px`;
122-
this.numberingDOM.style.left = `${absoluteLeft}px`;
109+
const lvlJc = defs.numLvlJs?.attributes?.['w:val'] || 'left';
110+
111+
const contentLeft = visibleIndent.left || 0;
112+
const hanging = visibleIndent.hanging || 0;
113+
114+
const handlers = {
115+
right: () => {
116+
const calculatedWidth = calculateMarkerWidth(this.dom, this.numberingDOM);
117+
const minMarkerWidth = Math.max(calculatedWidth, MIN_MARKER_WIDTH);
118+
const effectiveHanging = Math.max(hanging, minMarkerWidth);
119+
const markerLeft = contentLeft - effectiveHanging - MARKER_OFFSET_RIGHT;
120+
this.contentDOM.style.marginLeft = `${contentLeft}px`;
121+
this.numberingDOM.style.left = `${markerLeft}px`;
122+
this.numberingDOM.style.width = `${effectiveHanging}px`;
123+
this.numberingDOM.style.textAlign = 'right';
124+
},
125+
left: () => {
126+
let markerLeft = contentLeft - hanging;
127+
if (markerLeft === contentLeft) {
128+
const calculatedWidth = calculateMarkerWidth(this.dom, this.numberingDOM);
129+
const fallbackWidth = Math.max(calculatedWidth, MIN_MARKER_WIDTH);
130+
markerLeft -= fallbackWidth;
131+
}
132+
this.contentDOM.style.marginLeft = `${contentLeft}px`;
133+
this.numberingDOM.style.left = `${markerLeft}px`;
134+
this.numberingDOM.style.width = '';
135+
this.numberingDOM.style.textAlign = '';
136+
},
137+
};
138+
139+
const handleStyles = handlers[lvlJc] ?? handlers.left;
140+
141+
handleStyles();
123142
}
124143

125144
handleNumberingClick = () => {
@@ -131,13 +150,14 @@ export class ListItemNodeView {
131150
this.node = node;
132151
this.decorations = decorations;
133152

134-
const { fontSize, fontFamily } = getTextStyleMarksFromLinkedStyles({
153+
const { fontSize, fontFamily, lineHeight } = getTextStyleMarksFromLinkedStyles({
135154
node,
136155
pos: this.getPos(),
137156
editor: this.editor,
138157
});
139158
this.dom.style.fontSize = fontSize;
140159
this.dom.style.fontFamily = fontFamily || 'inherit';
160+
this.dom.style.lineHeight = lineHeight || '';
141161
}
142162

143163
destroy() {
@@ -164,12 +184,14 @@ export function refreshAllListItemNodeViews() {
164184
* Get the text style marks from a list item
165185
* @param {Node} listItem - The list item node
166186
* @param {MarkType} markType - The mark type to look for
167-
* @returns {Array} An array of text style marks
187+
* @returns {Object} An array of text style marks and attrs object
168188
*/
169189
function getListItemTextStyleMarks(listItem, markType) {
170190
let textStyleMarks = [];
191+
let attrs = {};
171192
listItem.forEach((childNode) => {
172193
if (childNode.type.name !== 'paragraph') return;
194+
attrs.lineHeight = childNode.attrs.lineHeight;
173195
childNode.forEach((textNode) => {
174196
let isTextNode = textNode.type.name === 'text';
175197
let hasTextStyleMarks = markType.isInSet(textNode.marks);
@@ -179,7 +201,10 @@ function getListItemTextStyleMarks(listItem, markType) {
179201
}
180202
});
181203
});
182-
return textStyleMarks;
204+
return {
205+
marks: textStyleMarks,
206+
attrs,
207+
};
183208
}
184209

185210
/**
@@ -199,12 +224,13 @@ function getTextStyleMarksFromLinkedStyles({ node, pos, editor }) {
199224

200225
// 2. Find all textStyle marks on this node
201226
const textStyleType = getMarkType('textStyle', editor.schema);
202-
const allMarks = getListItemTextStyleMarks(node, textStyleType);
227+
const { marks: allMarks, attrs: allAttrs } = getListItemTextStyleMarks(node, textStyleType);
203228
const styleMarks = allMarks.filter((m) => m.type === textStyleType);
204229

205230
// 3. Helpers to find the first mark that has a fontSize / fontFamily attr
206231
const sizeMark = styleMarks.find((m) => m.attrs.fontSize);
207232
const familyMark = styleMarks.find((m) => m.attrs.fontFamily);
233+
const lineHeight = allAttrs.lineHeight;
208234

209235
// 4. Compute final fontSize (parse it, fall back to default if invalid)
210236
let fontSize = sizeMark
@@ -233,7 +259,7 @@ function getTextStyleMarksFromLinkedStyles({ node, pos, editor }) {
233259
}
234260
}
235261

236-
return { fontSize, fontFamily };
262+
return { fontSize, fontFamily, lineHeight };
237263
}
238264

239265
/**
@@ -252,7 +278,7 @@ const getStylesFromLinkedStyles = ({ node, pos, editor }) => {
252278
const decorationsInPlace = linkedStyles?.find(pos, pos + node.nodeSize);
253279
// We are looking from the end as there may be several decorations
254280
// and we need to find the most specific one.
255-
const styleDeco = decorationsInPlace?.findLast((dec) => dec.type.attrs?.style);
281+
const styleDeco = decorationsInPlace?.find((dec) => dec.type.attrs?.style);
256282
const style = styleDeco?.type.attrs?.style;
257283

258284
const stylesArray = style?.split(';') || [];
@@ -291,5 +317,32 @@ export const getVisibleIndent = (stylePpr, numDefPpr, inlineIndent) => {
291317
if (IS_DEBUGGING) console.debug('[getVisibleIndent] numDefIndent', numDefIndent, numDefIndentTag, '\n\n');
292318

293319
const indent = combineIndents(styleIndent, numDefIndent);
294-
return combineIndents(indent, inlineIndent);
320+
const result = combineIndents(indent, inlineIndent);
321+
322+
return result;
295323
};
324+
325+
function calculateMarkerWidth(dom, numberingDOM, { withPadding = true } = {}) {
326+
const markerText = numberingDOM.textContent || '';
327+
const fontSize = dom.style.fontSize || '10pt';
328+
const fontValue = dom.style.fontFamily;
329+
const fontFamily = fontValue && fontValue !== 'inherit' ? fontValue : 'Arial';
330+
331+
if (!markerText.trim()) return 0;
332+
333+
try {
334+
const canvas = document.createElement('canvas');
335+
const context = canvas.getContext('2d');
336+
337+
const fontSizePx = fontSize.includes('pt') ? Number.parseFloat(fontSize) * 1.33 : Number.parseFloat(fontSize);
338+
339+
context.font = `${fontSizePx}px ${fontFamily}`;
340+
341+
const textWidth = context.measureText(markerText).width;
342+
const resultWidth = withPadding ? Math.ceil(textWidth + MARKER_PADDING) : Math.ceil(textWidth);
343+
344+
return resultWidth;
345+
} catch (err) {
346+
return 0;
347+
}
348+
}

0 commit comments

Comments
 (0)