mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
minor refactor, cleanup
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
// Shim for the btime native module (file birth time)
|
||||
// Obsidian wraps this in try/catch: try{this.btime=window.require("btime")}catch(e){}
|
||||
// Returning null causes graceful degradation - mtime is used instead.
|
||||
// Returning null causes graceful degradation. mtime is used instead.
|
||||
|
||||
export const btimeShim = null;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
// Shim for crypto.createHash
|
||||
// Obsidian uses createHash('SHA256') for signature verification (main process only)
|
||||
// and possibly for content hashing in the renderer.
|
||||
// Uses SubtleCrypto where possible.
|
||||
|
||||
export function createHash(algorithm) {
|
||||
const alg = algorithm.toUpperCase().replace('-', '');
|
||||
const subtleAlg = alg === 'SHA256' ? 'SHA-256' : alg === 'SHA1' ? 'SHA-1' : alg === 'SHA512' ? 'SHA-512' : alg;
|
||||
const alg = algorithm.toUpperCase().replace("-", "");
|
||||
const subtleAlg =
|
||||
alg === "SHA256"
|
||||
? "SHA-256"
|
||||
: alg === "SHA1"
|
||||
? "SHA-1"
|
||||
: alg === "SHA512"
|
||||
? "SHA-512"
|
||||
: alg;
|
||||
|
||||
let inputData = new Uint8Array(0);
|
||||
|
||||
return {
|
||||
update(data) {
|
||||
if (typeof data === 'string') {
|
||||
if (typeof data === "string") {
|
||||
data = new TextEncoder().encode(data);
|
||||
}
|
||||
// Concatenate
|
||||
const merged = new Uint8Array(inputData.length + data.length);
|
||||
merged.set(inputData);
|
||||
merged.set(data, inputData.length);
|
||||
@@ -22,27 +23,23 @@ export function createHash(algorithm) {
|
||||
return this;
|
||||
},
|
||||
|
||||
// Note: digest is sync in Node but we may need async.
|
||||
// For now provide sync hex/base64 via a simple JS implementation.
|
||||
// TODO: evaluate if any sync call sites exist; if not, make this async.
|
||||
digest(encoding) {
|
||||
// Fallback: simple sync hash (for SHA-256 only)
|
||||
// This is a placeholder - swap in a proper sync implementation if needed
|
||||
console.warn('[shim:crypto] createHash.digest - using placeholder');
|
||||
console.warn("[shim:crypto] createHash.digest - using placeholder");
|
||||
const hash = simpleHash(inputData);
|
||||
if (encoding === 'hex') return hash;
|
||||
if (encoding === 'base64') return btoa(hash);
|
||||
if (encoding === "hex") return hash;
|
||||
if (encoding === "base64") return btoa(hash);
|
||||
return hash;
|
||||
},
|
||||
|
||||
// Async alternative for contexts that can await
|
||||
async digestAsync(encoding) {
|
||||
const hashBuffer = await crypto.subtle.digest(subtleAlg, inputData);
|
||||
const hashArray = new Uint8Array(hashBuffer);
|
||||
if (encoding === 'hex') {
|
||||
return Array.from(hashArray).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
if (encoding === "hex") {
|
||||
return Array.from(hashArray)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
if (encoding === 'base64') {
|
||||
if (encoding === "base64") {
|
||||
return btoa(String.fromCharCode(...hashArray));
|
||||
}
|
||||
return hashArray;
|
||||
@@ -50,11 +47,10 @@ export function createHash(algorithm) {
|
||||
};
|
||||
}
|
||||
|
||||
// Very basic placeholder hash - not cryptographic, just for bootstrapping
|
||||
function simpleHash(data) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
hash = ((hash << 5) - hash + data[i]) | 0;
|
||||
}
|
||||
return Math.abs(hash).toString(16).padStart(8, '0');
|
||||
return Math.abs(hash).toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Crypto shim
|
||||
// Obsidian uses: scrypt, randomBytes, createHash
|
||||
|
||||
import { randomBytes } from './random-bytes.js';
|
||||
import { createHash } from './create-hash.js';
|
||||
import { scrypt } from './scrypt.js';
|
||||
import { randomBytes } from "./random-bytes.js";
|
||||
import { createHash } from "./create-hash.js";
|
||||
import { scrypt } from "./scrypt.js";
|
||||
|
||||
export const cryptoShim = {
|
||||
randomBytes,
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
// Shim for crypto.randomBytes
|
||||
// Uses Web Crypto API under the hood
|
||||
|
||||
export function randomBytes(size) {
|
||||
const buf = new Uint8Array(size);
|
||||
crypto.getRandomValues(buf);
|
||||
|
||||
// Add Buffer-like convenience methods
|
||||
buf.toString = function(encoding) {
|
||||
if (encoding === 'hex') {
|
||||
return Array.from(this).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
buf.toString = function (encoding) {
|
||||
if (encoding === "hex") {
|
||||
return Array.from(this)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
if (encoding === 'base64') {
|
||||
if (encoding === "base64") {
|
||||
return btoa(String.fromCharCode(...this));
|
||||
}
|
||||
return new TextDecoder().decode(this);
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
// Shim for crypto.scrypt
|
||||
// Delegates to window.scrypt which is already loaded by Obsidian's own scrypt.js
|
||||
|
||||
export function scrypt(password, salt, keylen, options, callback) {
|
||||
// Node signature: scrypt(password, salt, keylen, options, callback)
|
||||
// Obsidian's app.js checks for window.require("crypto") and uses it if available,
|
||||
// otherwise falls back to window.scrypt - so this shim just delegates to the latter.
|
||||
|
||||
if (typeof options === 'function') {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
@@ -16,14 +9,18 @@ export function scrypt(password, salt, keylen, options, callback) {
|
||||
const p = options?.p || 1;
|
||||
|
||||
if (window.scrypt && window.scrypt.scrypt) {
|
||||
// Use the browser scrypt library already loaded by Obsidian
|
||||
const pwBytes = typeof password === 'string' ? new TextEncoder().encode(password) : password;
|
||||
const saltBytes = typeof salt === 'string' ? new TextEncoder().encode(salt) : salt;
|
||||
const pwBytes =
|
||||
typeof password === "string"
|
||||
? new TextEncoder().encode(password)
|
||||
: password;
|
||||
const saltBytes =
|
||||
typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
|
||||
|
||||
window.scrypt.scrypt(pwBytes, saltBytes, N, r, p, keylen)
|
||||
window.scrypt
|
||||
.scrypt(pwBytes, saltBytes, N, r, p, keylen)
|
||||
.then((result) => callback(null, new Uint8Array(result)))
|
||||
.catch((err) => callback(err));
|
||||
} else {
|
||||
callback(new Error('scrypt not available'));
|
||||
callback(new Error("scrypt not available"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Electron module shim
|
||||
// Returned when Obsidian calls: window.require('electron')
|
||||
|
||||
import { ipcRenderer } from "./ipc-renderer.js";
|
||||
import { webFrame } from "./web-frame.js";
|
||||
import { remoteShim } from "./remote/index.js";
|
||||
@@ -10,14 +7,12 @@ export const electronShim = {
|
||||
webFrame,
|
||||
remote: remoteShim,
|
||||
|
||||
// electron.webUtils - used for drag/drop file path extraction (desktop only)
|
||||
webUtils: {
|
||||
getPathForFile(file) {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
|
||||
// electron.deprecate - used by Obsidian to mark deprecated APIs
|
||||
deprecate: {
|
||||
function(fn, name) {
|
||||
return fn;
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
// Shim for electron.ipcRenderer
|
||||
// Obsidian uses: .send(), .sendSync(), .on(), .once()
|
||||
//
|
||||
// sendSync channels discovered in app.js:
|
||||
// vault → {id, path} - critical for startup
|
||||
// version → string - app version
|
||||
// is-dev → boolean - dev mode flag
|
||||
// file-url → string - base URL prefix for vault assets
|
||||
// disable-update → boolean - whether updates are disabled
|
||||
// update → string - update status
|
||||
// disable-gpu → boolean - GPU acceleration toggle
|
||||
// frame → void - window frame style
|
||||
// set-icon → void - custom vault icon
|
||||
// get-icon → null|object - get custom vault icon
|
||||
// relaunch → void - restart app
|
||||
// starter → void - open vault chooser
|
||||
// help → void - open help
|
||||
// sandbox → void - open sandbox vault
|
||||
// copy-asar → boolean - install update
|
||||
|
||||
import { showVaultManager } from "../ui/vault-manager.js";
|
||||
|
||||
const listeners = new Map();
|
||||
|
||||
// Sync channel handlers - must return values synchronously
|
||||
const syncHandlers = {
|
||||
vault: () => window.__vaultConfig || { id: "default-vault", path: "/" },
|
||||
version: () => "1.8.9",
|
||||
@@ -51,7 +30,6 @@ const syncHandlers = {
|
||||
"copy-asar": () => false,
|
||||
"check-update": () => null,
|
||||
"vault-list": () => {
|
||||
// Starter expects an object keyed by ID: {id: {path, ts, name}}
|
||||
const result = {};
|
||||
for (const v of window.__vaultList || []) {
|
||||
result[v.id] = {
|
||||
@@ -66,14 +44,12 @@ const syncHandlers = {
|
||||
const id = (vaultPath || "").replace(/^\/+/, "");
|
||||
const vault = (window.__vaultList || []).find((v) => v.id === id);
|
||||
if (!vault && id) {
|
||||
// New vault created by starter - create it on the server
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/vault/create", false);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({ name: id }));
|
||||
if (xhr.status >= 400) return "Failed to create vault";
|
||||
}
|
||||
// Navigate - use parent if in iframe, otherwise current window
|
||||
const target = window.parent !== window ? window.parent : window;
|
||||
target.location.href = "/?vault=" + encodeURIComponent(id);
|
||||
return true;
|
||||
@@ -90,7 +66,6 @@ const syncHandlers = {
|
||||
return xhr.status < 400;
|
||||
},
|
||||
"vault-move": (oldPath, newPath) => {
|
||||
// Not supported in web context
|
||||
return "Moving vaults is not supported in the web version";
|
||||
},
|
||||
"vault-message": () => null,
|
||||
@@ -105,10 +80,6 @@ export const ipcRenderer = {
|
||||
send(channel, ...args) {
|
||||
console.log("[shim:ipcRenderer] send:", channel, args);
|
||||
|
||||
// context-menu: Obsidian sends this and waits (up to 1s) for a response.
|
||||
// In Electron, the main process returns spell-check info + edit flags.
|
||||
// We reply immediately with a response object so Obsidian proceeds to
|
||||
// build and show its HTML context menu without delay.
|
||||
if (channel === "context-menu") {
|
||||
queueMicrotask(() =>
|
||||
ipcRenderer._emit("context-menu", {
|
||||
@@ -163,7 +134,6 @@ export const ipcRenderer = {
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
// Internal: emit an event to registered listeners (used by ws bridge)
|
||||
_emit(channel, ...args) {
|
||||
const arr = listeners.get(channel);
|
||||
if (arr) {
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
// Shim for remote.app
|
||||
// Obsidian uses: getPath, getVersion, getName, quit, isPackaged, getLocale
|
||||
|
||||
export const appShim = {
|
||||
getPath(name) {
|
||||
// Return web-friendly paths; config lives server-side in the vault's .obsidian/ dir
|
||||
const paths = {
|
||||
userData: '/.obsidian',
|
||||
home: '/',
|
||||
documents: '/documents',
|
||||
desktop: '/desktop',
|
||||
temp: '/tmp',
|
||||
appData: '/.obsidian',
|
||||
userData: "/.obsidian",
|
||||
home: "/",
|
||||
documents: "/documents",
|
||||
desktop: "/desktop",
|
||||
temp: "/tmp",
|
||||
appData: "/.obsidian",
|
||||
};
|
||||
return paths[name] || '/';
|
||||
return paths[name] || "/";
|
||||
},
|
||||
|
||||
getVersion() {
|
||||
return '1.8.9';
|
||||
return "1.8.9";
|
||||
},
|
||||
|
||||
getName() {
|
||||
return 'Obsidian';
|
||||
return "Obsidian";
|
||||
},
|
||||
|
||||
getLocale() {
|
||||
return navigator.language || 'en-US';
|
||||
return navigator.language || "en-US";
|
||||
},
|
||||
|
||||
isPackaged: true,
|
||||
|
||||
quit() {
|
||||
console.log('[shim:app] quit (stub)');
|
||||
console.log("[shim:app] quit (stub)");
|
||||
},
|
||||
|
||||
relaunch() {
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
// Shim for remote.clipboard
|
||||
// Obsidian uses: readText, writeText, readImage, writeImage, readHTML, writeHTML
|
||||
|
||||
export const clipboardShim = {
|
||||
readText() {
|
||||
// navigator.clipboard.readText() is async; return empty for sync calls
|
||||
// TODO: maintain a local mirror updated via async reads
|
||||
return '';
|
||||
return "";
|
||||
},
|
||||
|
||||
writeText(text) {
|
||||
navigator.clipboard.writeText(text).catch((e) => {
|
||||
console.warn('[shim:clipboard] writeText failed:', e);
|
||||
console.warn("[shim:clipboard] writeText failed:", e);
|
||||
});
|
||||
},
|
||||
|
||||
readHTML() {
|
||||
return '';
|
||||
return "";
|
||||
},
|
||||
|
||||
writeHTML(html) {
|
||||
// TODO: use clipboard API with text/html mime type
|
||||
console.log('[shim:clipboard] writeHTML (stub)');
|
||||
console.log("[shim:clipboard] writeHTML (stub)");
|
||||
},
|
||||
|
||||
readImage() {
|
||||
// TODO: implement if needed
|
||||
return { isEmpty: () => true, toPNG: () => new Uint8Array(0) };
|
||||
},
|
||||
|
||||
writeImage(image) {
|
||||
console.log('[shim:clipboard] writeImage (stub)');
|
||||
console.log("[shim:clipboard] writeImage (stub)");
|
||||
},
|
||||
|
||||
has(format) {
|
||||
@@ -37,10 +31,10 @@ export const clipboardShim = {
|
||||
},
|
||||
|
||||
read(format) {
|
||||
return '';
|
||||
return "";
|
||||
},
|
||||
|
||||
clear() {
|
||||
navigator.clipboard.writeText('').catch(() => {});
|
||||
navigator.clipboard.writeText("").catch(() => {});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
// Shim for remote.dialog
|
||||
// Obsidian uses: showOpenDialog, showSaveDialog, showMessageBox, showErrorBox
|
||||
|
||||
export const dialogShim = {
|
||||
async showOpenDialog(browserWindow, options) {
|
||||
// TODO: implement custom modal UI with server-side file listing
|
||||
console.log('[shim:dialog] showOpenDialog (stub):', 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) {
|
||||
// TODO: implement custom modal UI
|
||||
console.log('[shim:dialog] showSaveDialog (stub):', options);
|
||||
// TODO: implement custom modal
|
||||
console.log("[shim:dialog] showSaveDialog (stub):", options);
|
||||
return { canceled: true, filePath: undefined };
|
||||
},
|
||||
|
||||
async showMessageBox(browserWindow, options) {
|
||||
// TODO: implement custom modal matching Electron's return format
|
||||
// For now, use browser confirm/alert as rough approximation
|
||||
if (typeof browserWindow === 'object' && !options) {
|
||||
if (typeof browserWindow === "object" && !options) {
|
||||
options = browserWindow;
|
||||
}
|
||||
console.log('[shim:dialog] showMessageBox:', options);
|
||||
console.log("[shim:dialog] showMessageBox:", options);
|
||||
|
||||
const message = options.message || '';
|
||||
const detail = options.detail || '';
|
||||
const buttons = options.buttons || ['OK'];
|
||||
const message = options.message || "";
|
||||
const detail = options.detail || "";
|
||||
const buttons = options.buttons || ["OK"];
|
||||
|
||||
// Simple fallback: use confirm for 2-button, alert for 1-button
|
||||
if (buttons.length <= 1) {
|
||||
alert(message + (detail ? '\n\n' + detail : ''));
|
||||
alert(message + (detail ? "\n\n" + detail : ""));
|
||||
return { response: 0, checkboxChecked: false };
|
||||
}
|
||||
|
||||
const result = confirm(message + (detail ? '\n\n' + detail : '') + '\n\n[OK] = "' + buttons[0] + '", [Cancel] = "' + buttons[1] + '"');
|
||||
const result = confirm(
|
||||
message +
|
||||
(detail ? "\n\n" + detail : "") +
|
||||
'\n\n[OK] = "' +
|
||||
buttons[0] +
|
||||
'", [Cancel] = "' +
|
||||
buttons[1] +
|
||||
'"',
|
||||
);
|
||||
return {
|
||||
response: result ? 0 : 1,
|
||||
checkboxChecked: false,
|
||||
@@ -40,7 +42,7 @@ export const dialogShim = {
|
||||
},
|
||||
|
||||
showErrorBox(title, content) {
|
||||
console.error('[shim:dialog] Error:', title, content);
|
||||
alert(title + '\n\n' + content);
|
||||
console.error("[shim:dialog] Error:", title, content);
|
||||
alert(title + "\n\n" + content);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// @electron/remote shim
|
||||
// Returned when Obsidian calls: window.require('@electron/remote')
|
||||
|
||||
import { clipboardShim } from "./clipboard.js";
|
||||
import { shellShim } from "./shell.js";
|
||||
import { dialogShim } from "./dialog.js";
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Shim for remote.Menu and remote.MenuItem
|
||||
// Obsidian uses: Menu.buildFromTemplate, Menu.popup, Menu.setApplicationMenu
|
||||
|
||||
export class menuShim {
|
||||
constructor() {
|
||||
this.items = [];
|
||||
@@ -8,13 +5,12 @@ export class menuShim {
|
||||
|
||||
static buildFromTemplate(template) {
|
||||
const menu = new menuShim();
|
||||
menu.items = (template || []).map(item => new menuItemShim(item));
|
||||
menu.items = (template || []).map((item) => new menuItemShim(item));
|
||||
return menu;
|
||||
}
|
||||
|
||||
static setApplicationMenu(menu) {
|
||||
// No native menu bar in browser - no-op
|
||||
console.log('[shim:Menu] setApplicationMenu (stub)');
|
||||
console.log("[shim:Menu] setApplicationMenu (stub)");
|
||||
}
|
||||
|
||||
static getApplicationMenu() {
|
||||
@@ -22,8 +18,8 @@ export class menuShim {
|
||||
}
|
||||
|
||||
popup(options) {
|
||||
// TODO: implement custom HTML context menu rendered at mouse position
|
||||
console.log('[shim:Menu] popup (stub)', options);
|
||||
// TODO: render custom HTML context menu at mouse position
|
||||
console.log("[shim:Menu] popup (stub)", options);
|
||||
}
|
||||
|
||||
append(menuItem) {
|
||||
@@ -41,19 +37,19 @@ export class menuShim {
|
||||
|
||||
export class menuItemShim {
|
||||
constructor(options = {}) {
|
||||
this.label = options.label || '';
|
||||
this.type = options.type || 'normal';
|
||||
this.label = options.label || "";
|
||||
this.type = options.type || "normal";
|
||||
this.click = options.click || null;
|
||||
this.role = options.role || null;
|
||||
this.accelerator = options.accelerator || '';
|
||||
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 : []
|
||||
Array.isArray(options.submenu) ? options.submenu : [],
|
||||
)
|
||||
: null;
|
||||
this.id = options.id || '';
|
||||
this.id = options.id || "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Shim for remote.nativeImage
|
||||
// Minimal stub - Obsidian's renderer-side usage is limited
|
||||
|
||||
export const nativeImageShim = {
|
||||
createFromBuffer(buffer) {
|
||||
return {
|
||||
@@ -8,7 +5,7 @@ export const nativeImageShim = {
|
||||
getSize: () => ({ width: 0, height: 0 }),
|
||||
toPNG: () => buffer || new Uint8Array(0),
|
||||
toJPEG: (quality) => buffer || new Uint8Array(0),
|
||||
toDataURL: () => '',
|
||||
toDataURL: () => "",
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
// Shim for remote.Notification
|
||||
// Maps to browser Notification API
|
||||
|
||||
export class notificationShim {
|
||||
constructor(options = {}) {
|
||||
this.title = options.title || '';
|
||||
this.body = options.body || '';
|
||||
this.title = options.title || "";
|
||||
this.body = options.body || "";
|
||||
this.silent = options.silent || false;
|
||||
this._handlers = {};
|
||||
}
|
||||
|
||||
show() {
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
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') {
|
||||
} 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 });
|
||||
if (perm === "granted") {
|
||||
new Notification(this.title, {
|
||||
body: this.body,
|
||||
silent: this.silent,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -29,6 +32,6 @@ export class notificationShim {
|
||||
}
|
||||
|
||||
static isSupported() {
|
||||
return 'Notification' in window;
|
||||
return "Notification" in window;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
// Shim for remote.screen
|
||||
// Obsidian uses screen for display/monitor info
|
||||
|
||||
export const screenShim = {
|
||||
getPrimaryDisplay() {
|
||||
return {
|
||||
workAreaSize: { width: window.screen.availWidth, height: window.screen.availHeight },
|
||||
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 },
|
||||
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,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Shim for remote.session
|
||||
// Mostly no-op; Obsidian's use is minimal
|
||||
|
||||
export const sessionShim = {
|
||||
defaultSession: {
|
||||
clearCache() {
|
||||
@@ -12,7 +9,9 @@ export const sessionShim = {
|
||||
},
|
||||
|
||||
setSpellCheckerLanguages(langs) {},
|
||||
getSpellCheckerLanguages() { return []; },
|
||||
getSpellCheckerLanguages() {
|
||||
return [];
|
||||
},
|
||||
|
||||
on() {},
|
||||
once() {},
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
// Shim for remote.shell
|
||||
// Obsidian uses: openExternal, openPath, showItemInFolder
|
||||
|
||||
export const shellShim = {
|
||||
openExternal(url) {
|
||||
window.open(url, '_blank');
|
||||
window.open(url, "_blank");
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
openPath(filePath) {
|
||||
// TODO: could trigger a server-side download or preview
|
||||
console.log('[shim:shell] openPath (stub):', filePath);
|
||||
return Promise.resolve('');
|
||||
console.log("[shim:shell] openPath (stub):", filePath);
|
||||
return Promise.resolve("");
|
||||
},
|
||||
|
||||
showItemInFolder(filePath) {
|
||||
// No OS file manager in browser context
|
||||
console.log('[shim:shell] showItemInFolder (stub):', filePath);
|
||||
console.log("[shim:shell] showItemInFolder (stub):", filePath);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Shim for remote.systemPreferences
|
||||
// No-op with safe defaults
|
||||
|
||||
export const systemPreferencesShim = {
|
||||
getAccentColor() {
|
||||
return '0078d4'; // Default Windows accent blue
|
||||
return "0078d4"; // Default Windows accent blue
|
||||
},
|
||||
|
||||
isAeroGlassEnabled() {
|
||||
@@ -11,7 +8,7 @@ export const systemPreferencesShim = {
|
||||
},
|
||||
|
||||
getMediaAccessStatus(mediaType) {
|
||||
return 'granted';
|
||||
return "granted";
|
||||
},
|
||||
|
||||
askForMediaAccess(mediaType) {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Shim for remote.nativeTheme
|
||||
// Obsidian uses: shouldUseDarkColors, on('updated', cb)
|
||||
|
||||
const listeners = [];
|
||||
|
||||
const darkQuery = typeof window !== 'undefined'
|
||||
? window.matchMedia('(prefers-color-scheme: dark)')
|
||||
: null;
|
||||
const darkQuery =
|
||||
typeof window !== "undefined"
|
||||
? window.matchMedia("(prefers-color-scheme: dark)")
|
||||
: null;
|
||||
|
||||
if (darkQuery?.addEventListener) {
|
||||
darkQuery.addEventListener('change', () => {
|
||||
darkQuery.addEventListener("change", () => {
|
||||
for (const fn of listeners) {
|
||||
fn();
|
||||
}
|
||||
@@ -21,7 +19,7 @@ export const themeShim = {
|
||||
},
|
||||
|
||||
get themeSource() {
|
||||
return 'system';
|
||||
return "system";
|
||||
},
|
||||
|
||||
set themeSource(val) {
|
||||
@@ -29,14 +27,14 @@ export const themeShim = {
|
||||
},
|
||||
|
||||
on(event, callback) {
|
||||
if (event === 'updated') {
|
||||
if (event === "updated") {
|
||||
listeners.push(callback);
|
||||
}
|
||||
return themeShim;
|
||||
},
|
||||
|
||||
once(event, callback) {
|
||||
if (event === 'updated') {
|
||||
if (event === "updated") {
|
||||
const wrapped = () => {
|
||||
const idx = listeners.indexOf(wrapped);
|
||||
if (idx >= 0) listeners.splice(idx, 1);
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
// Shim for remote.getCurrentWindow() / remote.BrowserWindow
|
||||
// Obsidian uses: isMaximized, isMinimized, isFullScreen, minimize, maximize,
|
||||
// unmaximize, close, setTitle, setAlwaysOnTop, isAlwaysOnTop,
|
||||
// getBounds, setBounds, show, focus, setFullScreen, etc.
|
||||
|
||||
const currentWindowState = {
|
||||
title: "Obsidian",
|
||||
isMaximized: false,
|
||||
@@ -80,7 +75,6 @@ const currentWindow = {
|
||||
},
|
||||
|
||||
setBounds(bounds) {
|
||||
// Cannot resize browser window from JS
|
||||
console.log("[shim:window] setBounds (stub):", bounds);
|
||||
},
|
||||
|
||||
@@ -113,7 +107,6 @@ const currentWindow = {
|
||||
},
|
||||
|
||||
on(event, handler) {
|
||||
// Map some Electron window events to browser equivalents
|
||||
if (event === "focus") window.addEventListener("focus", handler);
|
||||
else if (event === "blur") window.addEventListener("blur", handler);
|
||||
else if (event === "resize") window.addEventListener("resize", handler);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Shim for electron.webFrame
|
||||
// Obsidian uses: getZoomLevel(), setZoomLevel()
|
||||
|
||||
let currentZoom = 0;
|
||||
|
||||
export const webFrame = {
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
// Filesystem shim - the core piece
|
||||
// Returned for both require('original-fs') and require('fs')
|
||||
//
|
||||
// Strategy: metadata cache + on-demand content fetch + write-through
|
||||
// Server sync mechanism (REST vs WebSocket) is TBD - abstracted behind
|
||||
// the transport layer in ./transport.js
|
||||
|
||||
import { MetadataCache } from './metadata-cache.js';
|
||||
import { ContentCache } from './content-cache.js';
|
||||
import { transport } from './transport.js';
|
||||
import { createFsPromises } from './promises.js';
|
||||
import { createFsSync } from './sync.js';
|
||||
import { createFsWatch } from './watch.js';
|
||||
import { constants } from './constants.js';
|
||||
import { MetadataCache } from "./metadata-cache.js";
|
||||
import { ContentCache } from "./content-cache.js";
|
||||
import { transport } from "./transport.js";
|
||||
import { createFsPromises } from "./promises.js";
|
||||
import { createFsSync } from "./sync.js";
|
||||
import { createFsWatch } from "./watch.js";
|
||||
import { constants } from "./constants.js";
|
||||
|
||||
const metadataCache = new MetadataCache();
|
||||
const contentCache = new ContentCache();
|
||||
@@ -21,10 +14,8 @@ const fsSync = createFsSync(metadataCache, contentCache, transport);
|
||||
const fsWatch = createFsWatch(transport);
|
||||
|
||||
export const fsShim = {
|
||||
// Async promise-based API (this.fsPromises = this.fs.promises)
|
||||
promises: fsPromises,
|
||||
|
||||
// Sync methods
|
||||
existsSync: fsSync.existsSync,
|
||||
readFileSync: fsSync.readFileSync,
|
||||
writeFileSync: fsSync.writeFileSync,
|
||||
@@ -33,17 +24,12 @@ export const fsShim = {
|
||||
statSync: fsSync.statSync,
|
||||
readdirSync: fsSync.readdirSync,
|
||||
|
||||
// Watch
|
||||
watch: fsWatch.watch,
|
||||
|
||||
// Constants
|
||||
constants,
|
||||
|
||||
// Internal: for initialization
|
||||
_metadataCache: metadataCache,
|
||||
_contentCache: contentCache,
|
||||
|
||||
// Initialize the caches by fetching the full tree from server
|
||||
async _init(basePath) {
|
||||
const tree = await transport.fetchTree(basePath);
|
||||
metadataCache.populate(tree);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// Async fs.promises implementation
|
||||
// Maps to transport layer (REST/WebSocket/hybrid - TBD)
|
||||
|
||||
export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
return {
|
||||
async stat(path) {
|
||||
// Try cache first, fall back to server
|
||||
const cached = metadataCache.toStat(path);
|
||||
if (cached) return cached;
|
||||
|
||||
@@ -14,17 +10,15 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
},
|
||||
|
||||
async lstat(path) {
|
||||
// No symlinks in our context - same as stat
|
||||
// No symlinks in our context
|
||||
return this.stat(path);
|
||||
},
|
||||
|
||||
async readdir(path) {
|
||||
// If metadata cache knows this is a file, return empty (ENOTDIR)
|
||||
const meta = metadataCache.get(path);
|
||||
if (meta && meta.type === "file") {
|
||||
return [];
|
||||
}
|
||||
// If path not in cache at all (and not root), it doesn't exist
|
||||
if (!meta && path && path !== "/" && path !== ".") {
|
||||
const e = new Error(
|
||||
`ENOENT: no such file or directory, scandir '${path}'`,
|
||||
@@ -32,7 +26,6 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
e.code = "ENOENT";
|
||||
throw e;
|
||||
}
|
||||
// Serve from metadata cache
|
||||
const entries = metadataCache.readdir(path);
|
||||
return entries.map((e) => e.name);
|
||||
},
|
||||
@@ -41,14 +34,12 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
if (typeof encoding === "object") encoding = encoding?.encoding;
|
||||
const wantText = encoding === "utf8" || encoding === "utf-8";
|
||||
|
||||
// Short-circuit: reading a directory is an error
|
||||
const meta = metadataCache.get(path);
|
||||
if (meta && meta.type === "directory") {
|
||||
const e = new Error("EISDIR: illegal operation on a directory, read");
|
||||
e.code = "EISDIR";
|
||||
throw e;
|
||||
}
|
||||
// Short-circuit: file not in metadata cache → doesn't exist
|
||||
if (!meta && path) {
|
||||
const e = new Error(
|
||||
`ENOENT: no such file or directory, open '${path}'`,
|
||||
@@ -57,7 +48,6 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Check content cache
|
||||
const cached = contentCache.get(path);
|
||||
if (cached !== null) {
|
||||
if (wantText) {
|
||||
@@ -65,14 +55,13 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
? cached
|
||||
: new TextDecoder().decode(cached);
|
||||
}
|
||||
// Binary mode: ensure we return a proper Uint8Array with .buffer
|
||||
// binary. ensure we return a proper Uint8Array with .buffer
|
||||
if (typeof cached === "string") {
|
||||
return new TextEncoder().encode(cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fetch from server
|
||||
const data = await transport.readFile(path, encoding);
|
||||
contentCache.set(path, data);
|
||||
return data;
|
||||
@@ -81,7 +70,6 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
async writeFile(path, data, encoding) {
|
||||
if (typeof encoding === "object") encoding = encoding?.encoding;
|
||||
|
||||
// Update caches optimistically
|
||||
contentCache.set(path, data);
|
||||
const size =
|
||||
typeof data === "string" ? data.length : data.byteLength || 0;
|
||||
@@ -92,9 +80,7 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
ctime: metadataCache.get(path)?.ctime || Date.now(),
|
||||
});
|
||||
|
||||
// Send to server
|
||||
const result = await transport.writeFile(path, data, encoding);
|
||||
// Update metadata with server-confirmed values
|
||||
if (result.mtime) {
|
||||
metadataCache.set(path, {
|
||||
type: "file",
|
||||
@@ -108,7 +94,7 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
async appendFile(path, data, encoding) {
|
||||
contentCache.invalidate(path);
|
||||
await transport.appendFile(path, data);
|
||||
// Refresh metadata
|
||||
|
||||
const meta = await transport.stat(path);
|
||||
metadataCache.set(path, meta);
|
||||
},
|
||||
@@ -120,13 +106,11 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
},
|
||||
|
||||
async rename(oldPath, newPath) {
|
||||
// Move content cache entry
|
||||
const content = contentCache.get(oldPath);
|
||||
if (content !== null) {
|
||||
contentCache.set(newPath, content);
|
||||
contentCache.delete(oldPath);
|
||||
}
|
||||
// Move metadata
|
||||
metadataCache.rename(oldPath, newPath);
|
||||
|
||||
await transport.rename(oldPath, newPath);
|
||||
@@ -154,7 +138,6 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
|
||||
async copyFile(src, dest) {
|
||||
await transport.copyFile(src, dest);
|
||||
// Refresh metadata for dest
|
||||
const meta = await transport.stat(dest);
|
||||
metadataCache.set(dest, meta);
|
||||
},
|
||||
@@ -169,7 +152,6 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
||||
},
|
||||
|
||||
async realpath(path) {
|
||||
// Empty path = vault root, return the vault base path
|
||||
if (!path || path === "/" || path === ".") return "/";
|
||||
return transport.realpath(path);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Synchronous fs method implementations
|
||||
// Served from caches where possible, sync XHR fallback for uncached content.
|
||||
|
||||
export function createFsSync(metadataCache, contentCache, transport) {
|
||||
return {
|
||||
existsSync(path) {
|
||||
@@ -32,7 +29,6 @@ export function createFsSync(metadataCache, contentCache, transport) {
|
||||
readFileSync(path, encoding) {
|
||||
if (typeof encoding === "object") encoding = encoding?.encoding;
|
||||
|
||||
// Short-circuit: reading a directory is an error
|
||||
const meta = metadataCache.get(path);
|
||||
if (meta && meta.type === "directory") {
|
||||
const e = new Error("EISDIR: illegal operation on a directory, read");
|
||||
@@ -40,7 +36,6 @@ export function createFsSync(metadataCache, contentCache, transport) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Try content cache first
|
||||
const cached = contentCache.get(path);
|
||||
if (cached !== null) {
|
||||
if (encoding === "utf8" || encoding === "utf-8") {
|
||||
@@ -51,7 +46,6 @@ export function createFsSync(metadataCache, contentCache, transport) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fallback: synchronous XHR
|
||||
console.warn("[shim:fs] readFileSync cache miss, using sync XHR:", path);
|
||||
const data = transport.readFileSync(path, encoding);
|
||||
contentCache.set(path, data);
|
||||
@@ -61,7 +55,6 @@ export function createFsSync(metadataCache, contentCache, transport) {
|
||||
writeFileSync(path, data, encoding) {
|
||||
if (typeof encoding === "object") encoding = encoding?.encoding;
|
||||
|
||||
// Write to cache immediately (sync return)
|
||||
contentCache.set(path, data);
|
||||
const size =
|
||||
typeof data === "string" ? data.length : data.byteLength || 0;
|
||||
@@ -86,7 +79,7 @@ export function createFsSync(metadataCache, contentCache, transport) {
|
||||
contentCache.delete(path);
|
||||
metadataCache.delete(path);
|
||||
|
||||
// Fire-and-forget - suppress ENOENT (file already gone, e.g. .OBSIDIANTEST race)
|
||||
// Fire-and-forget - suppress ENOENT (file already gone)
|
||||
transport.unlink(path).catch((e) => {
|
||||
if (e.code !== "ENOENT") {
|
||||
console.error(
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
// Transport abstraction layer
|
||||
// Decouples the fs shim from the sync mechanism (REST, WebSocket, or hybrid).
|
||||
// Currently implements a REST-based transport. This can be swapped or extended
|
||||
// once the sync strategy is finalized.
|
||||
|
||||
const API_BASE = "/api/fs";
|
||||
|
||||
// Strip leading slashes from paths before sending to server
|
||||
function normPath(p) {
|
||||
return (p || "").replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
// Convert a Uint8Array to base64 without blowing the stack
|
||||
function uint8ToBase64(bytes) {
|
||||
let binary = "";
|
||||
const chunk = 8192;
|
||||
@@ -56,12 +49,10 @@ async function requestJson(method, endpoint, params = {}) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// Synchronous XHR - used only as fallback for sync fs calls on uncached content.
|
||||
// Blocking but functional. Should be rare after pre-warming.
|
||||
function requestSync(method, endpoint, params = {}) {
|
||||
const url = new URL(API_BASE + endpoint, window.location.origin);
|
||||
|
||||
if (method === "GET") {
|
||||
if (method === "GET" || method === "DELETE") {
|
||||
if (vaultId()) url.searchParams.set("vault", vaultId());
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
url.searchParams.set(key, val);
|
||||
@@ -71,7 +62,7 @@ function requestSync(method, endpoint, params = {}) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url.toString(), false); // synchronous
|
||||
|
||||
if (method !== "GET") {
|
||||
if (method !== "GET" && method !== "DELETE") {
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({ vault: vaultId(), ...params }));
|
||||
} else {
|
||||
@@ -95,8 +86,6 @@ function requestSync(method, endpoint, params = {}) {
|
||||
}
|
||||
|
||||
export const transport = {
|
||||
// --- Async methods (used by fs.promises) ---
|
||||
|
||||
async fetchTree(basePath) {
|
||||
return requestJson("GET", "/tree", basePath ? { path: basePath } : {});
|
||||
},
|
||||
@@ -190,8 +179,6 @@ export const transport = {
|
||||
});
|
||||
},
|
||||
|
||||
// --- Sync methods (fallback) ---
|
||||
|
||||
readFileSync(path, encoding) {
|
||||
const xhr = requestSync("GET", "/readFile", {
|
||||
path: normPath(path),
|
||||
@@ -200,7 +187,6 @@ export const transport = {
|
||||
if (encoding === "utf8" || encoding === "utf-8") {
|
||||
return xhr.responseText;
|
||||
}
|
||||
// Binary: return as Uint8Array
|
||||
const binary = xhr.responseText;
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
// File watching shim
|
||||
// Translates fs.watch() calls into WebSocket subscriptions.
|
||||
// The server pushes file-change events; this module dispatches them
|
||||
// to registered watch listeners.
|
||||
|
||||
export function createFsWatch(transport) {
|
||||
const watchers = new Map(); // path -> Set<listener>
|
||||
|
||||
return {
|
||||
watch(path, options, listener) {
|
||||
if (typeof options === 'function') {
|
||||
if (typeof options === "function") {
|
||||
listener = options;
|
||||
options = {};
|
||||
}
|
||||
@@ -32,22 +27,28 @@ export function createFsWatch(transport) {
|
||||
}
|
||||
}
|
||||
},
|
||||
on() { return this; },
|
||||
once() { return this; },
|
||||
removeListener() { return this; },
|
||||
on() {
|
||||
return this;
|
||||
},
|
||||
once() {
|
||||
return this;
|
||||
},
|
||||
removeListener() {
|
||||
return this;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Internal: called when transport receives a file-change event
|
||||
_dispatch(eventType, filePath) {
|
||||
for (const [watchPath, listeners] of watchers) {
|
||||
if (filePath === watchPath || filePath.startsWith(watchPath + '/')) {
|
||||
if (filePath === watchPath || filePath.startsWith(watchPath + "/")) {
|
||||
const relativeName = filePath.slice(watchPath.length + 1) || filePath;
|
||||
for (const fn of listeners) {
|
||||
try {
|
||||
fn(eventType, relativeName);
|
||||
} catch (e) {
|
||||
console.error('[shim:fs:watch] Listener error:', e);
|
||||
console.error("[shim:fs:watch] Listener error:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
// shim-loader.js
|
||||
// Loaded before app.js. Defines window.require() and window.process
|
||||
// to intercept all Electron/Node API calls from Obsidian's renderer code.
|
||||
|
||||
import { electronShim } from "./electron/index.js";
|
||||
import { remoteShim } from "./electron/remote/index.js";
|
||||
import { fsShim } from "./fs/index.js";
|
||||
import { pathShim } from "./path.js";
|
||||
import { urlShim } from "./url.js";
|
||||
import { cryptoShim } from "./crypto/index.js";
|
||||
import { btimeShim } from "./btime.js";
|
||||
import { processShim } from "./process.js";
|
||||
|
||||
// Debug mode: wrap shims in Proxy to log all property accesses
|
||||
const DEBUG = true;
|
||||
const _accessLog = new Map(); // "module.property" -> count
|
||||
|
||||
@@ -28,7 +22,7 @@ function wrapWithProxy(obj, name) {
|
||||
const key = `${name}.${prop}`;
|
||||
_accessLog.set(key, (_accessLog.get(key) || 0) + 1);
|
||||
if (!(prop in target)) {
|
||||
console.warn(`[shim:MISS] ${key} - property not found on shim`);
|
||||
console.warn(`[shim:MISS] ${key} - property not found on shim`);
|
||||
}
|
||||
}
|
||||
return target[prop];
|
||||
@@ -36,7 +30,6 @@ function wrapWithProxy(obj, name) {
|
||||
});
|
||||
}
|
||||
|
||||
// Expose access log for debugging in console: window.__shimLog()
|
||||
window.__shimLog = function () {
|
||||
const sorted = [..._accessLog.entries()].sort((a, b) => b[1] - a[1]);
|
||||
console.table(sorted.map(([k, v]) => ({ api: k, calls: v })));
|
||||
@@ -60,7 +53,6 @@ const rawRegistry = {
|
||||
path: pathShim,
|
||||
url: urlShim,
|
||||
crypto: cryptoShim,
|
||||
btime: btimeShim,
|
||||
};
|
||||
|
||||
const shimRegistry = {};
|
||||
@@ -68,7 +60,6 @@ for (const [name, shim] of Object.entries(rawRegistry)) {
|
||||
shimRegistry[name] = wrapWithProxy(shim, name);
|
||||
}
|
||||
|
||||
// Modules that should throw on require (native modules that don't exist in browser)
|
||||
const throwOnRequire = new Set(["btime", "get-fonts", "vibrancy-win"]);
|
||||
|
||||
window.require = function (moduleName) {
|
||||
@@ -84,9 +75,7 @@ window.require = function (moduleName) {
|
||||
|
||||
window.process = processShim;
|
||||
|
||||
// Provide a global Buffer if needed
|
||||
if (typeof window.Buffer === "undefined") {
|
||||
// TODO: evaluate if a full Buffer polyfill is needed or if Uint8Array suffices
|
||||
window.Buffer = {
|
||||
from: function (data, encoding) {
|
||||
if (typeof data === "string") {
|
||||
@@ -113,22 +102,10 @@ if (typeof window.Buffer === "undefined") {
|
||||
};
|
||||
}
|
||||
|
||||
// Prevent app.js from closing the window (browser blocks this anyway, but suppress the error)
|
||||
// In an iframe (starter modal), close the modal overlay instead.
|
||||
const _origClose = window.close;
|
||||
window.close = function () {
|
||||
if (window.parent !== window) {
|
||||
const modal = window.parent.document.getElementById("ignis-starter-modal");
|
||||
if (modal) modal.remove();
|
||||
return;
|
||||
}
|
||||
console.log("[obsidian-bridge] window.close() blocked");
|
||||
};
|
||||
|
||||
// Suppress the browser's native context menu without breaking Obsidian's.
|
||||
// Problem: preventDefault() blocks the browser menu but also sets
|
||||
// event.defaultPrevented=true, which Obsidian checks to bail out.
|
||||
// Solution: call preventDefault() then shadow defaultPrevented to return false.
|
||||
window.addEventListener(
|
||||
"contextmenu",
|
||||
(e) => {
|
||||
@@ -138,11 +115,9 @@ window.addEventListener(
|
||||
true,
|
||||
);
|
||||
|
||||
// Read vault ID from URL query param (?vault=my-notes)
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
window.__currentVaultId = _urlParams.get("vault") || "";
|
||||
|
||||
// Fetch vault config from server synchronously (before metadata cache)
|
||||
(function initVaultConfig() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
@@ -165,7 +140,6 @@ window.__currentVaultId = _urlParams.get("vault") || "";
|
||||
}
|
||||
})();
|
||||
|
||||
// Fetch vault list for IPC handlers
|
||||
(function initVaultList() {
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
@@ -179,8 +153,6 @@ window.__currentVaultId = _urlParams.get("vault") || "";
|
||||
}
|
||||
})();
|
||||
|
||||
// Pre-populate fs metadata cache synchronously before app.js runs.
|
||||
// This ensures existsSync() works for the vault path during startup.
|
||||
(function initMetadataCache() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Path shim - delegates to path-browserify (bundled via esbuild alias)
|
||||
// Path shim. delegates to path-browserify (bundled via esbuild alias)
|
||||
// Configured for posix mode since vault paths are normalized to forward slashes.
|
||||
|
||||
import pathBrowserify from "path";
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
// Shim for window.process
|
||||
// Obsidian checks process.platform, process.versions.electron, etc.
|
||||
|
||||
export const processShim = {
|
||||
platform: 'linux',
|
||||
platform: "linux",
|
||||
versions: {
|
||||
electron: '28.0.0',
|
||||
node: '18.18.0',
|
||||
chrome: '120.0.0.0',
|
||||
electron: "28.0.0",
|
||||
node: "18.18.0",
|
||||
chrome: "120.0.0.0",
|
||||
},
|
||||
env: {},
|
||||
cwd: () => '/',
|
||||
cwd: () => "/",
|
||||
nextTick: (fn, ...args) => setTimeout(() => fn(...args), 0),
|
||||
argv: [],
|
||||
type: 'renderer',
|
||||
resourcesPath: '/',
|
||||
type: "renderer",
|
||||
resourcesPath: "/",
|
||||
stdout: { write: (s) => console.log(s) },
|
||||
stderr: { write: (s) => console.error(s) },
|
||||
on: () => {},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Custom vault manager modal - vanilla JS (will migrate to Svelte later)
|
||||
// Custom vault manager modal. will migrate to Svelte later
|
||||
// Shows list of vaults, create new, delete, switch.
|
||||
|
||||
export function showVaultManager() {
|
||||
@@ -74,11 +74,7 @@ export function showVaultManager() {
|
||||
"color:var(--text-muted);border-radius:4px;padding:2px 8px;font-size:12px;cursor:pointer;";
|
||||
del.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (
|
||||
!confirm(
|
||||
'Delete vault "' + v.name + '"? This removes all files.',
|
||||
)
|
||||
)
|
||||
if (!confirm('Delete vault "' + v.name + '"? This removes all files.'))
|
||||
return;
|
||||
const xhr2 = new XMLHttpRequest();
|
||||
xhr2.open(
|
||||
|
||||
13
shims/url.js
13
shims/url.js
@@ -1,22 +1,19 @@
|
||||
// URL shim
|
||||
// Obsidian uses: pathToFileURL, fileURLToPath, URL, URLSearchParams
|
||||
|
||||
export const urlShim = {
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
|
||||
pathToFileURL(p) {
|
||||
// Return an object with .href matching Node's url.pathToFileURL behavior
|
||||
const encoded = encodeURI(p.replace(/\\/g, '/'));
|
||||
const href = 'file:///' + encoded.replace(/^\/+/, '');
|
||||
const encoded = encodeURI(p.replace(/\\/g, "/"));
|
||||
const href = "file:///" + encoded.replace(/^\/+/, "");
|
||||
return { href, toString: () => href };
|
||||
},
|
||||
|
||||
fileURLToPath(url) {
|
||||
let str = typeof url === 'string' ? url : url.href || url.toString();
|
||||
if (str.startsWith('file:///')) {
|
||||
let str = typeof url === "string" ? url : url.href || url.toString();
|
||||
if (str.startsWith("file:///")) {
|
||||
str = str.slice(8);
|
||||
} else if (str.startsWith('file://')) {
|
||||
} else if (str.startsWith("file://")) {
|
||||
str = str.slice(7);
|
||||
}
|
||||
return decodeURI(str);
|
||||
|
||||
Reference in New Issue
Block a user