Skip to content

Commit

Permalink
Windows platforms, erratic pasting of text into Markdown field (#15794)
Browse files Browse the repository at this point in the history
* Windows platforms, erratic pasting of text into Markdown field

Due to different line ending in windows display and data got corrupted.
Fixed by correcting paste handling of text in
packages/cells/src/widgets.ts

fixes: #14752

* Code formatting corrected.

* Changed string delimiter.

* Added carriage return replacement for pasteing on osx.

* adjusted formating of code

* Add a test for replacing line endings on paste

Co-authored-by: Kilian Singer <kilian.singer@quantumtechnology.info>

* Add mocks for `ClipboardEvent` and `DataTransfer`

---------

Co-authored-by: krassowski <5832902+krassowski@users.noreply.github.com>
  • Loading branch information
kiliansinger and krassowski committed Feb 16, 2024
1 parent b689a68 commit bb8a4ed
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 2 deletions.
4 changes: 3 additions & 1 deletion packages/cells/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1776,7 +1776,9 @@ export abstract class AttachmentsCell<
continue;
}
items[i].getAsString(text => {
this.editor!.replaceSelection?.(text);
this.editor!.replaceSelection?.(
text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
);
});
}
this._attachFiles(event.clipboardData.items);
Expand Down
29 changes: 28 additions & 1 deletion packages/cells/test/widget.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { createStandaloneCell, YCodeCell } from '@jupyter/ydoc';
import { createStandaloneCell, YCodeCell, YMarkdownCell } from '@jupyter/ydoc';
import { ISessionContext, SessionContext } from '@jupyterlab/apputils';
import { createSessionContext } from '@jupyterlab/apputils/lib/testutils';
import {
Expand Down Expand Up @@ -998,6 +998,33 @@ describe('cells/widget', () => {
});
});

describe('#getEditorOptions()', () => {
it('should normalise line endings on paste', () => {
const model = new MarkdownCellModel({
sharedModel: createStandaloneCell({
cell_type: 'markdown'
}) as YMarkdownCell
});
const widget = new MarkdownCell({
model,
rendermime,
contentFactory,
placeholder: false
});
widget.initializeState();
document.body.appendChild(widget.node);
// todo: replace with user-event
const dt = new DataTransfer();
dt.setData('text/plain', '\r\nTest\r\nString\r\n.\r\n');
const event = new ClipboardEvent('paste', { clipboardData: dt });
widget.editor!.host.querySelector('.cm-content')!.dispatchEvent(event);

expect(widget.model.sharedModel.getSource()).toEqual(
'\nTest\nString\n.\n'
);
});
});

describe('#rendered', () => {
it('should default to true', async () => {
const widget = new MarkdownCell({
Expand Down
69 changes: 69 additions & 0 deletions packages/testing/src/jest-shim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,75 @@ class ResizeObserverMock {

window.ResizeObserver = ResizeObserverMock;

// https://github.com/jsdom/jsdom/issues/2913
class DataTransferItemMock implements DataTransferItem {
constructor(
protected format: string,
protected value: string
) {
// no-op
}
get kind() {
return 'string';
}
get type() {
return this.format;
}
getAsString(callback: (v: string) => undefined): undefined {
callback(this.value);
}
getAsFile() {
return null as any;
}
webkitGetAsEntry() {
return null as any;
}
}

// https://github.com/jsdom/jsdom/issues/2913
class DataTransferMock implements DataTransfer {
dropEffect: DataTransfer['dropEffect'] = 'none';
effectAllowed: DataTransfer['dropEffect'] = 'none';
files: DataTransfer['files'];
get items(): DataTransfer['items'] {
return [
...Object.entries(this._data).map(
([k, v]) => new DataTransferItemMock(k, v)
)
] as unknown as DataTransferItemList;
}
readonly types: DataTransfer['types'] = [];
getData(format: string) {
return this._data[format];
}
setData(format: string, data: string) {
this._data[format] = data;
}
clearData() {
this._data = {};
}
setDragImage(imgElement: Element, xOffset: number, yOffset: number) {
// no-op
}
private _data: Record<string, string> = {};
}

window.DataTransfer = DataTransferMock;

// https://github.com/jsdom/jsdom/issues/1568
class ClipboardEventMock extends Event implements ClipboardEvent {
constructor(
type: 'copy' | 'cut' | 'paste',
options: { clipboardData: DataTransfer }
) {
super(type);
this.clipboardData = options.clipboardData;
}
clipboardData: DataTransfer;
}

window.ClipboardEvent = ClipboardEventMock;

(window as any).document.elementFromPoint = (left: number, top: number) =>
document.body;

Expand Down

0 comments on commit bb8a4ed

Please sign in to comment.