clipboard fixes

This commit is contained in:
Nystik
2026-03-29 20:02:22 +02:00
parent c02e6829ad
commit f14cac6490
7 changed files with 168 additions and 18 deletions

View File

@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file.
## [0.7.2] - Orm (2026-03-29)
### Added
- utils shim
### Fixed
- right sidebar toggle with css overrides
- clipboard functionality
## [0.7.1] - Orm (2026-03-29)
### Added

View File

@@ -1,6 +1,6 @@
{
"name": "ignis",
"version": "0.7.1",
"version": "0.7.2",
"private": true,
"description": "An Electron shim and server bridge for running Obsidian in a browser.",
"scripts": {

View File

@@ -1,7 +1,7 @@
{
"id": "ignis-bridge",
"name": "Ignis Bridge",
"version": "0.7.1",
"version": "0.7.2",
"minAppVersion": "1.12.4",
"description": "Additional Ignis specific functionality and ignis plugin management.",
"author": "Nystik",

View File

@@ -1,11 +1,15 @@
import { ipcRenderer } from "./ipc-renderer.js";
import { webFrame } from "./web-frame.js";
import { remoteShim } from "./remote/index.js";
import { nativeImageShim } from "./remote/native-image.js";
import { clipboardShim } from "./remote/clipboard.js";
export const electronShim = {
ipcRenderer,
webFrame,
remote: remoteShim,
nativeImage: nativeImageShim,
clipboard: clipboardShim,
safeStorage: {
isEncryptionAvailable() {

View File

@@ -1,4 +1,3 @@
// stub
export const clipboardShim = {
readText() {
return "";
@@ -15,7 +14,16 @@ export const clipboardShim = {
},
writeHTML(html) {
console.log("[shim:clipboard] writeHTML (stub)");
navigator.clipboard
.write([
new ClipboardItem({
"text/html": new Blob([html], { type: "text/html" }),
"text/plain": new Blob([html], { type: "text/plain" }),
}),
])
.catch((e) => {
console.warn("[shim:clipboard] writeHTML failed:", e);
});
},
readImage() {
@@ -23,7 +31,23 @@ export const clipboardShim = {
},
writeImage(image) {
console.log("[shim:clipboard] writeImage (stub)");
if (!image || image.isEmpty()) {
return;
}
const pngData = image.toPNG();
if (!pngData || pngData.length === 0) {
return;
}
const blob = new Blob([pngData], { type: "image/png" });
navigator.clipboard
.write([new ClipboardItem({ "image/png": blob })])
.catch((e) => {
console.warn("[shim:clipboard] writeImage failed:", e);
});
},
has(format) {

View File

@@ -1,20 +1,83 @@
function createImage(buffer, mimeType) {
return {
_buffer: buffer,
_mimeType: mimeType || "image/png",
isEmpty() {
return !buffer || buffer.length === 0;
},
getSize() {
return { width: 0, height: 0 };
},
toPNG() {
return buffer || new Uint8Array(0);
},
toJPEG(quality) {
return buffer || new Uint8Array(0);
},
toDataURL() {
if (!buffer || buffer.length === 0) {
return "";
}
const bytes =
buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return `data:${this._mimeType};base64,${btoa(binary)}`;
},
toBitmap() {
return buffer || new Uint8Array(0);
},
getBitmap() {
return buffer || new Uint8Array(0);
},
};
}
export const nativeImageShim = {
createFromBuffer(buffer) {
return {
isEmpty: () => !buffer || buffer.length === 0,
getSize: () => ({ width: 0, height: 0 }),
toPNG: () => buffer || new Uint8Array(0),
toJPEG: (quality) => buffer || new Uint8Array(0),
toDataURL: () => "",
};
createFromBuffer(buffer, options) {
return createImage(buffer, options?.mimeType);
},
createFromPath(filePath) {
// TODO: could fetch from server and create image
return nativeImageShim.createFromBuffer(new Uint8Array(0));
return createImage(new Uint8Array(0));
},
createEmpty() {
return nativeImageShim.createFromBuffer(new Uint8Array(0));
return createImage(new Uint8Array(0));
},
createFromDataURL(dataURL) {
if (!dataURL || !dataURL.startsWith("data:")) {
return createImage(new Uint8Array(0));
}
const parts = dataURL.split(",");
const mimeMatch = parts[0].match(/data:([^;]+)/);
const mimeType = mimeMatch ? mimeMatch[1] : "image/png";
try {
const binary = atob(parts[1]);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return createImage(bytes, mimeType);
} catch {
return createImage(new Uint8Array(0));
}
},
};

View File

@@ -196,10 +196,58 @@ const currentWebContents = {
document.execCommand("copy");
},
paste() {
document.execCommand("paste");
navigator.clipboard
.read()
.then(async (items) => {
const dt = new DataTransfer();
let hasFiles = false;
for (const item of items) {
for (const type of item.types) {
const blob = await item.getType(type);
if (type.startsWith("text/")) {
const text = await blob.text();
dt.items.add(text, type);
} else {
const ext = type.split("/")[1] || "bin";
dt.items.add(
new File([blob], `pasted-image.${ext}`, { type }),
);
hasFiles = true;
}
}
}
const pasteEvent = new ClipboardEvent("paste", {
bubbles: true,
cancelable: true,
clipboardData: dt,
});
const target = document.activeElement || document.body;
target.dispatchEvent(pasteEvent);
})
.catch((e) => {
console.warn("[shim:webContents] paste failed:", e);
});
},
pasteAndMatchStyle() {
document.execCommand("paste");
navigator.clipboard
.read()
.then(async (items) => {
for (const item of items) {
if (item.types.includes("text/plain")) {
const blob = await item.getType("text/plain");
const text = await blob.text();
document.execCommand("insertText", false, text);
return;
}
}
})
.catch((e) => {
console.warn("[shim:webContents] pasteAndMatchStyle failed:", e);
});
},
replaceMisspelling(word) {},