mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
implement shims
This commit is contained in:
47
shims/electron/remote/app.js
Normal file
47
shims/electron/remote/app.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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',
|
||||
};
|
||||
return paths[name] || '/';
|
||||
},
|
||||
|
||||
getVersion() {
|
||||
return '1.8.9';
|
||||
},
|
||||
|
||||
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() {},
|
||||
};
|
||||
46
shims/electron/remote/clipboard.js
Normal file
46
shims/electron/remote/clipboard.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 '';
|
||||
},
|
||||
|
||||
writeText(text) {
|
||||
navigator.clipboard.writeText(text).catch((e) => {
|
||||
console.warn('[shim:clipboard] writeText failed:', e);
|
||||
});
|
||||
},
|
||||
|
||||
readHTML() {
|
||||
return '';
|
||||
},
|
||||
|
||||
writeHTML(html) {
|
||||
// TODO: use clipboard API with text/html mime type
|
||||
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)');
|
||||
},
|
||||
|
||||
has(format) {
|
||||
return false;
|
||||
},
|
||||
|
||||
read(format) {
|
||||
return '';
|
||||
},
|
||||
|
||||
clear() {
|
||||
navigator.clipboard.writeText('').catch(() => {});
|
||||
},
|
||||
};
|
||||
46
shims/electron/remote/dialog.js
Normal file
46
shims/electron/remote/dialog.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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);
|
||||
return { canceled: true, filePaths: [] };
|
||||
},
|
||||
|
||||
async showSaveDialog(browserWindow, options) {
|
||||
// TODO: implement custom modal UI
|
||||
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) {
|
||||
options = browserWindow;
|
||||
}
|
||||
console.log('[shim:dialog] showMessageBox:', options);
|
||||
|
||||
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 : ''));
|
||||
return { response: 0, checkboxChecked: false };
|
||||
}
|
||||
|
||||
const result = confirm(message + (detail ? '\n\n' + detail : '') + '\n\n[OK] = "' + buttons[0] + '", [Cancel] = "' + buttons[1] + '"');
|
||||
return {
|
||||
response: result ? 0 : 1,
|
||||
checkboxChecked: false,
|
||||
};
|
||||
},
|
||||
|
||||
showErrorBox(title, content) {
|
||||
console.error('[shim:dialog] Error:', title, content);
|
||||
alert(title + '\n\n' + content);
|
||||
},
|
||||
};
|
||||
39
shims/electron/remote/index.js
Normal file
39
shims/electron/remote/index.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// @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';
|
||||
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,
|
||||
|
||||
getCurrentWindow() {
|
||||
return windowShim._current();
|
||||
},
|
||||
|
||||
getCurrentWebContents() {
|
||||
return webContentsShim._current();
|
||||
},
|
||||
};
|
||||
59
shims/electron/remote/menu.js
Normal file
59
shims/electron/remote/menu.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// Shim for remote.Menu and remote.MenuItem
|
||||
// Obsidian uses: Menu.buildFromTemplate, Menu.popup, Menu.setApplicationMenu
|
||||
|
||||
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) {
|
||||
// No native menu bar in browser - no-op
|
||||
console.log('[shim:Menu] setApplicationMenu (stub)');
|
||||
}
|
||||
|
||||
static getApplicationMenu() {
|
||||
return null;
|
||||
}
|
||||
|
||||
popup(options) {
|
||||
// TODO: implement custom HTML context menu rendered at mouse position
|
||||
console.log('[shim:Menu] popup (stub)', options);
|
||||
}
|
||||
|
||||
append(menuItem) {
|
||||
this.items.push(menuItem);
|
||||
}
|
||||
|
||||
insert(pos, menuItem) {
|
||||
this.items.splice(pos, 0, menuItem);
|
||||
}
|
||||
|
||||
closePopup() {
|
||||
// TODO: hide custom context menu
|
||||
}
|
||||
}
|
||||
|
||||
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 || '';
|
||||
}
|
||||
}
|
||||
23
shims/electron/remote/native-image.js
Normal file
23
shims/electron/remote/native-image.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// Shim for remote.nativeImage
|
||||
// Minimal stub - Obsidian's renderer-side usage is limited
|
||||
|
||||
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));
|
||||
},
|
||||
};
|
||||
34
shims/electron/remote/notification.js
Normal file
34
shims/electron/remote/notification.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// Shim for remote.Notification
|
||||
// Maps to browser Notification API
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
30
shims/electron/remote/screen.js
Normal file
30
shims/electron/remote/screen.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 },
|
||||
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() {},
|
||||
};
|
||||
21
shims/electron/remote/session.js
Normal file
21
shims/electron/remote/session.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// Shim for remote.session
|
||||
// Mostly no-op; Obsidian's use is minimal
|
||||
|
||||
export const sessionShim = {
|
||||
defaultSession: {
|
||||
clearCache() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
clearStorageData() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
setSpellCheckerLanguages(langs) {},
|
||||
getSpellCheckerLanguages() { return []; },
|
||||
|
||||
on() {},
|
||||
once() {},
|
||||
removeListener() {},
|
||||
},
|
||||
};
|
||||
20
shims/electron/remote/shell.js
Normal file
20
shims/electron/remote/shell.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// Shim for remote.shell
|
||||
// Obsidian uses: openExternal, openPath, showItemInFolder
|
||||
|
||||
export const shellShim = {
|
||||
openExternal(url) {
|
||||
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('');
|
||||
},
|
||||
|
||||
showItemInFolder(filePath) {
|
||||
// No OS file manager in browser context
|
||||
console.log('[shim:shell] showItemInFolder (stub):', filePath);
|
||||
},
|
||||
};
|
||||
24
shims/electron/remote/system-preferences.js
Normal file
24
shims/electron/remote/system-preferences.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// Shim for remote.systemPreferences
|
||||
// No-op with safe defaults
|
||||
|
||||
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() {},
|
||||
};
|
||||
60
shims/electron/remote/theme.js
Normal file
60
shims/electron/remote/theme.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// Shim for remote.nativeTheme
|
||||
// Obsidian uses: shouldUseDarkColors, on('updated', cb)
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
215
shims/electron/remote/window.js
Normal file
215
shims/electron/remote/window.js
Normal file
@@ -0,0 +1,215 @@
|
||||
// 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,
|
||||
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) {
|
||||
// Cannot resize browser window from JS
|
||||
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) {
|
||||
// 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);
|
||||
return currentWindow;
|
||||
},
|
||||
|
||||
once(event, handler) {
|
||||
if (event === "focus")
|
||||
window.addEventListener("focus", handler, { once: true });
|
||||
return currentWindow;
|
||||
},
|
||||
|
||||
removeListener() {
|
||||
return currentWindow;
|
||||
},
|
||||
removeAllListeners() {
|
||||
return currentWindow;
|
||||
},
|
||||
};
|
||||
|
||||
const currentWebContents = {
|
||||
_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;
|
||||
},
|
||||
|
||||
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() {},
|
||||
pasteAndMatchStyle() {},
|
||||
|
||||
setSpellCheckerLanguages(langs) {},
|
||||
|
||||
on(event, handler) {
|
||||
return currentWebContents;
|
||||
},
|
||||
once(event, handler) {
|
||||
return currentWebContents;
|
||||
},
|
||||
removeListener() {
|
||||
return currentWebContents;
|
||||
},
|
||||
|
||||
get isSecured() {
|
||||
return true;
|
||||
},
|
||||
set isSecured(v) {},
|
||||
};
|
||||
|
||||
export const windowShim = {
|
||||
_current: () => currentWindow,
|
||||
|
||||
getFocusedWindow() {
|
||||
return currentWindow;
|
||||
},
|
||||
};
|
||||
|
||||
export const webContentsShim = {
|
||||
_current: () => currentWebContents,
|
||||
};
|
||||
Reference in New Issue
Block a user