- Home
- Forum
- JavaScript - EJ 2
- Weekly Release 32.2.7 broke copy/paste
Weekly Release 32.2.7 broke copy/paste
Hi,
I've been noticing for a little while, but have only today drilled down into bug hunting a problem with copy/paste in the Rich Text Editor.
It works fine as a single event, but if doing more than one copy/paste in a row ... one after the other ... then the clipboard gets stuck with the contents of the first copy. No matter what is selected and copied, it doesn't change and so each paste, is the same as the first.
Changing back from version ≥32.2.7 to version 32.2.5 fixes the problem. The problem remains with the latest vs: 33.2.8.
Hi Grant,
- Used the same steps to reproduce the issue
- Copy and pasting the contents continuously more than one.
- Tested in both the V32.2.7 and V32.2.5
- Performed the tesing all browsers including Chrome, edge, and firefox.
- Issue reproducing video illustration.
- Exact issue replication steps.
- Entire code snippet of RichTextEditor or share a simple issue reproducing sample.
Hi Vinitha!
OK, I've dug a little deeper and discovered what is 'breaking' regarding my existing code and it turns out to be what you've handled in depth in your sample... the pasteCleanupSettings. My code is ~2yrs old for this bit and no doubt it should probably be more complete?? It did 'break' though, with the switch to v32.2.7 though from v32.2.5, as you'll see in the attached video.
I include my JS code below for ease of access should you want to look more closely. Could you suggest what I replace the old code with?
Best wishes,
Grant
var RichTextEditor = ej.richtexteditor.RichTextEditor;
var Dialog = ej.popups.Dialog;
var range;
var findAndReplaceDialog;
var saveSelection = new ej.richtexteditor.NodeSelection();
var selection = new ej.richtexteditor.NodeSelection();
var currentIndex = -1;
var matches = [];
var customHTMLModel = { // formatter is used to configure the custom key
keyConfig: {
'find-replace': 'ctrl+f',
'inle-toolbar': 'ctrl+shift'
}
};
var dialogClose = true;
var defaultRTE = new RichTextEditor({
/* Type-Return to remove <time> tags */
actionComplete: function (args) {
var range = document.getSelection().getRangeAt(0);
var startContainer = range.startContainer;
if (args.requestType === 'EnterAction' && startContainer.tagName === 'TIME')
{startContainer.remove();}
},
/* Type-Return to remove <time> tags */
formatter: new ej.richtexteditor.HTMLFormatter(customHTMLModel),
toolbarSettings: {
items: [
'Formats',
'FontSize',
'Blockquote',
'CodeBlock',
'Indent',
'Outdent',
'FormatPainter',
'ClearFormat',
'-',
'FontColor',
'BackgroundColor',
'HorizontalLine',
//e-search-3 icons don't work in Material3 Fluent2, so change bellow to:
{
tooltipText: 'Find and Replace',
template: '<button class="e-tbar-btn e-control e-btn e-lib e-icon-btn" type="button" id="defaultRTE_toolbar_Find_And_Replace" tabindex="-1" style="width: auto;"><span class="e-search e-icons"></span></button>',
command: 'Custom',
subCommand: 'FindAndReplace',
click: function () {
openFindAndReplaceDialog();
}
},
'OrderedList', 'UnorderedList',
'CreateLink',
]
},
slashMenuSettings: {
items: [{
iconCss: 'e-day e-icons',
text: 'DateTime Stamp',
description: 'Insert the Current Date Time',
type: 'Custom',
command: 'DateTime',
},
'OrderedList', 'UnorderedList', 'Image','Video', 'Audio', 'Table', 'Emojipicker',
],
enable: true
},
codeBlockSettings: {
languages: [
{label: "HTML", language: "html"},
{label: "JavaScript", language: "javascript"},
{label: "CSS", language: "css"},
{label: "Plain Text", language: "plaintext"}
],
defaultLanguage: "plaintext"
},
inlineMode: {
enable: true
},
slashMenuItemSelect: function (e) {
if (e.itemData.command === 'DateTime') {
insertDateTimeStamp();
}
},
created: function () {
renderFindAndReplaceDialog();
},
destroyed: function () {
destroyFindAndReplaceDialog();
},
actionBegin: function(e){
if (e.requestType === "find-replace") {
openFindAndReplaceDialog();
}
if (e.requestType === 'inle-toolbar') {
defaultRTE.showInlineToolbar();
}
},
quickToolbarSettings: {
image: [
'Replace',
'Align',
'Caption',
'Remove',
'InsertLink',
'OpenImageLink',
'-',
'EditImageLink',
'RemoveImageLink',
'Display',
'AltText',
'Dimension' ]
},
pasteCleanupSettings: {
prompt: true,
plainText: true,
keepFormat: false,
deniedAttrs: ['class', 'title', 'id'],
},
insertImageSettings: {
width: 'auto',
height: 'auto',
saveFormat: 'Base64'
},
keyConfig:{
'copy': 'Cmd+c',
'cut': 'Cmd+x',
'paste': 'Cmd+v'
},
format: {
default: 'Paragraph',
width: '2px',
types: [
{ cssClass: 'e-paragraph', text: 'P', value: 'P' },
{ cssClass: 'e-h1', text: 'H 1', value: 'H1' },
{ cssClass: 'e-h2', text: 'H 2', value: 'H2' },
{ cssClass: 'e-h3', text: 'H 3', value: 'H3' },
{ cssClass: 'e-h4', text: 'H 4', value: 'H4' },
{ cssClass: 'e-h5', text: 'H 5', value: 'H5' },
{ cssClass: 'e-h6', text: 'H 6', value: 'H6' },
{ cssClass: 'e-quotation', text: 'BlockQuote', value: 'Blockquote' },
],
},
fontSize: {
default: '0.9rem',
width: 'auto',
items: [
{ text: '-2', value: '0.8rem' },
{ text: '-1', value: '0.85rem'},
{ text: 'N', value: '0.9rem' },
{ text: '+1', value: '0.95rem'},
{ text: '+2', value: '1rem'},
{ text: '+3', value: '1.05rem'},
{ text: '+4', value: '1.1rem' },
{ text: '+5', value: '1.15rem' },
{ text: '+6', value: '1.2rem'} ]
},
fontColor: {
showRecentColors: true
},
backgroundColor: {
showRecentColors: true
},
backgroundColor: {
modeSwitcher : true
},
fontColor: {
modeSwitcher : true
},
keyConfig:{
'copy': 'Cmd+c',
'cut': 'Cmd+x',
'paste': 'Cmd+v'
},
// NB!!! This triggers the update function. It used to be 1 (1sec) changed due to Cmd-X failure (SyncF forum question)
saveInterval: 500,
change: change,
autoSaveOnIdle: true
/* NB the comma below when toolbarClick is enabled */
,
toolbarClick: toolbarClick
});
defaultRTE.appendTo('#defaultRTE');
function change(args){
const textPlain = defaultRTE.getText();
const textHTML = defaultRTE.getHtml();
const jsonARR = { textPlain, textHTML };
FileMaker.PerformScriptWithOption ( '🟣RTE-DATA', JSON.stringify(jsonARR) , '5' );
}
<!-- Alex' script/Function for a Link to another Note -->
function copyLink(url,text) {
var a = document.createElement('a');
a.setAttribute("rel='nofollow' href",url);
a.appendChild(document.createTextNode(text));
document.body.appendChild(a);
var range = document.createRange();
range.selectNode(a);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand('copy');
document.body.removeChild(a);
}
function toolbarClick(args) {
if (args.item.subCommand == 'OpenLink') {
FileMaker.PerformScriptWithOption ( '🟠Get-Source-JSFunction', "JSFn_fromCode", '5' )
}
}
<!-- Alex' script/Function for a Link to another Note. (Nov25: only working via Contextual Menu) -->
function copyLink(url,text) {
var a = document.createElement('a');
a.setAttribute("rel='nofollow' href",url);
a.appendChild(document.createTextNode(text));
document.body.appendChild(a);
var range = document.createRange();
range.selectNode(a);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand('copy');
document.body.removeChild(a);
}
function insertDateTimeStamp() {
defaultRTE.inputElement.focus();
range = selection.getRange(document);
saveSelection = selection.save(range, document);
if (defaultRTE.formatter.getUndoRedoStack().length === 0) {
defaultRTE.formatter.saveData();
}
saveSelection.restore();
var dateTime = new Date();
var formattedDate = dateTime.toLocaleDateString('en-GB'); // Format date as DD-MM-YYYY
var timeElement = '✏️<time>(' + formattedDate + ')</time>';
// string altered to show just date text in the html... ` ✏️<time datetime="${dateTime.toISOString()}">(${formattedDate}) </time>`;
defaultRTE.executeCommand('insertHTML', timeElement);
defaultRTE.formatter.saveData();
defaultRTE.formatter.enableUndo(defaultRTE);
}
function renderFindAndReplaceDialog() {
var dialogRootElement = document.createElement('div');
dialogRootElement.id = 'findAndReplaceDialog';
defaultRTE.rootContainer.appendChild(dialogRootElement);
var dlgContent =
'<div class="wrapper-container">' +
'<div class="search-replace-container">' +
'<div class="find-container">' +
'<div class="input-container">' +
'<label style="width: 80px;" for="findText">Find:</label>' +
'<input class="e-input" id="findText" type="text" placeholder="Enter text" style="margin-bottom: 10px;" tabindex="1"/>' +
'</div>' +
'</div>' +
'<div class="replace-container">' +
'<div class="input-container">' +
'<label style="width: 70px;" for="replaceText">Replace:</label>' +
'<input class="e-input" id="replaceText" type="text" placeholder="Enter text" style="margin-bottom: 10px;" tabindex="2" />' +
'</div>' +
'</div>' +
'</div>' +
'<br/>' +
'</div>';
findAndReplaceDialog = new Dialog({
header: 'Find and Replace',
showCloseIcon: true,
content: dlgContent,
target: defaultRTE.element,
visible: false,
width: 450,
height: 250,
buttons: [
{
buttonModel: {
content: 'Close (With highlight)',
isPrimary: true,
},
click: replaceHandler
},
{
buttonModel: {
content: 'Replace All',
isPrimary: true
},
click: replaceAllHandler
}
],
allowDragging: true,
position: {
Y: 'center'
},
close: function () {
document.querySelector('#findText').value = '';
document.querySelector('#replaceText').value = '';
if (dialogClose) {
var content = defaultRTE.inputElement.innerHTML;
content = content.replace(/<span[^>]*class="highlight"[^>]*>(.*?)<\/span>/g, '$1');
defaultRTE.inputElement.innerHTML = content;
}
dialogClose = true;
}
});
findAndReplaceDialog.appendTo('#findAndReplaceDialog');
bindInputEvents();
}
function destroyFindAndReplaceDialog() {
unBindInputEvents();
findAndReplaceDialog.destroy();
}
function openFindAndReplaceDialog() {
findAndReplaceDialog.refreshPosition();
findAndReplaceDialog.show();
defaultRTE.hideInlineToolbar();
}
function bindInputEvents() {
var findTextBox = document.querySelector('#findText');
var replaceTextBox = document.querySelector('#replaceText');
findTextBox.addEventListener('input', findHandler);
}
function unBindInputEvents() {
var findTextBox = document.querySelector('#findText');
findTextBox.removeEventListener('input', findHandler);
}
function findHandler(e) {
var searchText = document.querySelector('#findText').value;
var content = defaultRTE.inputElement.innerHTML;
if (searchText === '') {
content = content.replace(/<span[^>]*class="highlight"[^>]*>(.*?)<\/span>/g, '$1');
defaultRTE.inputElement.innerHTML = content;
return; // Exit the function early
}
if (searchText !== '') {
content = content.replace(/<span[^>]*class="highlight"[^>]*>(.*?)<\/span>/g, '$1');
content = replaceText(content, searchText);
defaultRTE.inputElement.innerHTML = content;
}
}
function replaceText(content, findText) {
const container = content;
if (!container) return '';
const tempContainer = document.createElement('div');
tempContainer.innerHTML = container;
function processNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.includes(findText)) {
// Create a temporary container
const temp = document.createElement('div');
// Replace text and wrap with span HTML
const newContent = node.textContent.replace(
new RegExp(findText, 'g'),
`<span class="highlight">${findText}</span>`
);
// Set the HTML content
temp.innerHTML = newContent;
// Replace the text node with the new nodes
const parent = node.parentNode;
while (temp.firstChild) {
parent.insertBefore(temp.firstChild, node);
}
parent.removeChild(node);
}
} else {
Array.from(node.childNodes).forEach(child => processNode(child));
}
}
processNode(tempContainer);
return tempContainer.innerHTML;
}
function moveToPreviousHighlight() {
}
function moveToNextHighlight() {
}
function scrollToHighlight() {
}
function updateMatchCount() {
}
function replaceAllHandler() {
var content = defaultRTE.inputElement.innerHTML;
var searchText = document.querySelector('#findText').value;
var replaceText = document.querySelector('#replaceText').value;
if (searchText !== '' && replaceText !== '') {
content = content.replace(/<span[^>]*class="highlight"[^>]*>(.*?)<\/span>/g, '$1');
content = replaceAllText(content, searchText, replaceText)
defaultRTE.inputElement.innerHTML = content;
}
findAndReplaceDialog.element.focus();
}
function replaceAllText(value, findText, replaceText) {
const container = value;
if (!container) return '';
const tempContainer = document.createElement('div');
tempContainer.innerHTML = value;
function processNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.includes(findText)) {
node.textContent = node.textContent.replace(
new RegExp(findText, 'g'),
replaceText
);
}
} else {
node.childNodes.forEach(child => processNode(child));
}
}
processNode(tempContainer);
return tempContainer.innerHTML;
}
function replaceHandler() {
dialogClose = false;
findAndReplaceDialog.hide();
}
Attachment: GSymonvideoGrabSyncfusionCopyPastebug.mp4_916d3e36.zip
Please review the attached sample and video for reference. Could you kindly confirm whether the reported issue is reproducible in the shared sample, or if it is specific to the FileMaker environment?
Attachment: screencapture_(37)_817b757d.zip
Hi Vinitha,
It's happening in Safari as well. NB that FileMaker for macOS uses WebKit.
macOS Tahoe 26.5
Safari - Version 26.5 (21624.2.5.11.4)
It's also happening in Safari Technology Preview - Release 244 (WebKit 22625.1.17.19.1)
ScreenGrab video, show both:
Attachment: GSymonvideoGrabSyncfusionCopyPastebugB.mp4_7676b656.zip
Hi Vinitha,
Any updates with this?
Best,
Grant
Attachment: Screen_RecordingIssue.mov_646fe755.zip
Hi Vinitha,
I was surprised that in your video, you are not using the keyboard for copy/paste and instead you are using the mouse to click ⌘-C and ⌘-V one key at a time in the Keyboard Viewer.
So I tried the same and *bingo* the problem is gone!
Conclusion: The RTE is using it’s own clipboard or is it that the 2nd ⌘-C is not being registered?
I have noticed for a long time, that there are occasional issues with the way the standard macOS modifier keys are applied and perhaps this is the root cause?
To reproduce the problem:
• Copy and paste using the Mac’s keyboard, not the Keyboard Viewer.
• Open the Clipboard History to see what is on the clipboard (⌘-Space + ⌘-4). You will see that the second ⌘-C is on the Clipboard, as it should be, but when you ⌘-V, it is not applied. This shows us that the RTE has it’s own clipboard and that it is not responding to the Mac’s ⌘-V as it should.
Perhaps a simple binary toggle for choosing Windows or Mac keyboard control, would be a good thing to include? (I have applied what I think this is, as you can see in the code I posted in my 2nd post … so maybe that’s ok and it’s just a bug that is preventing it from working correctly?)
Hi Grant,
We have considered the reported issue "Clipboard gets stuck on first copied item when performing consecutive copy–paste operations in RichTextEditor" as a bug from our end, and the fix for the issue will be included in our upcoming weekly patch release scheduled for the end of June 2026.
Hi Vinitha,
thanks for the update. Great news that it's getting fixed so quickly! 😊
I'm wondering... since you mention that it's a Safari only problem, do you think that this could be considered a 'bug' in Safari / Webkit? I say this, because I've found copy/paste issues in Filemaker for some time and a few days ago, I discovered that I cannot copy from a PDF that's 'embedded' in a webviewer. The copy command appears to work and there is an Filemaker icon with date and time numbers placed on the clipboard, but the paste command does nothing. So it's effectively empty. *However* a selection in the PDF can be copied via the macOS Contextual Menu... by simply selecting the Copy command from it.
What do you think??
Best,
Grant
Hi Grant,
Thank you for your kind words, we’re glad to hear your positive feedback!
Regarding your question, while the issue appears to be specific to Safari/WebKit, it is not something we can definitively classify as a confirmed bug within Safari itself. However, Safari does have certain known limitations and behavioral differences compared to other browsers, particularly when it comes to clipboard handling, embedded content (such as PDFs), and HTML processing.
The behavior you’re describing, especially with copy/paste from embedded PDFs within web viewers, is consistent with these known constraints in Safari’s rendering and security model. These restrictions are often more pronounced in environments like FileMaker web viewers, which rely on WebKit internally.
Hi Grant,
Thank you for your patience.
We revisited this reported behavior and attempted to reproduce it using the same configuration and scenarios that were previously associated with the issue. However, based on our latest validation, we were unable to reproduce the problem where the clipboard remains stuck on the first copied item during consecutive copy–paste operations in the Rich Text Editor.
At this point, the issue does not appear to be reproducible in our current testing environment. It is possible that the behavior was influenced by a specific environmental factor, browser state, or configuration that is no longer present.
Could you please verify whether you are still encountering this issue in your environment? If the problem persists, we would appreciate it if you could provide any additional details, such as updated reproduction steps, a sample project, or a screen recording demonstrating the behavior. This will help us investigate further and identify any environment-specific factors that may be contributing to the issue.
We appreciate your cooperation and look forward to your update.
You're absolutely right... something has changed the behaviour, which suggests it's a Safari/Webkit problem.
However, there is one aspect which is still broken and perhaps this is something that may be a wider issue regarding the RTE.
The problem still occurs, if you use the keyboard, instead of the mouse/pointer, to select an Option in the 'FormattingOption' popup menu, i.e. Down-Arrow to select the Option and Return-Key to apply the selected Option.
So, set the menu to Prompt and NB the problem only happens with the Keep and Retain options. Clean works correctly and the clipboard is applied as it should be.
If on the other hand, you use the mouse/pointer to select the Options, then they ALL work as they should.
The above happens on your demo-StackBlitz site.
Hi Grantt,
Hi Kirupa,
thanks for the update. I'm looking forward to the fix. 😊
Best,
Grant
Hi Grant,
Thank you for your patience.
We have included the fix for the issue "Clipboard gets stuck on first copied item when performing consecutive copy–paste operations in RichTextEditor" with our package version 34.2.3. Please upgrade your package to this version to resolve the reported issue at your end.
Sample: Link.
Release notes: Link.
Root cause analysis:
During consecutive copy-paste operations, the Paste Cleanup dialog was destroyed after use, but the associated event handlers remained attached to the dialog's appended DOM elements. This resulted in stale event callbacks being triggered, causing clipboard operations to reference outdated state and repeatedly paste the first copied content.
Please let us know if you need any further clarification or assistance.
Regards,
Kirupa.
Thank you Kirupa and Vinitha!
All now working as expected. 😊
Kirupa.
- 17 Replies
- 3 Participants
-
GS Grant Symon
- May 27, 2026 11:00 AM UTC
- Aug 21, 2026 07:02 AM UTC