Here is some workaround working for me: completely remove the base bullets and replace them using draftjs style of the first character of each li. Those are then rendered by the blockRendererFn callback of the Editor:
First, remove the normal list styling and prepare yours:
ul, ol {
list-style: none!important;
margin-left: 0!important;
li {
.liContent {
display: flex;
div { width: 100%; }
.bullet {
align-self: center;
margin-right: .5em;
}
}
&::before, &::marker {
display: none !important; // Not working everytimes (?!), thus content = ''
content: '';
}
}
}
Then, simply adapt this code for your needs (you may add whatever bullets/levels you need in the baseBullets constant:
const orderedList = 0, unorderedList = 1;
const baseBullets = [[1, 'a'], ['•', '▫', '▪']];
const leavingLevel = 0, initiatingLevel = 1, crossingLevel = 2;
// Returns the previous or the next bullet of a given bullet
function GetSisterBullet(idx, type, whichSister) {
if (type === orderedList) {
if (Number.isInteger(idx)) return idx + whichSister;
else return String.fromCharCode(idx.charCodeAt(0) + whichSister); // Let's assume, for now, that we won't have more than 26 sublevels...
} return idx;
}
// Gets the bullet of the very first entry of a given level
function GetLevelBaseBullet(depth, type, action) {
let curLevelBaseIdx = baseBullets[type][depth % baseBullets[type].length];
return action === leavingLevel ? GetSisterBullet(curLevelBaseIdx, type, -1) : curLevelBaseIdx;
}
// Set the bullet to be displayed for the next list/sublist entry
function SetLevelNextEntryBullet(type, depth, action = crossingLevel) {
switch (action) {
case crossingLevel: listEntriesLastIdx[type][depth] = GetSisterBullet(listEntriesLastIdx[type][depth], type, 1); break;
case leavingLevel:
listEntriesLastIdx[type].splice(depth, 1);
if (depth > 0 && !listEntriesLastIdx[type][depth - 1]) // Happens when two separate lists fellow each other
listEntriesLastIdx[type][depth - 1] = GetLevelBaseBullet(depth - 1, type, initiatingLevel); //
break;
default: listEntriesLastIdx[type][depth] = GetLevelBaseBullet(depth, type, action); break;
}
}
// And finaly, the Editor blockRendererFn callback
const handleBlockRender = block => {
let blockType = -1;
switch (block.getType()) {
case 'ordered-list-item': blockType = orderedList; break;
case 'unordered-list-item': blockType = unorderedList; break;
default: break;
}
if (blockType >= 0) { // List block/tree
// Update the levels entries counters
if (block.depth == curListLvl)
SetLevelNextEntryBullet(blockType, curListLvl)
else {
if (!listEntriesLastIdx[blockType])
listEntriesLastIdx[blockType] = [];
//-----
if (block.depth < curListLvl) SetLevelNextEntryBullet(blockType, curListLvl, leavingLevel);
else SetLevelNextEntryBullet(blockType, block.depth, listEntriesLastIdx[blockType][block.depth] ? undefined : initiatingLevel);
curListLvl = block.depth;
}
// Rendering
let levelIndexToDisplay = listEntriesLastIdx[blockType][curListLvl]; // Important to pass through an other variable because of the callback it will be used in
let HTMLStyles = EditorStylesToHTMLStyles(block.getInlineStyleAt(0)); // See bellow for this function definition, if you don't have your own function already
return {
component: props =>
<div className="liContent">
<span className="bullet" style={HTMLStyles}>{`${levelIndexToDisplay}${blockType === orderedList ? '.' : ''}`}</span>
<EditorBlock {...props} />
</div>
};
}
else {
// Leaving the actual list tree
listEntriesLastIdx = [];
curListLvl = -1;
}
// Rendering all other blocks here, if needed, or return nothing for the generic rendering
...
};
And, if you don't have yours, the functions which retrieve the HTML style at a given position of draftjs content, to also adapt depending on your needs:
function EditorStylesToHTMLStyles(editorStyles) {
return editorStyles
.map(editorStyle => GetHTMLStyles(editorStyle))
.toArray()
.reduce((acc, styles) => { return { ...acc, ...styles }; }, {});
}
function GetHTMLStyles(editorStyle) {
let matches = null;
if (matches = editorStyle.match(/fontsize-(.*)/))
return { fontSize: matches[1] + 'px' };
else if (matches = editorStyle.match(/color-(.*)/))
return { color: matches[1] };
else if (matches = editorStyle.match(/fontfamily-(.*)/))
return { fontFamily: matches[1] };
else switch (editorStyle) {
case 'BOLD': return { fontWeight: 'bold' };
case 'ITALIC': return { fontStyle: 'italic' };
case 'SUPERSCRIPT': return { fontSize: '.7rem', position: 'relative', top: '-.5rem' };
case 'SUBSCRIPT': return { fontSize: '.7rem', position: 'relative', bottom: '-.5rem' };
case 'UNDERLINE': return { textDecoration: 'underline' };
case 'STRIKETHROUGH': return { textDecoration: 'line-through' };
default: return {};
}
}