positron.js 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/mac/main.swift +23 -2
- package/core/win/main.cs +11 -0
- package/index.js +146 -15
- package/package.json +1 -1
package/core/mac/main.swift
CHANGED
|
@@ -18,6 +18,7 @@ let AUTH_TOKEN: String = {
|
|
|
18
18
|
}()
|
|
19
19
|
|
|
20
20
|
var windowObservations: [Int: NSKeyValueObservation] = [:]
|
|
21
|
+
var navigationDelegates: [Int: WebViewNavigationDelegate] = [:]
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
import Foundation
|
|
@@ -225,6 +226,9 @@ func handleCommand(windowId: Int, command: String, args: [String]) {
|
|
|
225
226
|
config.userContentController.addUserScript(preload)
|
|
226
227
|
|
|
227
228
|
let webView = PositronWebView(frame: NSRect(origin: .zero, size: frame.size), configuration: config)
|
|
229
|
+
let navDelegate = WebViewNavigationDelegate(windowId: windowId)
|
|
230
|
+
webView.navigationDelegate = navDelegate
|
|
231
|
+
navigationDelegates[windowId] = navDelegate
|
|
228
232
|
// Resize webview automatically when the window resizes
|
|
229
233
|
webView.autoresizingMask = [.width, .height]
|
|
230
234
|
newWindow.contentView = webView
|
|
@@ -242,6 +246,7 @@ func handleCommand(windowId: Int, command: String, args: [String]) {
|
|
|
242
246
|
observation.invalidate()
|
|
243
247
|
windowObservations.removeValue(forKey: windowId)
|
|
244
248
|
}
|
|
249
|
+
navigationDelegates.removeValue(forKey: windowId)
|
|
245
250
|
|
|
246
251
|
|
|
247
252
|
windows.removeValue(forKey: windowId)
|
|
@@ -305,7 +310,6 @@ case "forceCloseWindow":
|
|
|
305
310
|
}
|
|
306
311
|
(window.contentView as? WKWebView)?.load(URLRequest(url: url))
|
|
307
312
|
|
|
308
|
-
|
|
309
313
|
case "hide":
|
|
310
314
|
guard let window = windows[windowId] else { return }
|
|
311
315
|
window.orderOut(nil)
|
|
@@ -344,7 +348,6 @@ case "forceCloseWindow":
|
|
|
344
348
|
(window.contentView as? WKWebView)?
|
|
345
349
|
.loadFileURL(fileURL, allowingReadAccessTo: fileURL.deletingLastPathComponent())
|
|
346
350
|
|
|
347
|
-
|
|
348
351
|
case "setBounds":
|
|
349
352
|
guard let window = windows[windowId] else { return }
|
|
350
353
|
guard args.count >= 4,
|
|
@@ -682,6 +685,24 @@ case "resetMenu":
|
|
|
682
685
|
}
|
|
683
686
|
}
|
|
684
687
|
|
|
688
|
+
// MARK: - WebView Navigation Delegate
|
|
689
|
+
|
|
690
|
+
final class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
|
|
691
|
+
let windowId: Int
|
|
692
|
+
|
|
693
|
+
init(windowId: Int) {
|
|
694
|
+
self.windowId = windowId
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
698
|
+
let isFile = webView.url?.isFileURL ?? false
|
|
699
|
+
let eventName = isFile ? "loadFile-reply-\(windowId)" : "loadURL-reply-\(windowId)"
|
|
700
|
+
AppDelegate.shared?.ipcClient.send(
|
|
701
|
+
IPCResponse(windowId: windowId, event: eventName, data: [:])
|
|
702
|
+
)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
685
706
|
// MARK: - WebView → Swift IPC Handler
|
|
686
707
|
|
|
687
708
|
/// Receives messages from renderer JS: window.webkit.messageHandlers.ipc.postMessage({...})
|
package/core/win/main.cs
CHANGED
|
@@ -329,6 +329,17 @@ private void StartNodeProcess(string workingDirectory)
|
|
|
329
329
|
window.Title = webView.CoreWebView2.DocumentTitle;
|
|
330
330
|
};
|
|
331
331
|
|
|
332
|
+
webView.CoreWebView2.NavigationCompleted += (s, e) =>
|
|
333
|
+
{
|
|
334
|
+
bool isFile = webView.Source != null && webView.Source.IsFile;
|
|
335
|
+
string eventName = isFile ? $"loadFile-reply-{windowId}" : $"loadURL-reply-{windowId}";
|
|
336
|
+
_ipcClient.Send(new IPCResponse
|
|
337
|
+
{
|
|
338
|
+
windowId = windowId,
|
|
339
|
+
@event = eventName
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
332
343
|
webView.CoreWebView2.ContextMenuRequested += (s, e) =>
|
|
333
344
|
{
|
|
334
345
|
if (LayoutMap.TryGetValue(windowId, out var l) && l.ContextMenu != null)
|
package/index.js
CHANGED
|
@@ -294,16 +294,6 @@ class Window extends Events.EventEmitter {
|
|
|
294
294
|
this.emit("title-updated", title);
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
/**
|
|
298
|
-
* Loads a remote URL in the window. Emits "url-loaded" and "navigated" events with the URL as data.
|
|
299
|
-
* @param {string} url The URL to load.
|
|
300
|
-
*/
|
|
301
|
-
loadURL(url) {
|
|
302
|
-
this.sendCommand("loadURL", [url]);
|
|
303
|
-
this.emit("url-loaded", url);
|
|
304
|
-
this.emit("navigated", url);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
297
|
/**
|
|
308
298
|
* Triggers the print dialog for the window. Emits a "print" event. Note that the actual print functionality and dialog is handled by the native layer, so behavior may vary across platforms.
|
|
309
299
|
*/
|
|
@@ -322,13 +312,25 @@ class Window extends Events.EventEmitter {
|
|
|
322
312
|
}
|
|
323
313
|
|
|
324
314
|
/**
|
|
325
|
-
* Loads a
|
|
315
|
+
* Loads a file into the window. The path can be an absolute file path or a relative path from the application's root directory. Emits a "file-loaded" event with the path as data, and a "navigated" event with the path as data.
|
|
326
316
|
* @param {string} path The path to the file to load.
|
|
327
317
|
*/
|
|
328
|
-
loadFile(path) {
|
|
329
|
-
this.
|
|
330
|
-
this.emit("file-loaded", path);
|
|
331
|
-
this.emit("navigated", path);
|
|
318
|
+
async loadFile(path) {
|
|
319
|
+
const res = await this.request("loadFile", `loadFile-reply-${this.id}`, path);
|
|
320
|
+
this.emit("file-loaded", path);
|
|
321
|
+
this.emit("navigated", path);
|
|
322
|
+
return res;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Loads a URL into the window. Emits a "url-loaded" event with the URL as data, and a "navigated" event with the URL as data.
|
|
327
|
+
* @param {string} url The URL to load.
|
|
328
|
+
*/
|
|
329
|
+
async loadURL(url) {
|
|
330
|
+
const res = await this.request("loadURL", `loadURL-reply-${this.id}`, url);
|
|
331
|
+
this.emit("url-loaded", url);
|
|
332
|
+
this.emit("navigated", url);
|
|
333
|
+
return res;
|
|
332
334
|
}
|
|
333
335
|
|
|
334
336
|
/**
|
|
@@ -802,6 +804,135 @@ const res = await this.request("evaluateJS", `evaluateJS-reply-${this.id}`, scri
|
|
|
802
804
|
return res;
|
|
803
805
|
}
|
|
804
806
|
|
|
807
|
+
/**
|
|
808
|
+
* Gets the user agent string of the window. Returns a Promise that resolves to the user agent as a string.
|
|
809
|
+
* @returns {Promise<string>} The user agent string of the window.
|
|
810
|
+
*/
|
|
811
|
+
async getUserAgent() {
|
|
812
|
+
const res = await this.evaluateJavaScript("navigator.userAgent");
|
|
813
|
+
return res;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Sets the style of elements matching a CSS selector. Returns a Promise that resolves when the style has been applied.
|
|
818
|
+
* @param {string} selector The CSS selector for the elements to style.
|
|
819
|
+
* @param {Object} style The style properties to apply.
|
|
820
|
+
* @returns {Promise<void>} A Promise that resolves when the style has been applied.
|
|
821
|
+
*/
|
|
822
|
+
async setStyleOf(selector, style) {
|
|
823
|
+
const styleString = Object.entries(style).map(([key, value]) => `${key}: ${value};`).join(" ");
|
|
824
|
+
const script = `
|
|
825
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
826
|
+
elements.forEach(el => {
|
|
827
|
+
el.style.cssText += ${JSON.stringify(styleString)};
|
|
828
|
+
});
|
|
829
|
+
`;
|
|
830
|
+
await this.evaluateJavaScript(script);
|
|
831
|
+
this.emit("style-updated", { selector, style });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Sets an attribute of elements matching a CSS selector. Returns a Promise that resolves when the attribute has been set.
|
|
836
|
+
* @param {string} selector The CSS selector for the elements to update.
|
|
837
|
+
* @param {string} attribute The name of the attribute to set.
|
|
838
|
+
* @param {string} value The value to set for the attribute.
|
|
839
|
+
* @returns {Promise<void>} A Promise that resolves when the attribute has been set.
|
|
840
|
+
*/
|
|
841
|
+
async setAttributeOf(selector, attribute, value) {
|
|
842
|
+
const script = `
|
|
843
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
844
|
+
elements.forEach(el => {
|
|
845
|
+
el.setAttribute(${JSON.stringify(attribute)}, ${JSON.stringify(value)});
|
|
846
|
+
});
|
|
847
|
+
`;
|
|
848
|
+
await this.evaluateJavaScript(script);
|
|
849
|
+
this.emit("attribute-updated", { selector, attribute, value });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Removes an attribute from elements matching a CSS selector. Returns a Promise that resolves when the attribute has been removed.
|
|
854
|
+
* @param {string} selector The CSS selector for the elements to update.
|
|
855
|
+
* @param {string} attribute The name of the attribute to remove.
|
|
856
|
+
* @returns {Promise<void>} A Promise that resolves when the attribute has been removed.
|
|
857
|
+
*/
|
|
858
|
+
async removeAttributeOf(selector, attribute) {
|
|
859
|
+
const script = `
|
|
860
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
861
|
+
elements.forEach(el => {
|
|
862
|
+
el.removeAttribute(${JSON.stringify(attribute)});
|
|
863
|
+
});
|
|
864
|
+
`;
|
|
865
|
+
await this.evaluateJavaScript(script);
|
|
866
|
+
this.emit("attribute-removed", { selector, attribute });
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Removes specific style properties from elements matching a CSS selector. Returns a Promise that resolves when the styles have been removed.
|
|
871
|
+
* @param {string} selector The CSS selector for the elements to update.
|
|
872
|
+
* @param {string[]} styleProperties The style properties to remove.
|
|
873
|
+
* @returns {Promise<void>} A Promise that resolves when the styles have been removed.
|
|
874
|
+
*/
|
|
875
|
+
async removeStyleOf(selector, styleProperties) {
|
|
876
|
+
const propertiesString = styleProperties.map(prop => `${prop}:`).join("|");
|
|
877
|
+
const script = `
|
|
878
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
879
|
+
elements.forEach(el => {
|
|
880
|
+
el.style.cssText = el.style.cssText.split(";").filter(rule => {
|
|
881
|
+
return !${JSON.stringify(propertiesString)}.includes(rule.trim().split(":")[0] + ":");
|
|
882
|
+
}).join(";");
|
|
883
|
+
});
|
|
884
|
+
`;
|
|
885
|
+
await this.evaluateJavaScript(script);
|
|
886
|
+
this.emit("style-removed", { selector, styleProperties });
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Adds or replaces click handlers that emit IPC events.
|
|
891
|
+
*
|
|
892
|
+
* @param {string} selector - The CSS selector for the elements to attach the click handlers to.
|
|
893
|
+
* @param {string} channel - The IPC channel to emit events on when the elements are clicked.
|
|
894
|
+
* @param {{ replace?: boolean }} [options] - Optional settings for the click handlers. If `replace` is true, any existing IPC click handlers on the elements will be removed before adding the new handler. If false or omitted, the new handler will be added alongside existing handlers without removing them.
|
|
895
|
+
* @returns {Promise<void>} A Promise that resolves when the click handlers have been added.
|
|
896
|
+
*/
|
|
897
|
+
async onClick(selector, channel, { replace = true } = {}) {
|
|
898
|
+
const script = `
|
|
899
|
+
const selector = ${JSON.stringify(selector)};
|
|
900
|
+
const channel = ${JSON.stringify(channel)};
|
|
901
|
+
const replace = ${replace};
|
|
902
|
+
|
|
903
|
+
const elements = document.querySelectorAll(selector);
|
|
904
|
+
|
|
905
|
+
elements.forEach(el => {
|
|
906
|
+
if (replace) {
|
|
907
|
+
el.onclick = () => {
|
|
908
|
+
window.ipc.send(channel, { selector });
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
el.addEventListener("click", () => {
|
|
912
|
+
window.ipc.send(channel, { selector });
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
`;
|
|
917
|
+
|
|
918
|
+
await this.evaluateJavaScript(script);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Removes click handlers that emit IPC events from elements matching the specified CSS selector. This will remove all click handlers that were added via the onClick method for the given selector, regardless of the channel or whether they were set to replace existing handlers.
|
|
923
|
+
* @param {string} selector The CSS selector for the elements to remove click handlers from.
|
|
924
|
+
* @returns {Promise<void>} A Promise that resolves when the click handlers have been removed.
|
|
925
|
+
*/
|
|
926
|
+
async removeOnClick(selector) {
|
|
927
|
+
const script = `
|
|
928
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
929
|
+
elements.forEach(el => {
|
|
930
|
+
el.onclick = null;
|
|
931
|
+
});
|
|
932
|
+
`;
|
|
933
|
+
await this.evaluateJavaScript(script);
|
|
934
|
+
}
|
|
935
|
+
|
|
805
936
|
}
|
|
806
937
|
|
|
807
938
|
const app = {
|