Skip to content

Commit cd7ae0f

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

2 files changed

Lines changed: 85 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: 83 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,44 @@ 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+
const calculatedWidth = calculateMarkerWidth(this.dom, this.numberingDOM);
127+
const minMarkerWidth = Math.max(calculatedWidth, MIN_MARKER_WIDTH);
128+
let markerLeft = contentLeft - hanging;
129+
if (markerLeft === contentLeft) {
130+
markerLeft -= minMarkerWidth;
131+
} else if (minMarkerWidth > hanging) {
132+
const diff = minMarkerWidth - hanging;
133+
markerLeft -= diff;
134+
}
135+
this.contentDOM.style.marginLeft = `${contentLeft}px`;
136+
this.numberingDOM.style.left = `${markerLeft}px`;
137+
this.numberingDOM.style.width = '';
138+
this.numberingDOM.style.textAlign = '';
139+
},
140+
};
141+
142+
const handleStyles = handlers[lvlJc] ?? handlers.left;
143+
144+
handleStyles();
123145
}
124146

125147
handleNumberingClick = () => {
@@ -131,13 +153,14 @@ export class ListItemNodeView {
131153
this.node = node;
132154
this.decorations = decorations;
133155

134-
const { fontSize, fontFamily } = getTextStyleMarksFromLinkedStyles({
156+
const { fontSize, fontFamily, lineHeight } = getTextStyleMarksFromLinkedStyles({
135157
node,
136158
pos: this.getPos(),
137159
editor: this.editor,
138160
});
139161
this.dom.style.fontSize = fontSize;
140162
this.dom.style.fontFamily = fontFamily || 'inherit';
163+
this.dom.style.lineHeight = lineHeight || '';
141164
}
142165

143166
destroy() {
@@ -164,12 +187,14 @@ export function refreshAllListItemNodeViews() {
164187
* Get the text style marks from a list item
165188
* @param {Node} listItem - The list item node
166189
* @param {MarkType} markType - The mark type to look for
167-
* @returns {Array} An array of text style marks
190+
* @returns {Object} An array of text style marks and attrs object
168191
*/
169192
function getListItemTextStyleMarks(listItem, markType) {
170193
let textStyleMarks = [];
194+
let attrs = {};
171195
listItem.forEach((childNode) => {
172196
if (childNode.type.name !== 'paragraph') return;
197+
attrs.lineHeight = childNode.attrs.lineHeight;
173198
childNode.forEach((textNode) => {
174199
let isTextNode = textNode.type.name === 'text';
175200
let hasTextStyleMarks = markType.isInSet(textNode.marks);
@@ -179,7 +204,10 @@ function getListItemTextStyleMarks(listItem, markType) {
179204
}
180205
});
181206
});
182-
return textStyleMarks;
207+
return {
208+
marks: textStyleMarks,
209+
attrs,
210+
};
183211
}
184212

185213
/**
@@ -199,12 +227,13 @@ function getTextStyleMarksFromLinkedStyles({ node, pos, editor }) {
199227

200228
// 2. Find all textStyle marks on this node
201229
const textStyleType = getMarkType('textStyle', editor.schema);
202-
const allMarks = getListItemTextStyleMarks(node, textStyleType);
230+
const { marks: allMarks, attrs: allAttrs } = getListItemTextStyleMarks(node, textStyleType);
203231
const styleMarks = allMarks.filter((m) => m.type === textStyleType);
204232

205233
// 3. Helpers to find the first mark that has a fontSize / fontFamily attr
206234
const sizeMark = styleMarks.find((m) => m.attrs.fontSize);
207235
const familyMark = styleMarks.find((m) => m.attrs.fontFamily);
236+
const lineHeight = allAttrs.lineHeight;
208237

209238
// 4. Compute final fontSize (parse it, fall back to default if invalid)
210239
let fontSize = sizeMark
@@ -233,7 +262,7 @@ function getTextStyleMarksFromLinkedStyles({ node, pos, editor }) {
233262
}
234263
}
235264

236-
return { fontSize, fontFamily };
265+
return { fontSize, fontFamily, lineHeight };
237266
}
238267

239268
/**
@@ -252,7 +281,7 @@ const getStylesFromLinkedStyles = ({ node, pos, editor }) => {
252281
const decorationsInPlace = linkedStyles?.find(pos, pos + node.nodeSize);
253282
// We are looking from the end as there may be several decorations
254283
// and we need to find the most specific one.
255-
const styleDeco = decorationsInPlace?.findLast((dec) => dec.type.attrs?.style);
284+
const styleDeco = decorationsInPlace?.find((dec) => dec.type.attrs?.style);
256285
const style = styleDeco?.type.attrs?.style;
257286

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

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

0 commit comments

Comments
 (0)