diff --git a/frontend/components/Editor/index.js b/frontend/components/Editor/index.js index a5c00171..eed1c713 100644 --- a/frontend/components/Editor/index.js +++ b/frontend/components/Editor/index.js @@ -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'; @@ -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'] }, diff --git a/frontend/services/quill/msWordPasteMatchers.js b/frontend/services/quill/msWordPasteMatchers.js new file mode 100644 index 00000000..c8041783 --- /dev/null +++ b/frontend/services/quill/msWordPasteMatchers.js @@ -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;