Skip to content

Commit b8a7c47

Browse files
authored
fix(lists paste): create custom num definitions for pasted lists (#724)
* fix(lists paste): create custom num definitions for pasted lists
1 parent f848280 commit b8a7c47

3 files changed

Lines changed: 208 additions & 109 deletions

File tree

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

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,70 @@ import { findParentNode } from '@helpers/index.js';
1717
* @param {Editor} param0.editor - The editor instance where the list definition will be added.
1818
* @returns {Object} The new abstract and num definitions.
1919
*/
20-
export const generateNewListDefinition = ({ numId, listType, editor }) => {
20+
export const generateNewListDefinition = ({ numId, listType, level, start, text, fmt, editor }) => {
2121
// Generate a new numId to add to numbering.xml
2222
if (typeof listType === 'string') listType = editor.schema.nodes[listType];
2323

2424
const definition = listType.name === 'orderedList' ? baseOrderedListDef : baseBulletList;
2525
const numbering = editor.converter.numbering;
2626
const newNumbering = { ...numbering };
27+
let skipAddingNewAbstract = false;
2728

2829
// Generate the new abstractNum definition
29-
const newAbstractId = getNewListId(editor, 'abstracts');
30-
const newAbstractDef = {
31-
...definition,
32-
attributes: {
33-
...definition.attributes,
34-
'w:abstractNumId': String(newAbstractId),
35-
},
36-
};
37-
newNumbering.abstracts[newAbstractId] = newAbstractDef;
30+
let newAbstractId = getNewListId(editor, 'abstracts');
31+
let newAbstractDef = JSON.parse(
32+
JSON.stringify({
33+
...definition,
34+
attributes: {
35+
...definition.attributes,
36+
'w:abstractNumId': String(newAbstractId),
37+
},
38+
}),
39+
);
40+
41+
// Generate the new abstractNum definition for copy/paste lists
42+
if (level && start && text && fmt) {
43+
if (newNumbering.definitions[numId]) {
44+
const abstractId = newNumbering.definitions[numId]?.elements[0]?.attributes['w:val'];
45+
newAbstractId = abstractId;
46+
const abstract = editor.converter.numbering.abstracts[abstractId];
47+
newAbstractDef = { ...abstract };
48+
skipAddingNewAbstract = true;
49+
}
50+
51+
const levelDefIndex = newAbstractDef.elements.findIndex(
52+
(el) => el.name === 'w:lvl' && el.attributes['w:ilvl'] === level,
53+
);
54+
const levelProps = newAbstractDef.elements[levelDefIndex];
55+
const elToFilter = ['w:numFmt', 'w:lvlText', 'w:start'];
56+
const oldElements = levelProps.elements.filter((el) => !elToFilter.includes(el.name));
57+
levelProps.elements = [
58+
...oldElements,
59+
{
60+
type: 'element',
61+
name: 'w:start',
62+
attributes: {
63+
'w:val': start,
64+
},
65+
},
66+
{
67+
type: 'element',
68+
name: 'w:numFmt',
69+
attributes: {
70+
'w:val': fmt,
71+
},
72+
},
73+
{
74+
type: 'element',
75+
name: 'w:lvlText',
76+
attributes: {
77+
'w:val': text,
78+
},
79+
},
80+
];
81+
}
82+
83+
if (!skipAddingNewAbstract) newNumbering.abstracts[newAbstractId] = newAbstractDef;
3884

3985
// Generate the new numId definition
4086
const newNumDef = getBasicNumIdTag(numId, newAbstractId);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
export const extractListLevelStyles = (cssText, listId, level) => {
2+
const pattern = new RegExp(`@list\\s+l${listId}:level${level}\\s*\\{([^}]+)\\}`, 'i');
3+
const match = cssText.match(pattern);
4+
if (!match) return null;
5+
6+
const rawStyles = match[1]
7+
.split(';')
8+
.map((line) => line.trim())
9+
.filter(Boolean);
10+
11+
const styleMap = {};
12+
for (const style of rawStyles) {
13+
const [key, value] = style.split(':').map((s) => s.trim());
14+
styleMap[key] = value;
15+
}
16+
17+
return styleMap;
18+
};
19+
20+
export const numDefMap = new Map([
21+
['decimal', 'decimal'],
22+
['alpha-lower', 'lowerLetter'],
23+
['alpha-upper', 'upperLetter'],
24+
['roman-lower', 'lowerRoman'],
25+
['roman-upper', 'upperRoman'],
26+
['bullet', 'bullet'],
27+
]);
28+
29+
export const numDefByTypeMap = new Map([
30+
['1', 'decimal'],
31+
['a', 'lowerLetter'],
32+
['A', 'upperLetter'],
33+
['I', 'upperRoman'],
34+
['i', 'lowerRoman'],
35+
]);
36+
37+
function getStartNumber(lvlText) {
38+
const match = lvlText.match(/^(\d+)/);
39+
if (match) return parseInt(match[1], 10);
40+
return null;
41+
}
42+
43+
function letterToNumber(letter) {
44+
return letter.toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0) + 1;
45+
}
46+
47+
function getStartNumberFromAlpha(lvlText) {
48+
const match = lvlText.match(/^([a-zA-Z])/);
49+
if (match) return letterToNumber(match[1]);
50+
return null;
51+
}
52+
53+
function romanToNumber(roman) {
54+
const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
55+
let num = 0,
56+
prev = 0;
57+
for (let i = roman.length - 1; i >= 0; i--) {
58+
const curr = map[roman[i].toUpperCase()] || 0;
59+
if (curr < prev) num -= curr;
60+
else num += curr;
61+
prev = curr;
62+
}
63+
return num;
64+
}
65+
66+
function getStartNumberFromRoman(lvlText) {
67+
const match = lvlText.match(/^([ivxlcdmIVXLCDM]+)/);
68+
if (match) return romanToNumber(match[1]);
69+
return null;
70+
}
71+
72+
export const startHelperMap = new Map([
73+
['decimal', getStartNumber],
74+
['lowerLetter', getStartNumberFromAlpha],
75+
['upperLetter', getStartNumberFromAlpha],
76+
['lowerRoman', getStartNumberFromRoman],
77+
['upperRoman', getStartNumberFromRoman],
78+
['bullet', () => 1],
79+
]);

0 commit comments

Comments
 (0)