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

Handle consecutive spaces when copying and pasting #4394

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion packages/quill/src/core/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ function convertHTML(
return blot.html(index, length);
}
if (blot instanceof TextBlot) {
return escapeText(blot.value().slice(index, index + length));
const escapedText = escapeText(blot.value().slice(index, index + length));
return escapedText.replaceAll(' ', ' ');
}
if (blot instanceof ParentBlot) {
// TODO fix API
Expand Down
16 changes: 8 additions & 8 deletions packages/quill/src/modules/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,9 +624,12 @@ function matchTable(
return delta;
}

const NBSP = '\u00a0';
const SPACE_EXCLUDE_NBSP = `[^\\S${NBSP}]`;

function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
// @ts-expect-error
let text = node.data;
let text = node.data as string;
// Word represents empty line with <o:p>&nbsp;</o:p>
if (node.parentElement?.tagName === 'O:P') {
return delta.insert(text.trim());
Expand All @@ -639,29 +642,26 @@ function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
) {
return delta;
}
const replacer = (collapse: unknown, match: string) => {
const replaced = match.replace(/[^\u00a0]/g, ''); // \u00a0 is nbsp;
return replaced.length < 1 && collapse ? ' ' : replaced;
};
text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' ');
text = text.replace(/\s\s+/g, replacer.bind(replacer, true)); // collapse whitespace
text = text.replace(new RegExp(`${SPACE_EXCLUDE_NBSP}{2,}`, 'g'), ' '); // collapse whitespace
if (
(node.previousSibling == null &&
node.parentElement != null &&
isLine(node.parentElement, scroll)) ||
(node.previousSibling instanceof Element &&
isLine(node.previousSibling, scroll))
) {
text = text.replace(/^\s+/, replacer.bind(replacer, false));
text = text.replace(new RegExp(`^${SPACE_EXCLUDE_NBSP}+`), '');
}
if (
(node.nextSibling == null &&
node.parentElement != null &&
isLine(node.parentElement, scroll)) ||
(node.nextSibling instanceof Element && isLine(node.nextSibling, scroll))
) {
text = text.replace(/\s+$/, replacer.bind(replacer, false));
text = text.replace(new RegExp(`${SPACE_EXCLUDE_NBSP}+$`), '');
}
text = text.replaceAll(NBSP, ' ');
}
return delta.insert(text);
}
Expand Down
28 changes: 26 additions & 2 deletions packages/quill/test/unit/core/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import { ColorClass } from '../../../src/formats/color.js';
import Quill from '../../../src/core.js';
import { normalizeHTML } from '../__helpers__/utils.js';

const createEditor = (html: string) => {
const createEditor = (htmlOrContents: string | Delta) => {
const container = document.createElement('div');
container.innerHTML = normalizeHTML(html);
if (typeof htmlOrContents === 'string') {
container.innerHTML = normalizeHTML(htmlOrContents);
}
document.body.appendChild(container);
const quill = new Quill(container, {
registry: createRegistry([
Expand All @@ -54,6 +56,9 @@ const createEditor = (html: string) => {
SizeClass,
]),
});
if (typeof htmlOrContents !== 'string') {
quill.setContents(htmlOrContents);
}
return quill.editor;
};

Expand Down Expand Up @@ -1246,6 +1251,25 @@ describe('Editor', () => {
);
});

test('collapsible spaces', () => {
expect(
createEditor('<p><strong>123 </strong>123<em> 123</em></p>').getHTML(
0,
11,
),
).toEqual('<strong>123&nbsp;</strong>123<em>&nbsp;123</em>');

expect(createEditor(new Delta().insert('1 2\n')).getHTML(0, 5)).toEqual(
'1&nbsp;&nbsp;&nbsp;2',
);

expect(
createEditor(
new Delta().insert(' 123', { bold: true }).insert('\n'),
).getHTML(0, 5),
).toEqual('<strong>&nbsp;&nbsp;123</strong>');
});

test('mixed list', () => {
const editor = createEditor(
`
Expand Down
24 changes: 17 additions & 7 deletions packages/quill/test/unit/modules/clipboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ describe('Clipboard', () => {
expect(delta).toEqual(new Delta().insert('0\n1 2 3 4\n5 6 7 8'));
});

test('multiple whitespaces', () => {
const html = '<div>1 2 3</div>';
const delta = createClipboard().convert({ html });
expect(delta).toEqual(new Delta().insert('1 2 3'));
});

test('inline whitespace', () => {
const html = '<p>0 <strong>1</strong> 2</p>';
const delta = createClipboard().convert({ html });
Expand All @@ -256,19 +262,23 @@ describe('Clipboard', () => {
const html = '<span>0&nbsp;<strong>1</strong>&nbsp;2</span>';
const delta = createClipboard().convert({ html });
expect(delta).toEqual(
new Delta()
.insert('0\u00a0')
.insert('1', { bold: true })
.insert('\u00a02'),
new Delta().insert('0 ').insert('1', { bold: true }).insert(' 2'),
);
});

test('consecutive intentional whitespace', () => {
const html = '<strong>&nbsp;&nbsp;1&nbsp;&nbsp;</strong>';
const delta = createClipboard().convert({ html });
expect(delta).toEqual(
new Delta().insert('\u00a0\u00a01\u00a0\u00a0', { bold: true }),
);
expect(delta).toEqual(new Delta().insert(' 1 ', { bold: true }));
});

test('intentional whitespace at line start/end', () => {
expect(
createClipboard().convert({ html: '<p>0 &nbsp;</p><p>&nbsp; 2</p>' }),
).toEqual(new Delta().insert('0 \n 2'));
expect(
createClipboard().convert({ html: '<p>0&nbsp; </p><p> &nbsp;2</p>' }),
).toEqual(new Delta().insert('0 \n 2'));
});

test('newlines between inline elements', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quill/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"compilerOptions": {
"outDir": "./dist",
"allowSyntheticDefaultImports": true,
"target": "ES2020",
"target": "ES2021",
"sourceMap": true,
"resolveJsonModule": true,
"declaration": false,
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"target": "ES2020",
"target": "ES2021",
"sourceMap": true,
"declaration": true,
"module": "ES2020",
Expand Down
Loading