Skip to content

Commit c7e29ed

Browse files
committed
Improve pasting, support dragging in tables
1 parent 5699550 commit c7e29ed

File tree

3 files changed

+43
-15
lines changed

3 files changed

+43
-15
lines changed

TODO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- ChatGPT
4242
- Huggingface
4343
- Ollama
44+
- OpenRouter
4445
- Tool function exception handling
4546
- Clipboard integration
4647
- Fix "put" behavior to copy formulas around to selection
@@ -132,6 +133,7 @@
132133
- Playwright
133134
- Integrate Playwright coverage with vitest coverage
134135
- Test parsers and classes in formulas
136+
- LLM tests
135137
- Push to versioned folders on Pages based on git tags
136138
- Documentation
137139
- README

src/App.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
import { compressText } from "./compress.js";
119119
import { evalDebounced, functions } from "./formula-functions.svelte.js";
120120
import { debounce, replaceValues } from "./helpers.js";
121-
import { keyboardHandler, keybindings } from "./keyboard.js";
121+
import { actions, keyboardHandler, keybindings } from "./keyboard.js";
122122
123123
let { urlData } = $props();
124124
let globals = $state(
@@ -270,6 +270,9 @@
270270
{/snippet}
271271
272272
<svelte:window
273+
onpaste={(e) => actions.Paste(e, globals)}
274+
ondragover={(e) => e.preventDefault(/* Necessary for drop handler to work */)}
275+
ondrop={(e) => actions.Paste(e, globals)}
273276
onkeydown={(e) => keyboardHandler(e, globals)}
274277
onpopstate={(e) => {
275278
if (e.state == null) {

src/keyboard.js

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ export const keybindings = {
3232
"Meta+c": "Copy",
3333
"Ctrl+x": "Cut",
3434
"Meta+x": "Cut",
35-
"Ctrl+v": "Paste",
3635
"Ctrl+Shift+v": "Paste values",
37-
"Meta+v": "Paste",
36+
// Disabled here because we handle paste event instead
37+
// TODO: Handle these keybindings only if no paste event is fired
38+
// "Ctrl+v": "Paste",
39+
// "Meta+v": "Paste",
3840
"Meta+Shift+v": "Paste values",
3941
p: "Put After",
4042
"Shift+p": "Put Before",
@@ -81,14 +83,33 @@ export const keybindings = {
8183
// gT and gt
8284
};
8385

84-
async function getClipboard() {
85-
let clipboard = await navigator.clipboard.read();
86+
async function getClipboard(e) {
8687
const types = {};
87-
for (const item of clipboard) {
88-
for (const type of item.types) {
89-
if (type in types || !type.startsWith("text/")) continue;
90-
const blob = await item.getType(type);
91-
types[type] = await blob.text();
88+
const dataTransfer = e.dataTransfer ?? e.clipboardData;
89+
if (dataTransfer != null) {
90+
await Promise.all(
91+
Array.from(dataTransfer.items).map(
92+
(item) =>
93+
new Promise((resolve) => {
94+
if (item.kind != "string") return resolve();
95+
item.getAsString((s) => {
96+
if (item.type in types || !item.type.startsWith("text/")) {
97+
return resolve();
98+
}
99+
types[item.type] = s;
100+
resolve();
101+
});
102+
}),
103+
),
104+
);
105+
} else {
106+
let clipboard = await navigator.clipboard.read();
107+
for (const item of clipboard) {
108+
for (const type of item.types) {
109+
if (type in types || !type.startsWith("text/")) continue;
110+
const blob = await item.getType(type);
111+
types[type] = await blob.text();
112+
}
92113
}
93114
}
94115
return types;
@@ -131,10 +152,12 @@ function setPasteBufferFromClipboard(globals, clipboard) {
131152
globals.pasteBuffer = new Register(
132153
type,
133154
Array.from(table.querySelectorAll("tr")).map((row) =>
134-
Array.from(row.querySelectorAll("td")).map(
155+
Array.from(row.querySelectorAll("td, th")).map(
135156
({ dataset: { formula }, innerText: value }) => ({
136-
formula: formula != "" ? formula : undefined,
137-
get: () => (value != "" ? value : undefined),
157+
formula:
158+
formula || (value != "" && value != null ? value : undefined),
159+
// Values can be zero, so we have to explicitly check falsy cases
160+
get: () => (value != "" && value != null ? value : undefined),
138161
}),
139162
),
140163
),
@@ -242,13 +265,13 @@ export const actions = {
242265

243266
Paste: async (e, globals) => {
244267
// TODO: Notify the user of paste error
245-
setPasteBufferFromClipboard(globals, await getClipboard());
268+
setPasteBufferFromClipboard(globals, await getClipboard(e));
246269
return actions["Put After"](e, globals);
247270
},
248271

249272
"Paste values": async (e, globals) => {
250273
// TODO: Notify the user of paste error
251-
setPasteBufferFromClipboard(globals, await getClipboard());
274+
setPasteBufferFromClipboard(globals, await getClipboard(e));
252275
switch (globals.mode) {
253276
case "normal":
254277
case "visual":

0 commit comments

Comments
 (0)