Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pasting bullet and ordered lists from Word into Memcode editor #163

Merged
merged 2 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions frontend/components/Editor/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import standardToolbarContainer from '~/services/quill/standardToolbarContainer';
import dropOrPasteImageHandler from '~/services/quill/handlers/dropOrPasteImageHandler';
import msWordPasteMatchers from '~/services/quill/msWordPasteMatchers';
import uploadImageHandler from '~/services/quill/handlers/uploadImageHandler';
import markAsAnswerHandler from '~/services/quill/handlers/markAsAnswerHandler';
import codeBlockHandler from '~/services/quill/handlers/codeBlockHandler';
Expand Down Expand Up @@ -208,8 +209,12 @@ class Editor extends React.Component {
bindings
},

// https://github.com/zenoamaro/react-quill/issues/250
clipboard: { matchVisual: false },
clipboard: {
// https://github.com/zenoamaro/react-quill/issues/250
matchVisual: false,
// https://github.com/lakesare/memcode/pull/163
matchers: msWordPasteMatchers
},
imageResize: {
modules: ['Resize']
},
Expand Down
52 changes: 52 additions & 0 deletions frontend/services/quill/msWordPasteMatchers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// See https://github.com/quilljs/quill/issues/1225#issuecomment-1000785590

import Delta from 'quill-delta';

function matchMsWordList(node, delta) {
// Clone the operations
let ops = delta.ops.map((op) => Object.assign({}, op));

// Trim the front of the first op to remove the bullet/number
let bulletOp = ops.find((op) => op.insert && op.insert.trim().length);
if (!bulletOp) { return delta }

bulletOp.insert = bulletOp.insert.trimLeft();
let listPrefix = bulletOp.insert.match(/^.*?(^·|\.)/) || bulletOp.insert[0];
bulletOp.insert = bulletOp.insert.substring(listPrefix[0].length, bulletOp.insert.length);

// Trim the newline off the last op
let last = ops[ops.length-1];
last.insert = last.insert.substring(0, last.insert.length - 1);

// Determine the list type
let listType = listPrefix[0].length === 1 ? 'bullet' : 'ordered';

// 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);
}

function maybeMatchMsWordList(node, delta) {
if (delta.ops[0].insert.trimLeft()[0] === '·') {
return matchMsWordList(node, delta);
}

return delta;
}

const msWordPasteMatchers = [
['p.MsoListParagraphCxSpFirst', matchMsWordList],
['p.MsoListParagraphCxSpMiddle', matchMsWordList],
['p.MsoListParagraphCxSpLast', matchMsWordList],
['p.MsoListParagraph', matchMsWordList],
['p.msolistparagraph', matchMsWordList],
['p.MsoNormal', maybeMatchMsWordList]
];

export default msWordPasteMatchers;