{"id":72399,"date":"2025-10-02T11:39:03","date_gmt":"2025-10-02T06:09:03","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=72399"},"modified":"2025-10-13T14:46:02","modified_gmt":"2025-10-13T09:16:02","slug":"resolve-microsoft-word-list-copy-paste-issues-in-aem-rte","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/resolve-microsoft-word-list-copy-paste-issues-in-aem-rte\/","title":{"rendered":"Resolve Microsoft Word List Copy-Paste Issues in AEM RTE"},"content":{"rendered":"<p>Our client raised an issue stating the list conversion is not happening properly while copy-pasting the text from MS Word desktop App to RTE. When I explored, I found that the RTE component in AEM has mainly two paste modes (<strong>wordhtml<\/strong> and <strong>plaintext<\/strong>). Plain text mode scraps all the mark-up as the mode name suggests. While \u201cwordhtml\u201d keeps the markups and works well for most of the tags. But when authors copy any list (ordered \/ unordered) from Microsoft Word document ( Desktop application ) and try to paste in RTE directly, it doesn&#8217;t paste it well.<\/p>\n<p>While going through OOTB implementation, found comment in EditToolsPlugin js stating <strong>ol &amp; ul <\/strong>tags are not supported at the moment( current release of AEM 6.5.23 and cloud ) and hence as a fallback it creates individual &lt;p&gt; tags\u00a0 with dot (.) and 6 span tags\u00a0 instead of ul or ol. Hence, <strong>list is not being currently converted to &lt;ul&gt;, &lt;ol&gt;<\/strong> and it is mentioned clearly in EditToolsPlugin js.<\/p>\n<div id=\"attachment_72735\" style=\"width: 734px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72735\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72735 size-full\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/EditToolsPlugin.png\" alt=\"EditToolsPlugin\" width=\"724\" height=\"577\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/EditToolsPlugin.png 724w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/EditToolsPlugin-300x239.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/EditToolsPlugin-624x497.png 624w\" sizes=\"(max-width: 724px) 100vw, 724px\" \/><p id=\"caption-attachment-72735\" class=\"wp-caption-text\">EditToolsPlugin<\/p><\/div>\n<p>This leads to inconsistency as well as frustration if authors try to correct it novicely. We can see the extra spans with dots instead of ul\/li tags upon copy paste to rte from MsWord in the image below.<\/p>\n<div id=\"attachment_76626\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-76626\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-76626\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/09\/Screenshot-from-2025-09-29-14-09-07.png\" alt=\"copy paste to rte\" width=\"1105\" height=\"427\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/09\/Screenshot-from-2025-09-29-14-09-07.png 1158w, \/blog\/wp-ttn-blog\/uploads\/2025\/09\/Screenshot-from-2025-09-29-14-09-07-300x116.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/09\/Screenshot-from-2025-09-29-14-09-07-1024x396.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/09\/Screenshot-from-2025-09-29-14-09-07-768x297.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/09\/Screenshot-from-2025-09-29-14-09-07-624x241.png 624w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-76626\" class=\"wp-caption-text\">copy-paste to RTE<\/p><\/div>\n<p>So let&#8217;s delve into a solution which mitigates the discussed issue.<\/p>\n<h3>Solution<\/h3>\n<p>As a part of solution we will customised the JavaScript code of OOTB plugin where we intercept the paste operation to clean the MsWord formatting and change it to html &lt;ul&gt;\/&lt;ol&gt; tags along with preserving the other tags like &lt;b&gt;,&lt;i&gt;,&lt;u&gt;, etc if any already applied. Also paste operation can be done either directly in RTE textfield area or via paste plugin icon and hence handling of both scenarios is discussed below.<\/p>\n<p><strong>Step 1: Clientlibs Creation<\/strong><\/p>\n<p>Create a clientlibs to limit the scope of this custom plugin to RTE only and add it category to extra client properties of the RTE dialog as shown below.<\/p>\n<div id=\"attachment_72736\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72736\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72736\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/AddCategoryToTextCmp-1024x673.png\" alt=\"extra clientlibs \" width=\"1105\" height=\"726\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/AddCategoryToTextCmp-1024x673.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/AddCategoryToTextCmp-300x197.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/AddCategoryToTextCmp-768x505.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/AddCategoryToTextCmp-624x410.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/AddCategoryToTextCmp.png 1140w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72736\" class=\"wp-caption-text\">extra clientlibs<\/p><\/div>\n<p><strong>Step 2: Custom solution<\/strong><\/p>\n<p>Now it&#8217;s time to override (copy and paste) the OOTB <strong>edit plugin\u00a0<\/strong> from <strong>\/libs\/clientlibs\/granite\/richtext\/core\/js\/plugins\/EditToolsPlugin.js<\/strong> to our clientlibs js (edit-tools-plugin-workaround-for-paste.js)\u00a0 file created in step 1.<\/p>\n<p>Lets search for the <strong>afterPaste<\/strong> method and put the below line as displayed in the image.<\/p>\n<p><code>clipNode.innerHTML = convertWordListsToUL(clipNode.innerHTML);<\/code><\/p>\n<div id=\"attachment_72737\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72737\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72737\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/afterPasteMethod.png\" alt=\"afterPaste\" width=\"1105\" height=\"932\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/afterPasteMethod.png 881w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/afterPasteMethod-300x253.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/afterPasteMethod-768x648.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/afterPasteMethod-624x526.png 624w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72737\" class=\"wp-caption-text\">afterPaste<\/p><\/div>\n<p>Lets keep the whole lines of <strong>convertWordListsToUL<\/strong> function from this blog in the last of <strong>edit-tools-plugin-workaround-for-paste.js<\/strong> which transforms the list from MS word to &lt;ul&gt; \/ &lt;ol&gt; tags.<\/p>\n<div id=\"attachment_72738\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72738\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72738\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/ConvertWordListToUl.png\" alt=\"ConvertWordListToUL\" width=\"1105\" height=\"936\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/ConvertWordListToUl.png 760w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/ConvertWordListToUl-300x254.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/ConvertWordListToUl-624x529.png 624w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72738\" class=\"wp-caption-text\">ConvertWordListToUL<\/p><\/div>\n<pre>function convertWordListsToUL(inputHtml) {\r\nconst tempDiv = document.createElement('div');\r\ntempDiv.innerHTML = inputHtml;\r\nfunction cleanNode(node) {\r\nconst comments = [];\r\nconst walker = document.createTreeWalker(node, NodeFilter.SHOW_COMMENT, null);\r\nlet comment;\r\nwhile (comment = walker.nextNode()) {\r\ncomments.push(comment);\r\n}\r\ncomments.forEach(comment =&gt; comment.parentNode.removeChild(comment));\r\n\/\/ Remove o:p tags\r\nconst oTags = node.querySelectorAll('o\\\\:p');\r\noTags.forEach(tag =&gt; tag.parentNode.removeChild(tag));\r\nreturn node;\r\n}\r\nfunction getListLevel(style) {\r\nif (!style) return 1;\r\nconst levelMatch = style.match(\/level(\\d+)\/i);\r\nreturn levelMatch ? parseInt(levelMatch[1], 10) : 1;\r\n}\r\nfunction getListType(bulletText) {\r\nif (!bulletText) return 'ul';\r\nconst orderedPatterns = [\r\n\/^\\d+\\.$\/,\u00a0 \u00a0 \u00a0 \u00a0 \/\/ 1., 2., 3.\r\n\/^[a-z]\\.$\/i, \u00a0 \u00a0 \/\/ a., b., c.\r\n\/^[ivx]+\\.$\/i,\u00a0 \u00a0 \/\/ i., ii., iii.\r\n\/^\\(\\d+\\)$\/,\u00a0 \u00a0 \u00a0 \/\/ (1), (2)\r\n\/^\\[\\d+\\]$\/,\u00a0 \u00a0 \u00a0 \/\/ [1], [2]\r\n\/^[a-z]\\)$\/i, \u00a0 \u00a0 \/\/ a), b)\r\n\/^[ivx]+\\)$\/i,\u00a0 \u00a0 \/\/ i), ii)\r\n\/^\\d+\\)$\/ \u00a0 \u00a0 \u00a0 \u00a0 \/\/ 1), 2)\r\n];\r\nfor (const pattern of orderedPatterns) {\r\nif (pattern.test(bulletText)) {\r\nreturn 'ol';\r\n}\r\n}\r\nreturn 'ul';\r\n}\r\nconst root = document.createElement('div');\r\nconst stack = [{ level: 0, element: root }];\r\nArray.from(tempDiv.children).forEach(node =&gt; {\r\nif (node.className.includes('MsoListParagraph')) {\r\nconst style = node.getAttribute('style') || '';\r\nconst currentLevel = getListLevel(style);\r\nconst cleanedNode = cleanNode(node.cloneNode(true));\r\nlet bulletText = '';\r\nif (cleanedNode.firstElementChild) {\r\nbulletText = cleanedNode.firstElementChild.textContent.trim();\r\ncleanedNode.removeChild(cleanedNode.firstElementChild);\r\n}\r\nconst listType = getListType(bulletText);\r\n\/\/ Pop stack until we find a parent level lower than current\r\nwhile (stack.length &gt; 1 &amp;&amp; stack[stack.length - 1].level &gt;= currentLevel) {\r\nstack.pop();\r\n}\r\nconst parent = stack[stack.length - 1].element;\r\nlet listContainer = null;\r\n\/\/ Check if parent already has a list of the same type\r\nif (parent.lastElementChild &amp;&amp;\r\nparent.lastElementChild.tagName === listType.toUpperCase() &amp;&amp;\r\n\/\/ Only reuse list if we're at same level as last item\r\n(stack[stack.length - 1].level &lt; currentLevel ||\r\nstack[stack.length - 1].level === currentLevel)) {\r\nlistContainer = parent.lastElementChild;\r\n}\r\n\/\/ Check for adjacent list of different type\r\nelse if (parent.lastElementChild &amp;&amp;\r\n(parent.lastElementChild.tagName === 'OL' ||\r\nparent.lastElementChild.tagName === 'UL') &amp;&amp;\r\nparent.lastElementChild.tagName !== listType.toUpperCase()) {\r\nlistContainer = document.createElement(listType);\r\nparent.appendChild(listContainer);\r\n}\r\nelse {\r\nlistContainer = document.createElement(listType);\r\nparent.appendChild(listContainer);\r\n}\r\nconst li = document.createElement('li');\r\nli.innerHTML = cleanedNode.innerHTML;\r\nlistContainer.appendChild(li);\r\nstack.push({ level: currentLevel, element: li });\r\n} else {\r\n\/\/ Reset stack to root level for non-list items\r\nwhile (stack.length &gt; 1) stack.pop();\r\nconst cleanedNode = cleanNode(node.cloneNode(true));\r\nif (cleanedNode.textContent.trim() !== '') {\r\nroot.appendChild(cleanedNode);\r\n}\r\n}\r\n});\r\nreturn root.innerHTML;\r\n}<\/pre>\n<p><strong>Save all<\/strong> the changes to reflect it correctly.<\/p>\n<p><em><span style=\"text-decoration: underline;\"><strong>Note:<\/strong><\/span><\/em>\u00a0Please find the attached <a href=\"https:\/\/drive.google.com\/file\/d\/1B4NIDPMldatwW1eLWCv1p2qid9RaByR9\/view?usp=sharing\">toolsplugin.js<\/a> and\u00a0 <a href=\"https:\/\/drive.google.com\/file\/d\/1UXp_NTzxYCkp5jU_sL7mVHnSqoxa6Tv5\/view?usp=sharing\">edit-tools-plugin-workaround-for-paste.js<\/a>\u00a0 js files for reference, however copy paste from the specified path in your AEM instance.<\/p>\n<p><strong>Step 3: Configure \u201cwordhtml\u201d as default paste mode<\/strong><\/p>\n<div id=\"attachment_72740\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72740\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72740\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/defaultPasteMode-1024x538.png\" alt=\"defaultPasteMode\" width=\"1105\" height=\"581\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/defaultPasteMode-1024x538.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/defaultPasteMode-300x158.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/defaultPasteMode-768x404.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/defaultPasteMode-624x328.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/defaultPasteMode.png 1141w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72740\" class=\"wp-caption-text\">defaultPasteMode<\/p><\/div>\n<p><strong>Step 4: Optionally, <\/strong>we can enable plugin in toolbar itself, by configuring<strong>\u00a0\u201cpaste as wordhtml\u201d<\/strong> plugin explicitly as well if required.<\/p>\n<p>Navigate to inline node of text component and enable paste plugin by adding <strong>edit#paste-wordhtml<\/strong><\/p>\n<div id=\"attachment_72741\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72741\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72741\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/EnablePastePlugin-1024x360.png\" alt=\"PastePlugin\" width=\"1105\" height=\"389\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/EnablePastePlugin-1024x360.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/EnablePastePlugin-300x106.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/EnablePastePlugin-768x270.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/EnablePastePlugin-624x220.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/EnablePastePlugin.png 1296w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72741\" class=\"wp-caption-text\">PastePlugin<\/p><\/div>\n<p>To do auto transform list tags to ol\/li add below code to the same file in <strong>showPasteDialog<\/strong> function as shown below<\/p>\n<pre>const iframe = document.querySelector('iframe[name^=\"rte-paste-html\"]');\r\nif (iframe &amp;&amp; iframe.contentWindow) {\r\n   const iframeDocument = iframe.contentWindow.document;\r\n   \/\/ Listen for the 'paste' event in the iframe's document\r\n   iframeDocument.addEventListener('paste', function() {\r\n   setTimeout(() =&gt; {\r\n      const bodyContent = iframeDocument.querySelector('body');\r\n      iframeDocument.body.innerHTML = convertWordListsToUL(bodyContent.innerHTML);\r\n      }, 100);\r\n   });\r\n}<\/pre>\n<div id=\"attachment_72742\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72742\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72742\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/showPasteDialog.png\" alt=\"pasteDialog\" width=\"1105\" height=\"345\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/showPasteDialog.png 736w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/showPasteDialog-300x94.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/showPasteDialog-624x195.png 624w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72742\" class=\"wp-caption-text\">pasteDialog<\/p><\/div>\n<h3>Results<\/h3>\n<p>Congratulations! It&#8217;s time to verify our effort by copy-pasting some text from MS Word App to RTE.<\/p>\n<p>Here we can clearly see from the image attached below, it is converted to &lt;ol&gt; \/ &lt;li&gt; with the required bold, italic, under, anchor tags if applicable.<\/p>\n<div id=\"attachment_72739\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72739\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72739\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/WordListToUL-1024x588.png\" alt=\"WordListToUL\" width=\"1105\" height=\"634\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/WordListToUL-1024x588.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/WordListToUL-300x172.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/WordListToUL-768x441.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/WordListToUL-624x358.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/WordListToUL.png 1289w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72739\" class=\"wp-caption-text\">WordListToUL<\/p><\/div>\n<h3>Conclusion<\/h3>\n<p>It is a quick fix and provides flexibility to authors. However, if we opt for this approach don&#8217;t forget to keep an eye on the release notes while upgrading service pack or auto upgrade in case of AEM cloud and remove this file once fixed by Adobe.<\/p>\n<p>Hope you will be able to sail through such issues after going through this blog, feel free to customize and use if needed. Thanks!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Our client raised an issue stating the list conversion is not happening properly while copy-pasting the text from MS Word desktop App to RTE. When I explored, I found that the RTE component in AEM has mainly two paste modes (wordhtml and plaintext). Plain text mode scraps all the mark-up as the mode name suggests. [&hellip;]<\/p>\n","protected":false},"author":1823,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":49},"categories":[5868],"tags":[5314,4847,5008,7005,8198,7000],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/72399"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1823"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=72399"}],"version-history":[{"count":26,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/72399\/revisions"}],"predecessor-version":[{"id":76703,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/72399\/revisions\/76703"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=72399"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=72399"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=72399"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}