Copying and pasting unordered bullets from Word puts a bullet symbol in the editor instead of an actual bullet.
Steps for Reproduction
Expected behavior:
The bullet gets removed
Actual behavior:
A real bullet gets created, containing the bullet symbol from the clipboard
Platforms:
All
Version:
All
I'd love to get some guidance on a proper approach to fixing this issue.
I've been playing around with creating a custom matcher for clipboard to do this. The matcher essentially ignores any "p.MsoListParagraphCxSpMiddle" or "p.MsoListParagraphCxSpLast" tags (returns a Delta that doesn't do anything), and, for any "p.MsoListParagraphCxSpFirst" iterates through that tag, and its siblings, until it finds the "...SpLast" tag.
But, from there, I'm not sure exactly what the right thing to do is. The deltas that you get from creating a bullet list manually in quill are kind of strange, and I'm not sure that the matcher should be creating them from scratch? Should it be creating deltas from a List blot? I'm a bit confused as to whether or not I'm even on the right track.
How you looked at how the officially supported matchers in the clipboard work? It uses its own API the same way a third party would. If so what are specific things you have tried and have not gotten to work?
Yes, I've looked at the built-in matchers. http://codepen.io/anon/pen/ENqRdP is what I have so far, but I'm not sure what should go in the "addNodeToDelta" function. None of the built-in matchers quite seem to do what I'm trying to do here, unless I'm misunderstanding them.
The purpose of a matcher is to return a Delta representing a given node. If you fulfill this contract, the clipboard can build a Delta for the entire pasted tree. By traversing siblings and attempting to return Deltas for them instead, you are not fulfilling this contract. I would also suggest taking a look at Delta documentation. One of the more important takeaways from the Delta docs is not to create them by hand.
Yes, I did read the Delta documentation. But I think, in this case, what I want to do is to construct a single delta with a List blot embedded, which corresponds to all the paragraphs that correspond to bullets. Is that not correct? You say that the contract is a one-to-one correspondence between deltas and nodes, yet there are a number of times in https://github.com/quilljs/quill/blob/develop/modules/clipboard.js where previousSibling, nextSibling, etc. are called. What I can't find an example of is a matcher which results in a particular blot.
return { ops: [] } is constructing a Delta by hand. When Quill's clipboard is using sibling, it does so for context about the current Delta.
@arwagner did you had any luck with this issue?
So after a few hours of works, I have a solution. IMHO it is probably not the most elegant, but it works for unordered lists pasted from MS Word. Unfortunately, it does not work for ordered lists (any hints why), the implementation seems the same as for unordered lists.
const MSwordMatcher = function (node, delta) {
const _build = [];
while (true) {
if (node) {
if (node.tagName === 'P') {
const content = node.querySelectorAll('span'); //[0] index contains bullet or numbers, [1] index contains spaces, [2] index contains item content
const _nodeText = content[2].innerText.trim();
//const _listType = content[0].innerText.match(/[0-9]/g) ? 'ordered' : 'bullet'; //@TODO: implement ordered lists
_build.push({ insert: `${_nodeText}\n`, attributes: { 'bullet': true } });
if (node.className === 'MsoListParagraphCxSpLast') {
break;
}
}
}
node = node.nextSibling;
}
return new Delta(_build);
};
const matcherNoop = (node, delta) => ({ ops: [] });
While initing quill
modules: {
clipboard: {
matchers: [
['p.MsoListParagraphCxSpFirst', MSwordMatcher],
['p.MsoListParagraphCxSpMiddle', matcherNoop],
['p.MsoListParagraphCxSpLast', matcherNoop],
]
},
}
ping @arwagner (if you are still interested)
I tried to take the example from @DavidReinberger and apply the feedback from @jhchen on this issue. I wanted to preserve bullet vs ordered, indentation as well as allow HTML within each list item. Any feedback / suggestions are welcome.
Note: I am using underscore in the below code, but that could be removed.
const MSWORD_MATCHERS = [
['p.MsoListParagraphCxSpFirst', matchMsWordList],
['p.MsoListParagraphCxSpMiddle', matchMsWordList],
['p.MsoListParagraphCxSpLast', matchMsWordList],
];
function matchMsWordList(node, delta) {
// Clone the operations
let ops = _.map(delta.ops, _.clone);
// Trim the front of the first op to remove the bullet/number
let first = _.first(ops);
first.insert = first.insert.trimLeft();
let firstMatch = first.insert.match(/^(\S+)\s+/);
if (!firstMatch) return delta;
first.insert = first.insert.substring(firstMatch[0].length, first.insert.length);
// Trim the newline off the last op
let last = _.last(ops);
last.insert = last.insert.substring(0, last.insert.length - 1);
// Determine the list type
let prefix = firstMatch[1];
let listType = prefix.match(/\S+\./) ? 'ordered' : 'bullet';
// Determine the list indent
let style = node.getAttribute('style').replace(/\n+/g, '')
let levelMatch = style.match(/level(\d+)/);
let indent = levelMatch ? levelMatch[1] - 1 : 0;
// Add the list attribute
ops.push({insert: '\n', attributes: {list: listType, indent}})
return new Delta(ops);
}
Thanks @SamDuvall, your matchers are working flawlessly for me.
Most helpful comment
I tried to take the example from @DavidReinberger and apply the feedback from @jhchen on this issue. I wanted to preserve bullet vs ordered, indentation as well as allow HTML within each list item. Any feedback / suggestions are welcome.
Note: I am using underscore in the below code, but that could be removed.