clipboard fixes
This commit is contained in:
parent
c02e6829ad
commit
f14cac6490
7 changed files with 168 additions and 18 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -2,6 +2,17 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
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)
|
## [0.7.1] - Orm (2026-03-29)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ignis",
|
"name": "ignis",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "An Electron shim and server bridge for running Obsidian in a browser.",
|
"description": "An Electron shim and server bridge for running Obsidian in a browser.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ignis-bridge",
|
"id": "ignis-bridge",
|
||||||
"name": "Ignis Bridge",
|
"name": "Ignis Bridge",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"minAppVersion": "1.12.4",
|
"minAppVersion": "1.12.4",
|
||||||
"description": "Additional Ignis specific functionality and ignis plugin management.",
|
"description": "Additional Ignis specific functionality and ignis plugin management.",
|
||||||
"author": "Nystik",
|
"author": "Nystik",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { ipcRenderer } from "./ipc-renderer.js";
|
import { ipcRenderer } from "./ipc-renderer.js";
|
||||||
import { webFrame } from "./web-frame.js";
|
import { webFrame } from "./web-frame.js";
|
||||||
import { remoteShim } from "./remote/index.js";
|
import { remoteShim } from "./remote/index.js";
|
||||||
|
import { nativeImageShim } from "./remote/native-image.js";
|
||||||
|
import { clipboardShim } from "./remote/clipboard.js";
|
||||||
|
|
||||||
export const electronShim = {
|
export const electronShim = {
|
||||||
ipcRenderer,
|
ipcRenderer,
|
||||||
webFrame,
|
webFrame,
|
||||||
remote: remoteShim,
|
remote: remoteShim,
|
||||||
|
nativeImage: nativeImageShim,
|
||||||
|
clipboard: clipboardShim,
|
||||||
|
|
||||||
safeStorage: {
|
safeStorage: {
|
||||||
isEncryptionAvailable() {
|
isEncryptionAvailable() {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// stub
|
|
||||||
export const clipboardShim = {
|
export const clipboardShim = {
|
||||||
readText() {
|
readText() {
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -15,7 +14,16 @@ export const clipboardShim = {
|
||||||
},
|
},
|
||||||
|
|
||||||
writeHTML(html) {
|
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() {
|
readImage() {
|
||||||
|
|
@ -23,7 +31,23 @@ export const clipboardShim = {
|
||||||
},
|
},
|
||||||
|
|
||||||
writeImage(image) {
|
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) {
|
has(format) {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
export const nativeImageShim = {
|
||||||
createFromBuffer(buffer) {
|
createFromBuffer(buffer, options) {
|
||||||
return {
|
return createImage(buffer, options?.mimeType);
|
||||||
isEmpty: () => !buffer || buffer.length === 0,
|
|
||||||
getSize: () => ({ width: 0, height: 0 }),
|
|
||||||
toPNG: () => buffer || new Uint8Array(0),
|
|
||||||
toJPEG: (quality) => buffer || new Uint8Array(0),
|
|
||||||
toDataURL: () => "",
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createFromPath(filePath) {
|
createFromPath(filePath) {
|
||||||
// TODO: could fetch from server and create image
|
return createImage(new Uint8Array(0));
|
||||||
return nativeImageShim.createFromBuffer(new Uint8Array(0));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createEmpty() {
|
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));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -196,10 +196,58 @@ const currentWebContents = {
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
},
|
},
|
||||||
paste() {
|
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() {
|
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) {},
|
replaceMisspelling(word) {},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue