Fix double double quotes when copying chat message text in Firefox

34ff8e239f580b0aa51307f091e529c13a71335d

maver <christian.pahren@googlemail.com>

2 files changed, +54 -0Showing whitespace changes
public/script.js+2 -0
@@ -247,6 +247,7 @@ import { AbortReason } from './scripts/util/AbortReason.js';
247247import { initSystemPrompts } from './scripts/sysprompt.js';
248248import { registerExtensionSlashCommands as initExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
249249import { ToolManager } from './scripts/tool-calling.js';
250+import { applyBrowserFixes } from './scripts/browser-fixes.js';
250251
251252//exporting functions and vars for mods
252253export {
@@ -272,6 +273,7 @@ await new Promise((resolve) => {
272273});
273274
274275showLoader();
276+applyBrowserFixes();
275277
276278// Configure toast library:
277279toastr.options.escapeHtml = true; // Prevent raw HTML inserts
public/scripts/browser-fixes.js+52 -0
@@ -0,0 +1,52 @@
1+const isFirefox = () => /firefox/i.test(navigator.userAgent);
2+
3+function sanitizeInlineQuotationOnCopy() {
4+ // STRG+C, STRG+V on firefox leads to duplicate double quotes when inline quotation elements are copied.
5+ // To work around this, take the selection and transform <q> to <span> before calling toString().
6+ document.addEventListener('copy', function (event) {
7+ const selection = window.getSelection();
8+ if (selection.anchorNode.nodeName !== '#text' || selection.focusNode.nodeName !== '#text' || !selection.anchorNode?.parentElement.closest('.mes_text')) {
9+ // Complex selection, skip.
10+ return;
11+ }
12+
13+ const range = selection.getRangeAt(0).cloneContents();
14+ const tempDOM = document.createDocumentFragment();
15+
16+ function processNode(node) {
17+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'q') {
18+ // Transform <q> to <span>, preserve children
19+ const span = document.createElement('span');
20+
21+ [...node.childNodes].forEach(child => {
22+ const processedChild = processNode(child);
23+ span.appendChild(processedChild);
24+ });
25+
26+ return span;
27+ } else {
28+ // Nested structures containing <q> elements are unlikely
29+ return node.cloneNode(true);
30+ }
31+ }
32+
33+ [...range.childNodes].forEach(child => {
34+ const processedChild = processNode(child);
35+ tempDOM.appendChild(processedChild);
36+ });
37+
38+ const newRange = document.createRange();
39+ newRange.selectNodeContents(tempDOM);
40+
41+ event.preventDefault();
42+ event.clipboardData.setData('text/plain', newRange.toString());
43+ });
44+}
45+
46+function applyBrowserFixes() {
47+ if (isFirefox()) {
48+ sanitizeInlineQuotationOnCopy();
49+ }
50+}
51+
52+export { isFirefox, applyBrowserFixes };