mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
consolidate build scripts, reorganize source into src/ directory, fix favicon injection
This commit is contained in:
47
src/shims/electron/index.js
Normal file
47
src/shims/electron/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ipcRenderer } from "./ipc-renderer.js";
|
||||
import { webFrame } from "./web-frame.js";
|
||||
import { remoteShim } from "./remote/index.js";
|
||||
|
||||
export const electronShim = {
|
||||
ipcRenderer,
|
||||
webFrame,
|
||||
remote: remoteShim,
|
||||
|
||||
safeStorage: {
|
||||
isEncryptionAvailable() {
|
||||
return false;
|
||||
},
|
||||
encryptString(plainText) {
|
||||
return Buffer.from(plainText);
|
||||
},
|
||||
decryptString(encrypted) {
|
||||
return encrypted.toString();
|
||||
},
|
||||
},
|
||||
|
||||
webUtils: {
|
||||
getPathForFile(file) {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
|
||||
deprecate: {
|
||||
function(fn, name) {
|
||||
return fn;
|
||||
},
|
||||
event(emitter, name) {},
|
||||
removeFunction(fn, name) {
|
||||
return fn;
|
||||
},
|
||||
log(message) {
|
||||
console.log("[electron:deprecate]", message);
|
||||
},
|
||||
warn(oldName, newName) {},
|
||||
promisify(fn) {
|
||||
return fn;
|
||||
},
|
||||
renameFunction(fn, newName) {
|
||||
return fn;
|
||||
},
|
||||
},
|
||||
};
|
||||
260
src/shims/electron/ipc-renderer.js
Normal file
260
src/shims/electron/ipc-renderer.js
Normal file
@@ -0,0 +1,260 @@
|
||||
import { showVaultManager } from "../../ui/bootstrap.js";
|
||||
import { vaultService } from "../../services/vault-service.js";
|
||||
|
||||
const listeners = new Map();
|
||||
|
||||
const syncHandlers = {
|
||||
vault: () => window.__vaultConfig || { id: "default-vault", path: "/" },
|
||||
version: () => window.__obsidianVersion || "0.0.0",
|
||||
"is-dev": () => false,
|
||||
|
||||
"file-url": () =>
|
||||
"/vault-files/" + encodeURIComponent(window.__currentVaultId || "") + "/",
|
||||
|
||||
"disable-update": () => true,
|
||||
update: () => "",
|
||||
"disable-gpu": () => false,
|
||||
frame: () => null,
|
||||
"set-icon": () => null,
|
||||
"get-icon": () => null,
|
||||
|
||||
relaunch: () => {
|
||||
window.location.reload();
|
||||
return null;
|
||||
},
|
||||
|
||||
starter: () => {
|
||||
showVaultManager();
|
||||
return null;
|
||||
},
|
||||
|
||||
help: () => {
|
||||
window.open("https://help.obsidian.md/", "_blank");
|
||||
return null;
|
||||
},
|
||||
|
||||
sandbox: () => null,
|
||||
|
||||
"copy-asar": () => false,
|
||||
"check-update": () => null,
|
||||
|
||||
"vault-list": () => {
|
||||
const result = {};
|
||||
|
||||
for (const v of window.__vaultList || []) {
|
||||
result[v.id] = {
|
||||
path: "/" + v.id,
|
||||
ts: Date.now(),
|
||||
open: v.id === vaultService.getCurrentVaultId(),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
"vault-open": (vaultPath, newWindow) => {
|
||||
const id = (vaultPath || "").replace(/^\/+/, "");
|
||||
const vault = (window.__vaultList || []).find((v) => v.id === id);
|
||||
|
||||
if (!vault && id) {
|
||||
if (!vaultService.createVaultSync(id)) {
|
||||
return "Failed to create vault";
|
||||
}
|
||||
}
|
||||
|
||||
vaultService.openVault(id);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
"vault-remove": (vaultPath) => {
|
||||
const id = (vaultPath || "").replace(/^\/+/, "");
|
||||
|
||||
return vaultService.deleteVaultSync(id);
|
||||
},
|
||||
|
||||
"vault-move": (oldPath, newPath) => {
|
||||
return "Moving vaults is not supported in the web version";
|
||||
},
|
||||
|
||||
"vault-message": () => null,
|
||||
"get-default-vault-path": () => "/My Vault",
|
||||
"get-documents-path": () => "/",
|
||||
"desktop-dir": () => "/desktop",
|
||||
"documents-dir": () => "/documents",
|
||||
resources: () => "",
|
||||
};
|
||||
|
||||
function arrayBufferToBase64(buf) {
|
||||
const bytes = new Uint8Array(buf);
|
||||
let binary = "";
|
||||
const chunk = 8192;
|
||||
|
||||
for (let i = 0; i < bytes.length; i += chunk) {
|
||||
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
|
||||
}
|
||||
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
function base64ToArrayBuffer(base64) {
|
||||
const binary = atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
async function handleRequestUrl(requestId, request) {
|
||||
try {
|
||||
let body = request.body;
|
||||
let binary = false;
|
||||
|
||||
if (body instanceof ArrayBuffer) {
|
||||
body = arrayBufferToBase64(body);
|
||||
binary = true;
|
||||
}
|
||||
|
||||
const res = await fetch("/api/proxy", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
url: request.url,
|
||||
method: request.method || "GET",
|
||||
headers: request.headers || {},
|
||||
contentType: request.contentType,
|
||||
body,
|
||||
binary,
|
||||
}),
|
||||
});
|
||||
|
||||
const proxyResult = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
ipcRenderer._emit(requestId, {
|
||||
error: proxyResult.error || "Proxy request failed",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Electron's e.reply(requestId, data) sends on the requestId channel
|
||||
ipcRenderer._emit(requestId, {
|
||||
status: proxyResult.status,
|
||||
headers: proxyResult.headers,
|
||||
body: base64ToArrayBuffer(proxyResult.body),
|
||||
});
|
||||
} catch (e) {
|
||||
ipcRenderer._emit(requestId, {
|
||||
error: e.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const ipcRenderer = {
|
||||
send(channel, ...args) {
|
||||
console.log("[shim:ipcRenderer] send:", channel, args);
|
||||
|
||||
if (channel === "context-menu") {
|
||||
queueMicrotask(() =>
|
||||
ipcRenderer._emit("context-menu", {
|
||||
webContentsId: 1,
|
||||
editFlags: { canCut: true, canCopy: true, canPaste: true },
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel === "request-url") {
|
||||
const [requestId, request] = args;
|
||||
handleRequestUrl(requestId, request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel === "print-to-pdf") {
|
||||
const iframe = window.__popupIframe;
|
||||
|
||||
if (iframe) {
|
||||
setTimeout(() => {
|
||||
iframe.contentWindow.print();
|
||||
setTimeout(() => {
|
||||
iframe.contentWindow.close();
|
||||
ipcRenderer._emit("print-to-pdf", { success: true });
|
||||
}, 500);
|
||||
}, 200);
|
||||
} else {
|
||||
window.print();
|
||||
|
||||
queueMicrotask(() => {
|
||||
ipcRenderer._emit("print-to-pdf", { success: true });
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
sendSync(channel, ...args) {
|
||||
console.log("[shim:ipcRenderer] sendSync:", channel, args);
|
||||
|
||||
if (syncHandlers[channel]) {
|
||||
return syncHandlers[channel](...args);
|
||||
}
|
||||
|
||||
console.warn("[shim:ipcRenderer] Unhandled sendSync channel:", channel);
|
||||
return null;
|
||||
},
|
||||
|
||||
on(channel, listener) {
|
||||
if (!listeners.has(channel)) {
|
||||
listeners.set(channel, []);
|
||||
}
|
||||
|
||||
listeners.get(channel).push(listener);
|
||||
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
once(channel, listener) {
|
||||
const wrapped = (...args) => {
|
||||
ipcRenderer.removeListener(channel, wrapped);
|
||||
listener(...args);
|
||||
};
|
||||
|
||||
return ipcRenderer.on(channel, wrapped);
|
||||
},
|
||||
|
||||
removeListener(channel, listener) {
|
||||
const arr = listeners.get(channel);
|
||||
if (arr) {
|
||||
const idx = arr.indexOf(listener);
|
||||
|
||||
if (idx >= 0) {
|
||||
arr.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
removeAllListeners(channel) {
|
||||
if (channel) {
|
||||
listeners.delete(channel);
|
||||
} else {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
_emit(channel, ...args) {
|
||||
const arr = listeners.get(channel);
|
||||
|
||||
if (arr) {
|
||||
for (const fn of arr) {
|
||||
fn({}, ...args);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
43
src/shims/electron/remote/app.js
Normal file
43
src/shims/electron/remote/app.js
Normal file
@@ -0,0 +1,43 @@
|
||||
export const appShim = {
|
||||
getPath(name) {
|
||||
const paths = {
|
||||
userData: "/.obsidian",
|
||||
home: "/",
|
||||
documents: "/documents",
|
||||
desktop: "/desktop",
|
||||
temp: "/tmp",
|
||||
appData: "/.obsidian",
|
||||
};
|
||||
return paths[name] || "/";
|
||||
},
|
||||
|
||||
getVersion() {
|
||||
return window.__obsidianVersion || "0.0.0";
|
||||
},
|
||||
|
||||
getName() {
|
||||
return "Obsidian";
|
||||
},
|
||||
|
||||
getLocale() {
|
||||
return navigator.language || "en-US";
|
||||
},
|
||||
|
||||
isPackaged: true,
|
||||
|
||||
quit() {
|
||||
console.log("[shim:app] quit (stub)");
|
||||
},
|
||||
|
||||
relaunch() {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
whenReady() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
on() {},
|
||||
once() {},
|
||||
removeListener() {},
|
||||
};
|
||||
40
src/shims/electron/remote/clipboard.js
Normal file
40
src/shims/electron/remote/clipboard.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// stub
|
||||
export const clipboardShim = {
|
||||
readText() {
|
||||
return "";
|
||||
},
|
||||
|
||||
writeText(text) {
|
||||
navigator.clipboard.writeText(text).catch((e) => {
|
||||
console.warn("[shim:clipboard] writeText failed:", e);
|
||||
});
|
||||
},
|
||||
|
||||
readHTML() {
|
||||
return "";
|
||||
},
|
||||
|
||||
writeHTML(html) {
|
||||
console.log("[shim:clipboard] writeHTML (stub)");
|
||||
},
|
||||
|
||||
readImage() {
|
||||
return { isEmpty: () => true, toPNG: () => new Uint8Array(0) };
|
||||
},
|
||||
|
||||
writeImage(image) {
|
||||
console.log("[shim:clipboard] writeImage (stub)");
|
||||
},
|
||||
|
||||
has(format) {
|
||||
return false;
|
||||
},
|
||||
|
||||
read(format) {
|
||||
return "";
|
||||
},
|
||||
|
||||
clear() {
|
||||
navigator.clipboard.writeText("").catch(() => {});
|
||||
},
|
||||
};
|
||||
70
src/shims/electron/remote/dialog.js
Normal file
70
src/shims/electron/remote/dialog.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
showMessageDialog,
|
||||
showConfirmDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../ui/bootstrap.js";
|
||||
|
||||
export const dialogShim = {
|
||||
async showOpenDialog(browserWindow, options) {
|
||||
// TODO: implement custom modal with server-side file listing
|
||||
console.log("[shim:dialog] showOpenDialog (stub):", options);
|
||||
return { canceled: true, filePaths: [] };
|
||||
},
|
||||
|
||||
async showSaveDialog(browserWindow, options) {
|
||||
if (typeof browserWindow === "object" && !options) {
|
||||
options = browserWindow;
|
||||
}
|
||||
|
||||
const defaultName =
|
||||
options?.defaultPath?.split(/[\/\\]/).pop() || "download";
|
||||
const name = await showPromptDialog(
|
||||
"Save File",
|
||||
"Save as:",
|
||||
"filename",
|
||||
defaultName,
|
||||
"Save",
|
||||
);
|
||||
|
||||
if (!name) {
|
||||
return { canceled: true, filePath: undefined };
|
||||
}
|
||||
|
||||
return { canceled: false, filePath: "/downloads/" + name };
|
||||
},
|
||||
|
||||
async showMessageBox(browserWindow, options) {
|
||||
if (typeof browserWindow === "object" && !options) {
|
||||
options = browserWindow;
|
||||
}
|
||||
|
||||
console.log("[shim:dialog] showMessageBox:", options);
|
||||
|
||||
const message = options.message || "";
|
||||
const detail = options.detail || "";
|
||||
const buttons = options.buttons || ["OK"];
|
||||
const fullMessage = message + (detail ? "\n\n" + detail : "");
|
||||
|
||||
if (buttons.length <= 1) {
|
||||
await showMessageDialog(options.title || "Message", fullMessage);
|
||||
return { response: 0, checkboxChecked: false };
|
||||
}
|
||||
|
||||
const result = await showConfirmDialog(
|
||||
options.title || "Confirm",
|
||||
message,
|
||||
detail,
|
||||
buttons[0],
|
||||
);
|
||||
|
||||
return {
|
||||
response: result ? 0 : 1,
|
||||
checkboxChecked: false,
|
||||
};
|
||||
},
|
||||
|
||||
showErrorBox(title, content) {
|
||||
console.error("[shim:dialog] Error:", title, content);
|
||||
showMessageDialog(title, content);
|
||||
},
|
||||
};
|
||||
50
src/shims/electron/remote/index.js
Normal file
50
src/shims/electron/remote/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { clipboardShim } from "./clipboard.js";
|
||||
import { shellShim } from "./shell.js";
|
||||
import { dialogShim } from "./dialog.js";
|
||||
import { menuShim, menuItemShim } from "./menu.js";
|
||||
import { appShim } from "./app.js";
|
||||
import { windowShim, webContentsShim } from "./window.js";
|
||||
import { themeShim } from "./theme.js";
|
||||
import { sessionShim } from "./session.js";
|
||||
import { systemPreferencesShim } from "./system-preferences.js";
|
||||
import { screenShim } from "./screen.js";
|
||||
import { nativeImageShim } from "./native-image.js";
|
||||
import { notificationShim } from "./notification.js";
|
||||
|
||||
export const remoteShim = {
|
||||
clipboard: clipboardShim,
|
||||
shell: shellShim,
|
||||
dialog: dialogShim,
|
||||
Menu: menuShim,
|
||||
MenuItem: menuItemShim,
|
||||
app: appShim,
|
||||
BrowserWindow: windowShim,
|
||||
nativeTheme: themeShim,
|
||||
session: sessionShim,
|
||||
systemPreferences: systemPreferencesShim,
|
||||
screen: screenShim,
|
||||
nativeImage: nativeImageShim,
|
||||
Notification: notificationShim,
|
||||
|
||||
safeStorage: {
|
||||
isEncryptionAvailable() {
|
||||
return false;
|
||||
},
|
||||
encryptString(plainText) {
|
||||
return Buffer.from(plainText);
|
||||
},
|
||||
decryptString(encrypted) {
|
||||
return encrypted.toString();
|
||||
},
|
||||
},
|
||||
|
||||
getCurrentWindow() {
|
||||
return windowShim._current();
|
||||
},
|
||||
|
||||
webContents: webContentsShim,
|
||||
|
||||
getCurrentWebContents() {
|
||||
return webContentsShim._current();
|
||||
},
|
||||
};
|
||||
53
src/shims/electron/remote/menu.js
Normal file
53
src/shims/electron/remote/menu.js
Normal file
@@ -0,0 +1,53 @@
|
||||
export class menuShim {
|
||||
constructor() {
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
static buildFromTemplate(template) {
|
||||
const menu = new menuShim();
|
||||
menu.items = (template || []).map((item) => new menuItemShim(item));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
static setApplicationMenu(menu) {
|
||||
console.log("[shim:Menu] setApplicationMenu (stub)");
|
||||
}
|
||||
|
||||
static getApplicationMenu() {
|
||||
return null;
|
||||
}
|
||||
|
||||
popup(options) {
|
||||
console.log("[shim:Menu] popup (stub)", options);
|
||||
}
|
||||
|
||||
append(menuItem) {
|
||||
this.items.push(menuItem);
|
||||
}
|
||||
|
||||
insert(pos, menuItem) {
|
||||
this.items.splice(pos, 0, menuItem);
|
||||
}
|
||||
|
||||
closePopup() {}
|
||||
}
|
||||
|
||||
export class menuItemShim {
|
||||
constructor(options = {}) {
|
||||
this.label = options.label || "";
|
||||
this.type = options.type || "normal";
|
||||
this.click = options.click || null;
|
||||
this.role = options.role || null;
|
||||
this.accelerator = options.accelerator || "";
|
||||
this.enabled = options.enabled !== false;
|
||||
this.visible = options.visible !== false;
|
||||
this.checked = !!options.checked;
|
||||
this.submenu = options.submenu
|
||||
? menuShim.buildFromTemplate(
|
||||
Array.isArray(options.submenu) ? options.submenu : [],
|
||||
)
|
||||
: null;
|
||||
this.id = options.id || "";
|
||||
}
|
||||
}
|
||||
20
src/shims/electron/remote/native-image.js
Normal file
20
src/shims/electron/remote/native-image.js
Normal file
@@ -0,0 +1,20 @@
|
||||
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: () => "",
|
||||
};
|
||||
},
|
||||
|
||||
createFromPath(filePath) {
|
||||
// TODO: could fetch from server and create image
|
||||
return nativeImageShim.createFromBuffer(new Uint8Array(0));
|
||||
},
|
||||
|
||||
createEmpty() {
|
||||
return nativeImageShim.createFromBuffer(new Uint8Array(0));
|
||||
},
|
||||
};
|
||||
37
src/shims/electron/remote/notification.js
Normal file
37
src/shims/electron/remote/notification.js
Normal file
@@ -0,0 +1,37 @@
|
||||
export class notificationShim {
|
||||
constructor(options = {}) {
|
||||
this.title = options.title || "";
|
||||
this.body = options.body || "";
|
||||
this.silent = options.silent || false;
|
||||
this._handlers = {};
|
||||
}
|
||||
|
||||
show() {
|
||||
if ("Notification" in window && Notification.permission === "granted") {
|
||||
new Notification(this.title, { body: this.body, silent: this.silent });
|
||||
} else if (
|
||||
"Notification" in window &&
|
||||
Notification.permission !== "denied"
|
||||
) {
|
||||
Notification.requestPermission().then((perm) => {
|
||||
if (perm === "granted") {
|
||||
new Notification(this.title, {
|
||||
body: this.body,
|
||||
silent: this.silent,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
close() {}
|
||||
|
||||
on(event, handler) {
|
||||
this._handlers[event] = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
static isSupported() {
|
||||
return "Notification" in window;
|
||||
}
|
||||
}
|
||||
40
src/shims/electron/remote/screen.js
Normal file
40
src/shims/electron/remote/screen.js
Normal file
@@ -0,0 +1,40 @@
|
||||
export const screenShim = {
|
||||
getPrimaryDisplay() {
|
||||
return {
|
||||
workAreaSize: {
|
||||
width: window.screen.availWidth,
|
||||
height: window.screen.availHeight,
|
||||
},
|
||||
size: { width: window.screen.width, height: window.screen.height },
|
||||
scaleFactor: window.devicePixelRatio || 1,
|
||||
bounds: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: window.screen.width,
|
||||
height: window.screen.height,
|
||||
},
|
||||
workArea: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: window.screen.availWidth,
|
||||
height: window.screen.availHeight,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
getAllDisplays() {
|
||||
return [screenShim.getPrimaryDisplay()];
|
||||
},
|
||||
|
||||
getDisplayNearestPoint(point) {
|
||||
return screenShim.getPrimaryDisplay();
|
||||
},
|
||||
|
||||
getCursorScreenPoint() {
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
|
||||
on() {},
|
||||
once() {},
|
||||
removeListener() {},
|
||||
};
|
||||
20
src/shims/electron/remote/session.js
Normal file
20
src/shims/electron/remote/session.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export const sessionShim = {
|
||||
defaultSession: {
|
||||
clearCache() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
clearStorageData() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
setSpellCheckerLanguages(langs) {},
|
||||
getSpellCheckerLanguages() {
|
||||
return [];
|
||||
},
|
||||
|
||||
on() {},
|
||||
once() {},
|
||||
removeListener() {},
|
||||
},
|
||||
};
|
||||
15
src/shims/electron/remote/shell.js
Normal file
15
src/shims/electron/remote/shell.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const shellShim = {
|
||||
openExternal(url) {
|
||||
window.open(url, "_blank");
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
openPath(filePath) {
|
||||
console.log("[shim:shell] openPath (stub):", filePath);
|
||||
return Promise.resolve("");
|
||||
},
|
||||
|
||||
showItemInFolder(filePath) {
|
||||
console.log("[shim:shell] showItemInFolder (stub):", filePath);
|
||||
},
|
||||
};
|
||||
21
src/shims/electron/remote/system-preferences.js
Normal file
21
src/shims/electron/remote/system-preferences.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export const systemPreferencesShim = {
|
||||
getAccentColor() {
|
||||
return "0078d4"; // Default Windows accent blue
|
||||
},
|
||||
|
||||
isAeroGlassEnabled() {
|
||||
return false;
|
||||
},
|
||||
|
||||
getMediaAccessStatus(mediaType) {
|
||||
return "granted";
|
||||
},
|
||||
|
||||
askForMediaAccess(mediaType) {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
|
||||
on() {},
|
||||
once() {},
|
||||
removeListener() {},
|
||||
};
|
||||
64
src/shims/electron/remote/theme.js
Normal file
64
src/shims/electron/remote/theme.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const listeners = [];
|
||||
|
||||
const darkQuery =
|
||||
typeof window !== "undefined"
|
||||
? window.matchMedia("(prefers-color-scheme: dark)")
|
||||
: null;
|
||||
|
||||
if (darkQuery?.addEventListener) {
|
||||
darkQuery.addEventListener("change", () => {
|
||||
for (const fn of listeners) {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const themeShim = {
|
||||
get shouldUseDarkColors() {
|
||||
return darkQuery ? darkQuery.matches : true;
|
||||
},
|
||||
|
||||
get themeSource() {
|
||||
return "system";
|
||||
},
|
||||
|
||||
set themeSource(val) {
|
||||
// No-op in browser; theme is controlled by OS
|
||||
},
|
||||
|
||||
on(event, callback) {
|
||||
if (event === "updated") {
|
||||
listeners.push(callback);
|
||||
}
|
||||
return themeShim;
|
||||
},
|
||||
|
||||
once(event, callback) {
|
||||
if (event === "updated") {
|
||||
const wrapped = () => {
|
||||
const idx = listeners.indexOf(wrapped);
|
||||
if (idx >= 0) {
|
||||
listeners.splice(idx, 1);
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
listeners.push(wrapped);
|
||||
}
|
||||
return themeShim;
|
||||
},
|
||||
|
||||
removeListener(event, callback) {
|
||||
const idx = listeners.indexOf(callback);
|
||||
if (idx >= 0) {
|
||||
listeners.splice(idx, 1);
|
||||
}
|
||||
|
||||
return themeShim;
|
||||
},
|
||||
|
||||
removeAllListeners() {
|
||||
listeners.length = 0;
|
||||
return themeShim;
|
||||
},
|
||||
};
|
||||
371
src/shims/electron/remote/window.js
Normal file
371
src/shims/electron/remote/window.js
Normal file
@@ -0,0 +1,371 @@
|
||||
const currentWindowState = {
|
||||
title: "Obsidian",
|
||||
isMaximized: false,
|
||||
isMinimized: false,
|
||||
isFullScreen: false,
|
||||
isAlwaysOnTop: false,
|
||||
bounds: { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight },
|
||||
focusTime: Date.now(),
|
||||
};
|
||||
|
||||
const currentWindow = {
|
||||
isMaximized: () => currentWindowState.isMaximized,
|
||||
isMinimized: () => currentWindowState.isMinimized,
|
||||
isFullScreen: () => !!document.fullscreenElement,
|
||||
isAlwaysOnTop: () => currentWindowState.isAlwaysOnTop,
|
||||
isFocused: () => document.hasFocus(),
|
||||
isVisible: () => true,
|
||||
isDestroyed: () => false,
|
||||
|
||||
minimize() {
|
||||
console.log("[shim:window] minimize (stub)");
|
||||
},
|
||||
|
||||
maximize() {
|
||||
currentWindowState.isMaximized = true;
|
||||
},
|
||||
|
||||
unmaximize() {
|
||||
currentWindowState.isMaximized = false;
|
||||
},
|
||||
|
||||
restore() {
|
||||
currentWindowState.isMinimized = false;
|
||||
},
|
||||
|
||||
close() {
|
||||
console.log("[shim:window] close (stub)");
|
||||
},
|
||||
|
||||
focus() {
|
||||
window.focus();
|
||||
},
|
||||
|
||||
show() {},
|
||||
hide() {},
|
||||
|
||||
setTitle(title) {
|
||||
currentWindowState.title = title;
|
||||
document.title = title;
|
||||
},
|
||||
|
||||
getTitle() {
|
||||
return currentWindowState.title;
|
||||
},
|
||||
|
||||
setAlwaysOnTop(flag) {
|
||||
currentWindowState.isAlwaysOnTop = flag;
|
||||
},
|
||||
|
||||
setFullScreen(flag) {
|
||||
if (flag) {
|
||||
document.documentElement.requestFullscreen?.();
|
||||
} else {
|
||||
document.exitFullscreen?.();
|
||||
}
|
||||
},
|
||||
|
||||
getBounds() {
|
||||
return {
|
||||
x: window.screenX,
|
||||
y: window.screenY,
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
};
|
||||
},
|
||||
|
||||
setBounds(bounds) {
|
||||
console.log("[shim:window] setBounds (stub):", bounds);
|
||||
},
|
||||
|
||||
setSize(width, height) {},
|
||||
setPosition(x, y) {},
|
||||
center() {},
|
||||
|
||||
setTrafficLightPosition() {},
|
||||
setWindowButtonPosition() {},
|
||||
|
||||
get webContents() {
|
||||
return webContentsShim._current();
|
||||
},
|
||||
|
||||
get menuBarVisible() {
|
||||
return false;
|
||||
},
|
||||
set menuBarVisible(v) {},
|
||||
|
||||
get loaded() {
|
||||
return true;
|
||||
},
|
||||
set loaded(v) {},
|
||||
|
||||
get focusTime() {
|
||||
return currentWindowState.focusTime;
|
||||
},
|
||||
set focusTime(v) {
|
||||
currentWindowState.focusTime = v;
|
||||
},
|
||||
|
||||
on(event, handler) {
|
||||
if (event === "focus") {
|
||||
window.addEventListener("focus", handler);
|
||||
} else if (event === "blur") {
|
||||
window.addEventListener("blur", handler);
|
||||
} else if (event === "resize") {
|
||||
window.addEventListener("resize", handler);
|
||||
}
|
||||
|
||||
return currentWindow;
|
||||
},
|
||||
|
||||
once(event, handler) {
|
||||
if (event === "focus") {
|
||||
window.addEventListener("focus", handler, { once: true });
|
||||
}
|
||||
return currentWindow;
|
||||
},
|
||||
|
||||
removeListener() {
|
||||
return currentWindow;
|
||||
},
|
||||
removeAllListeners() {
|
||||
return currentWindow;
|
||||
},
|
||||
};
|
||||
|
||||
const currentWebContents = {
|
||||
id: 1,
|
||||
_zoomLevel: 0,
|
||||
|
||||
get zoomLevel() {
|
||||
return this._zoomLevel;
|
||||
},
|
||||
set zoomLevel(v) {
|
||||
this._zoomLevel = v;
|
||||
},
|
||||
|
||||
executeJavaScript(code) {
|
||||
try {
|
||||
return Promise.resolve(eval(code));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
|
||||
getZoomFactor() {
|
||||
return Math.pow(1.2, this._zoomLevel);
|
||||
},
|
||||
getZoomLevel() {
|
||||
return this._zoomLevel;
|
||||
},
|
||||
setZoomLevel(v) {
|
||||
this._zoomLevel = v;
|
||||
},
|
||||
|
||||
isDevToolsOpened() {
|
||||
return false;
|
||||
},
|
||||
openDevTools() {},
|
||||
|
||||
setWindowOpenHandler(handler) {
|
||||
this._windowOpenHandler = handler;
|
||||
},
|
||||
|
||||
printToPDF(options) {
|
||||
return new Promise((resolve) => {
|
||||
window.print();
|
||||
resolve(Buffer.from([]));
|
||||
});
|
||||
},
|
||||
|
||||
capturePage(rect) {
|
||||
// TODO: could use html2canvas
|
||||
console.log("[shim:webContents] capturePage (stub)");
|
||||
return Promise.resolve({
|
||||
toPNG: () => new Uint8Array(0),
|
||||
toJPEG: () => new Uint8Array(0),
|
||||
});
|
||||
},
|
||||
|
||||
undo() {},
|
||||
redo() {},
|
||||
cut() {
|
||||
document.execCommand("cut");
|
||||
},
|
||||
copy() {
|
||||
document.execCommand("copy");
|
||||
},
|
||||
paste() {
|
||||
document.execCommand("paste");
|
||||
},
|
||||
pasteAndMatchStyle() {
|
||||
document.execCommand("paste");
|
||||
},
|
||||
replaceMisspelling(word) {},
|
||||
|
||||
session: {
|
||||
availableSpellCheckerLanguages: [],
|
||||
setSpellCheckerLanguages(langs) {},
|
||||
addWordToSpellCheckerDictionary(word) {},
|
||||
},
|
||||
|
||||
setSpellCheckerLanguages(langs) {},
|
||||
|
||||
on(event, handler) {
|
||||
return currentWebContents;
|
||||
},
|
||||
once(event, handler) {
|
||||
return currentWebContents;
|
||||
},
|
||||
removeListener() {
|
||||
return currentWebContents;
|
||||
},
|
||||
|
||||
get isSecured() {
|
||||
return true;
|
||||
},
|
||||
set isSecured(v) {},
|
||||
};
|
||||
|
||||
// Popup tracking for PDF export etc.
|
||||
let _popupWindow = null;
|
||||
let _popupWebContents = null;
|
||||
|
||||
export function registerPopupWindow() {
|
||||
_popupWebContents = {
|
||||
id: 2,
|
||||
_zoomLevel: 0,
|
||||
getZoomFactor() {
|
||||
return 1;
|
||||
},
|
||||
getZoomLevel() {
|
||||
return 0;
|
||||
},
|
||||
setZoomLevel() {},
|
||||
printToPDF(options) {
|
||||
return Promise.resolve(Buffer.from([]));
|
||||
},
|
||||
executeJavaScript(code) {
|
||||
try {
|
||||
return Promise.resolve(eval(code));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
on() {
|
||||
return _popupWebContents;
|
||||
},
|
||||
once() {
|
||||
return _popupWebContents;
|
||||
},
|
||||
removeListener() {
|
||||
return _popupWebContents;
|
||||
},
|
||||
isDestroyed() {
|
||||
return false;
|
||||
},
|
||||
isFocused() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
_popupWindow = {
|
||||
id: 2,
|
||||
webContents: _popupWebContents,
|
||||
isDestroyed() {
|
||||
return false;
|
||||
},
|
||||
isFocused() {
|
||||
return false;
|
||||
},
|
||||
isVisible() {
|
||||
return false;
|
||||
},
|
||||
close() {
|
||||
_popupWindow = null;
|
||||
_popupWebContents = null;
|
||||
},
|
||||
destroy() {
|
||||
_popupWindow = null;
|
||||
_popupWebContents = null;
|
||||
},
|
||||
on() {
|
||||
return _popupWindow;
|
||||
},
|
||||
once() {
|
||||
return _popupWindow;
|
||||
},
|
||||
removeListener() {
|
||||
return _popupWindow;
|
||||
},
|
||||
};
|
||||
return _popupWindow;
|
||||
}
|
||||
|
||||
export function unregisterPopupWindow() {
|
||||
_popupWindow = null;
|
||||
_popupWebContents = null;
|
||||
}
|
||||
|
||||
export const windowShim = {
|
||||
_current: () => currentWindow,
|
||||
|
||||
getFocusedWindow() {
|
||||
return currentWindow;
|
||||
},
|
||||
|
||||
getAllWindows() {
|
||||
const wins = [currentWindow];
|
||||
if (_popupWindow) {
|
||||
wins.push(_popupWindow);
|
||||
}
|
||||
|
||||
return wins;
|
||||
},
|
||||
|
||||
fromId(id) {
|
||||
if (id === currentWindow.id) {
|
||||
return currentWindow;
|
||||
}
|
||||
|
||||
if (_popupWindow && id === _popupWindow.id) {
|
||||
return _popupWindow;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
fromWebContents(wc) {
|
||||
if (wc === currentWebContents) {
|
||||
return currentWindow;
|
||||
}
|
||||
|
||||
if (_popupWebContents && wc === _popupWebContents) {
|
||||
return _popupWindow;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export const webContentsShim = {
|
||||
_current: () => currentWebContents,
|
||||
fromId(id) {
|
||||
if (id === currentWebContents.id) {
|
||||
return currentWebContents;
|
||||
}
|
||||
|
||||
if (_popupWebContents && id === _popupWebContents.id) {
|
||||
return _popupWebContents;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getAllWebContents() {
|
||||
const wcs = [currentWebContents];
|
||||
if (_popupWebContents) {
|
||||
wcs.push(_popupWebContents);
|
||||
}
|
||||
|
||||
return wcs;
|
||||
},
|
||||
};
|
||||
24
src/shims/electron/web-frame.js
Normal file
24
src/shims/electron/web-frame.js
Normal file
@@ -0,0 +1,24 @@
|
||||
let currentZoom = 0;
|
||||
|
||||
export const webFrame = {
|
||||
getZoomLevel() {
|
||||
return currentZoom;
|
||||
},
|
||||
|
||||
setZoomLevel(level) {
|
||||
currentZoom = level;
|
||||
// Approximate Electron's zoom behavior via CSS zoom
|
||||
// Electron zoom level 0 = 100%, each step is ~20%
|
||||
const scale = Math.pow(1.2, level);
|
||||
document.body.style.zoom = scale;
|
||||
},
|
||||
|
||||
getZoomFactor() {
|
||||
return Math.pow(1.2, currentZoom);
|
||||
},
|
||||
|
||||
setZoomFactor(factor) {
|
||||
currentZoom = Math.log(factor) / Math.log(1.2);
|
||||
document.body.style.zoom = factor;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user