Skip to content

Commit

Permalink
[FIX] clipboard: paste without asking permission
Browse files Browse the repository at this point in the history
Since 825f017, copy and paste some cells in the spreadsheet
requires the user to allow reading the OS clipboard.
It's annoying, and if the user dismisses (or blocks) the popup,
copy-pasting doesn't work anymore (the permissions need to be reset).

closes #4922

Task: 4138195
Signed-off-by: Lucas Lefèvre (lul) <[email protected]>
  • Loading branch information
Rachico authored and LucasLefevre committed Sep 19, 2024
1 parent c0c8f9e commit 0e37522
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/actions/menu_items_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async function paste(env: SpreadsheetChildEnv, pasteOption?: ClipboardPasteOptio
case "ok":
const htmlDocument = new DOMParser().parseFromString(
osClipboard.content[ClipboardMIMEType.Html] ?? "<div></div>",
"text/xml"
"text/html"
);
const osClipboardSpreadsheetContent =
osClipboard.content[ClipboardMIMEType.OSpreadsheet] || "{}";
Expand Down
27 changes: 20 additions & 7 deletions src/components/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,10 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
this.env.model.dispatch("COPY");
}
const content = this.env.model.getters.getClipboardContent();
await this.env.clipboard.write(content);
const clipboardData = ev.clipboardData;
for (const type in content) {
clipboardData?.setData(type, content[type]);
}
ev.preventDefault();
}

Expand All @@ -619,15 +622,18 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {

ev.preventDefault();

const clipboard = await this.env.clipboard.read();
if (clipboard.status !== "ok") {
const clipboardData = ev.clipboardData;
if (!clipboardData) {
return;
}
const clipboardDataTextContent = clipboardData?.getData(ClipboardMIMEType.PlainText);
const clipboardDataHtmlContent = clipboardData?.getData(ClipboardMIMEType.Html);
const htmlDocument = new DOMParser().parseFromString(
clipboard.content[ClipboardMIMEType.Html] ?? "<div></div>",
"text/xml"
clipboardDataHtmlContent ?? "<div></div>",
"text/html"
);
const osClipboardSpreadsheetContent = clipboard.content[ClipboardMIMEType.OSpreadsheet] || "{}";
const osClipboardSpreadsheetContent =
clipboardData.getData(ClipboardMIMEType.OSpreadsheet) || "{}";

const target = this.env.model.getters.getSelectedZones();
const isCutOperation = this.env.model.getters.isCutOperation();
Expand All @@ -639,7 +645,14 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
if (this.env.model.getters.getClipboardId() === clipboardId) {
interactivePaste(this.env, target);
} else {
interactivePasteFromOS(this.env, target, clipboard.content);
const clipboardContent = {
[ClipboardMIMEType.PlainText]: clipboardDataTextContent,
[ClipboardMIMEType.Html]: clipboardDataHtmlContent,
};
if (osClipboardSpreadsheetContent !== "{}") {
clipboardContent[ClipboardMIMEType.OSpreadsheet] = osClipboardSpreadsheetContent;
}
interactivePasteFromOS(this.env, target, clipboardContent);
}
if (isCutOperation) {
await this.env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
Expand Down
46 changes: 26 additions & 20 deletions src/helpers/clipboard/navigator_clipboard_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@ class WebClipboardWrapper implements ClipboardInterface {
await this.clipboard?.write(this.getClipboardItems(clipboardContent));
} catch (e) {
/**
* Some browsers (e.g firefox, safari) do not support writing
* custom mimetypes in the clipboard. Therefore, we try to catch
* any errors and fallback on writing only standard mimetypes to
* prevent the whole copy action from crashing.
* Some browsers do not support writing custom mimetypes in the clipboard.
* Therefore, we try to catch any errors and fallback on writing only standard
* mimetypes to prevent the whole copy action from crashing.
*/
await this.clipboard?.write([
new ClipboardItem({
[ClipboardMIMEType.PlainText]: this.getBlob(
clipboardContent,
ClipboardMIMEType.PlainText
),
[ClipboardMIMEType.Html]: this.getBlob(clipboardContent, ClipboardMIMEType.Html),
}),
]);
try {
await this.clipboard?.write([
new ClipboardItem({
[ClipboardMIMEType.PlainText]: this.getBlob(
clipboardContent,
ClipboardMIMEType.PlainText
),
[ClipboardMIMEType.Html]: this.getBlob(clipboardContent, ClipboardMIMEType.Html),
}),
]);
} catch (e) {}
}
} else {
await this.writeText(clipboardContent[ClipboardMIMEType.PlainText] ?? "");
Expand Down Expand Up @@ -83,13 +84,18 @@ class WebClipboardWrapper implements ClipboardInterface {
}

private getClipboardItems(content: ClipboardContent): ClipboardItems {
return [
new ClipboardItem({
[ClipboardMIMEType.PlainText]: this.getBlob(content, ClipboardMIMEType.PlainText),
[ClipboardMIMEType.Html]: this.getBlob(content, ClipboardMIMEType.Html),
[ClipboardMIMEType.OSpreadsheet]: this.getBlob(content, ClipboardMIMEType.OSpreadsheet),
}),
];
const clipboardItemData = {
[ClipboardMIMEType.PlainText]: this.getBlob(content, ClipboardMIMEType.PlainText),
[ClipboardMIMEType.Html]: this.getBlob(content, ClipboardMIMEType.Html),
};
const spreadsheetData = content[ClipboardMIMEType.OSpreadsheet];
if (spreadsheetData) {
clipboardItemData[ClipboardMIMEType.OSpreadsheet] = this.getBlob(
content,
ClipboardMIMEType.OSpreadsheet
);
}
return [new ClipboardItem(clipboardItemData)];
}

private getBlob(clipboardContent: ClipboardContent, type: ClipboardMIMEType): Blob {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui_stateful/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ export class ClipboardPlugin extends UIPlugin {
);
}

private getHTMLContent(): string | undefined {
private getHTMLContent(): string {
if (!this.copiedData?.cells) {
return `<div data-clipboard-id="${this.clipboardId}">\t</div>`;
}
Expand Down
24 changes: 9 additions & 15 deletions tests/grid/grid_component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ describe("Copy paste keyboard shortcut", () => {
});

test("Can paste from OS", async () => {
await parent.env.clipboard.writeText("Excalibur");
clipboardData.setData(ClipboardMIMEType.PlainText, "Excalibur");
selectCell(model, "A1");
document.body.dispatchEvent(getClipboardEvent("paste", clipboardData));
await nextTick();
Expand All @@ -1414,8 +1414,7 @@ describe("Copy paste keyboard shortcut", () => {
setCellContent(model, "A1", "things");
selectCell(model, "A1");
document.body.dispatchEvent(getClipboardEvent("copy", clipboardData));
const clipboard = await parent.env.clipboard.read!();
const clipboardContent = "content" in clipboard ? clipboard.content : {};
const clipboardContent = clipboardData.content;
expect(clipboardContent).toMatchObject({
"text/plain": "things",
"text/html": `<div data-clipboard-id="${model.getters.getClipboardId()}">things</div>`,
Expand All @@ -1430,8 +1429,7 @@ describe("Copy paste keyboard shortcut", () => {
setCellContent(model, "A1", "things");
selectCell(model, "A1");
document.body.dispatchEvent(getClipboardEvent("cut", clipboardData));
const clipboard = await parent.env.clipboard.read!();
const clipboardContent = "content" in clipboard ? clipboard.content : {};
const clipboardContent = clipboardData.content;
expect(clipboardContent).toMatchObject({
"text/plain": "things",
"text/html": `<div data-clipboard-id="${model.getters.getClipboardId()}">things</div>`,
Expand Down Expand Up @@ -1463,8 +1461,7 @@ describe("Copy paste keyboard shortcut", () => {
setCellContent(model, "A1", "1");
setCellFormat(model, "A1", "m/d/yyyy");
document.body.dispatchEvent(getClipboardEvent("cut", clipboardData));
const clipboard = await parent.env.clipboard.read!();
const clipboardContent = "content" in clipboard ? clipboard.content : {};
const clipboardContent = clipboardData.content;
expect(clipboardContent[ClipboardMIMEType.PlainText]).toEqual(getCellContent(model, "A1"));
model.dispatch("SET_FORMULA_VISIBILITY", { show: false });
selectCell(model, "A2");
Expand All @@ -1477,8 +1474,7 @@ describe("Copy paste keyboard shortcut", () => {
setCellContent(model, "A1", "1");
setCellFormat(model, "A1", "m/d/yyyy");
document.body.dispatchEvent(getClipboardEvent("cut", clipboardData));
let clipboard = await parent.env.clipboard.read!();
let clipboardContent = "content" in clipboard ? clipboard.content : {};
let clipboardContent = clipboardData.content;
expect(clipboardContent[ClipboardMIMEType.PlainText]).toEqual(
getEvaluatedCell(model, "A1").formattedValue
);
Expand All @@ -1491,8 +1487,7 @@ describe("Copy paste keyboard shortcut", () => {
setCellContent(model, "B1", "1");
selectCell(model, "B1");
document.body.dispatchEvent(getClipboardEvent("cut", clipboardData));
clipboard = await parent.env.clipboard.read!();
clipboardContent = "content" in clipboard ? clipboard.content : {};
clipboardContent = clipboardData.content;
expect(clipboardContent[ClipboardMIMEType.PlainText]).toEqual(
getEvaluatedCell(model, "B1").formattedValue
);
Expand All @@ -1512,6 +1507,7 @@ describe("Copy paste keyboard shortcut", () => {
// Fake OS clipboard should have the same content
// to make paste come from spreadsheet clipboard
// which support paste as values
parent.env.clipboard.write(clipboardData.content);
selectCell(model, "A2");
document.activeElement!.dispatchEvent(
new KeyboardEvent("keydown", { key: "V", ctrlKey: true, bubbles: true, shiftKey: true })
Expand Down Expand Up @@ -1607,8 +1603,7 @@ describe("Copy paste keyboard shortcut", () => {
createChart(model, { type: "bar" }, "chartId");
model.dispatch("SELECT_FIGURE", { id: "chartId" });
document.body.dispatchEvent(getClipboardEvent("copy", clipboardData));
const clipboard = await parent.env.clipboard.read!();
const clipboardContent = "content" in clipboard ? clipboard.content : {};
const clipboardContent = clipboardData.content;
expect(clipboardContent).toMatchObject({
"text/plain": "\t",
});
Expand All @@ -1621,8 +1616,7 @@ describe("Copy paste keyboard shortcut", () => {
createChart(model, { type: "bar" }, "chartId");
model.dispatch("SELECT_FIGURE", { id: "chartId" });
document.body.dispatchEvent(getClipboardEvent("cut", clipboardData));
const clipboard = await parent.env.clipboard.read!();
const clipboardContent = "content" in clipboard ? clipboard.content : {};
const clipboardContent = clipboardData.content;
expect(clipboardContent).toMatchObject({
"text/plain": "\t",
});
Expand Down

0 comments on commit 0e37522

Please sign in to comment.