positron.js 1.0.2 → 1.0.3
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/bin/positron.js +1 -1
- package/builder.js +1 -1
- package/core/mac/main.swift +51 -0
- package/core/win/main.cs +44 -0
- package/index.js +69 -18
- package/package.json +1 -1
package/bin/positron.js
CHANGED
package/builder.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const cp = require("child_process");
|
|
4
|
-
const { success, error, info } = require("./logs");
|
|
4
|
+
const { success, error, info, warn } = require("./logs");
|
|
5
5
|
|
|
6
6
|
const arch = process.argv.includes("--x64") ? "x64" : process.argv.includes("--arm64") ? "arm64" : process.arch;
|
|
7
7
|
|
package/core/mac/main.swift
CHANGED
|
@@ -169,6 +169,26 @@ struct IPCResponse: Codable {
|
|
|
169
169
|
let data: [String: String]
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
func GetIPCClient() -> IPCClient {
|
|
173
|
+
return AppDelegate.shared?.ipcClient ?? IPCClient()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func GetWebView(windowId: Int) -> WKWebView? {
|
|
177
|
+
guard let window = windows[windowId],
|
|
178
|
+
let webView = window.contentView as? WKWebView else {
|
|
179
|
+
printError("GetWebView failed: no webview found for window \(windowId)")
|
|
180
|
+
return nil
|
|
181
|
+
}
|
|
182
|
+
return webView
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func GetWindow(windowId: Int) -> NSWindow? {
|
|
186
|
+
guard let window = windows[windowId] else {
|
|
187
|
+
printError("GetWindow failed: no window found with ID \(windowId)")
|
|
188
|
+
return nil
|
|
189
|
+
}
|
|
190
|
+
return window
|
|
191
|
+
}
|
|
172
192
|
|
|
173
193
|
// MARK: - Command Handler
|
|
174
194
|
|
|
@@ -272,6 +292,19 @@ func handleCommand(windowId: Int, command: String, args: [String]) {
|
|
|
272
292
|
|
|
273
293
|
window.performClose(nil)
|
|
274
294
|
|
|
295
|
+
case "setSwipeNav":
|
|
296
|
+
guard let window = windows[windowId],
|
|
297
|
+
let webView = window.contentView as? WKWebView else {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let enable = args.first?.lowercased() != "false"
|
|
302
|
+
webView.allowsBackForwardNavigationGestures = enable
|
|
303
|
+
|
|
304
|
+
GetIPCClient().send(
|
|
305
|
+
IPCResponse(windowId: windowId, event: "setSwipeNav-reply-\(windowId)", data: ["enabled": enable ? "true" : "false"])
|
|
306
|
+
)
|
|
307
|
+
|
|
275
308
|
case "forceCloseWindow":
|
|
276
309
|
guard let window = windows[windowId] else { return }
|
|
277
310
|
|
|
@@ -606,6 +639,24 @@ UNUserNotificationCenter.current().requestAuthorization(
|
|
|
606
639
|
}
|
|
607
640
|
}
|
|
608
641
|
|
|
642
|
+
case "confirm":
|
|
643
|
+
guard let window = windows[windowId] else { return }
|
|
644
|
+
guard let message = args.first else {
|
|
645
|
+
printError("confirm — missing message argument")
|
|
646
|
+
return
|
|
647
|
+
}
|
|
648
|
+
let alert = NSAlert()
|
|
649
|
+
alert.messageText = message
|
|
650
|
+
alert.addButton(withTitle: "OK")
|
|
651
|
+
alert.addButton(withTitle: "Cancel")
|
|
652
|
+
|
|
653
|
+
alert.beginSheetModal(for: window) { response in
|
|
654
|
+
let confirmed = (response == .alertFirstButtonReturn)
|
|
655
|
+
AppDelegate.shared?.ipcClient.send(
|
|
656
|
+
IPCResponse(windowId: windowId, event: "confirm-reply-\(windowId)", data: ["confirmed": confirmed ? "true" : "false"])
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
|
|
609
660
|
case "isFocused":
|
|
610
661
|
guard let window = windows[windowId] else { return }
|
|
611
662
|
let isFocused = window.isKeyWindow
|
package/core/win/main.cs
CHANGED
|
@@ -382,6 +382,23 @@ private void StartNodeProcess(string workingDirectory)
|
|
|
382
382
|
|
|
383
383
|
// 3. Attach it to the layout
|
|
384
384
|
layout.ContextMenu = contextMenu;
|
|
385
|
+
break;
|
|
386
|
+
|
|
387
|
+
case "setSwipeNav":
|
|
388
|
+
if (!WindowsMap.TryGetValue(windowId, out var winSwipeNav)) break;
|
|
389
|
+
var wvSwipeNav = GetWebView(windowId);
|
|
390
|
+
if (wvSwipeNav != null) {
|
|
391
|
+
bool enable = args.Count == 0 || args[0].ToLower() != "false";
|
|
392
|
+
wvSwipeNav.CoreWebView2.Settings.IsSwipeNavigationEnabled = enable;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
GetIPCClient().Send(new IPCResponse
|
|
396
|
+
{
|
|
397
|
+
windowId = windowId,
|
|
398
|
+
@event = "setSwipeNav-reply-" + windowId,
|
|
399
|
+
data = new() { { "enabled", (wvSwipeNav?.CoreWebView2.Settings.IsSwipeNavigationEnabled ?? false).ToString().ToLower() } }
|
|
400
|
+
});
|
|
401
|
+
|
|
385
402
|
break;
|
|
386
403
|
|
|
387
404
|
case "closeWindow":
|
|
@@ -621,6 +638,24 @@ case "setBounds":
|
|
|
621
638
|
}
|
|
622
639
|
break;
|
|
623
640
|
|
|
641
|
+
case "confirm":
|
|
642
|
+
if (args.Count < 1)
|
|
643
|
+
{
|
|
644
|
+
error("confirm — expected message argument");
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
{
|
|
648
|
+
var message = args[0];
|
|
649
|
+
var result = MessageBox.Show(message, "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes;
|
|
650
|
+
_ipcClient.Send(new IPCResponse
|
|
651
|
+
{
|
|
652
|
+
windowId = windowId,
|
|
653
|
+
@event = "confirm-reply-" + windowId,
|
|
654
|
+
data = new() { { "confirmed", result.ToString().ToLower() } }
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
|
|
624
659
|
case "emitToRenderer":
|
|
625
660
|
if (args.Count < 2)
|
|
626
661
|
{
|
|
@@ -841,6 +876,15 @@ case "setBounds":
|
|
|
841
876
|
return null;
|
|
842
877
|
}
|
|
843
878
|
|
|
879
|
+
public static Window? GetWindow(int windowId)
|
|
880
|
+
{
|
|
881
|
+
if (WindowsMap.TryGetValue(windowId, out var window))
|
|
882
|
+
return window;
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
public static IPCClient GetIPCClient() => _ipcClient;
|
|
887
|
+
|
|
844
888
|
// MARK: - Menu Management
|
|
845
889
|
|
|
846
890
|
private static void BuildAndAttachMenu(int windowId, string jsonStr)
|
package/index.js
CHANGED
|
@@ -627,7 +627,7 @@ if (timeoutArg) {
|
|
|
627
627
|
|
|
628
628
|
args = args.filter(arg => arg !== timeoutArg);
|
|
629
629
|
|
|
630
|
-
timeoutDuration = parseInt(timeoutArg.split("=")[1]
|
|
630
|
+
timeoutDuration = parseInt(timeoutArg.split("=")[1]);
|
|
631
631
|
}
|
|
632
632
|
|
|
633
633
|
timeout = setTimeout(() => {
|
|
@@ -765,7 +765,7 @@ async getBounds() {
|
|
|
765
765
|
* @returns {Promise<string>} The current URL loaded in the window.
|
|
766
766
|
*/
|
|
767
767
|
async getURL() {
|
|
768
|
-
return await this.request("getURL", `getURL-reply-${this.id}`);
|
|
768
|
+
return (await this.request("getURL", `getURL-reply-${this.id}`))?.url || "";
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
/**
|
|
@@ -773,7 +773,7 @@ async getURL() {
|
|
|
773
773
|
* @returns {Promise<string>} The current title of the window.
|
|
774
774
|
*/
|
|
775
775
|
async getTitle() {
|
|
776
|
-
return await this.request("getTitle", `getTitle-reply-${this.id}`);
|
|
776
|
+
return (await this.request("getTitle", `getTitle-reply-${this.id}`)).title || "";
|
|
777
777
|
}
|
|
778
778
|
|
|
779
779
|
/**
|
|
@@ -801,7 +801,7 @@ setTitlebarTransparent(isTransparent) {
|
|
|
801
801
|
*/
|
|
802
802
|
async evaluateJavaScript(script) {
|
|
803
803
|
const res = await this.request("evaluateJS", `evaluateJS-reply-${this.id}`, script);
|
|
804
|
-
return res;
|
|
804
|
+
return res.result;
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
/**
|
|
@@ -809,8 +809,7 @@ return res;
|
|
|
809
809
|
* @returns {Promise<string>} The user agent string of the window.
|
|
810
810
|
*/
|
|
811
811
|
async getUserAgent() {
|
|
812
|
-
|
|
813
|
-
return res;
|
|
812
|
+
return await this.evaluateJavaScript("navigator.userAgent");
|
|
814
813
|
}
|
|
815
814
|
|
|
816
815
|
/**
|
|
@@ -822,10 +821,14 @@ async getUserAgent() {
|
|
|
822
821
|
async setStyleOf(selector, style) {
|
|
823
822
|
const styleString = Object.entries(style).map(([key, value]) => `${key}: ${value};`).join(" ");
|
|
824
823
|
const script = `
|
|
824
|
+
(function() {
|
|
825
825
|
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
826
826
|
elements.forEach(el => {
|
|
827
|
-
|
|
827
|
+
Object.entries(${JSON.stringify(style)}).forEach(([key, value]) => {
|
|
828
|
+
el.style[key] = value;
|
|
829
|
+
});
|
|
828
830
|
});
|
|
831
|
+
})();
|
|
829
832
|
`;
|
|
830
833
|
await this.evaluateJavaScript(script);
|
|
831
834
|
this.emit("style-updated", { selector, style });
|
|
@@ -840,10 +843,12 @@ async setStyleOf(selector, style) {
|
|
|
840
843
|
*/
|
|
841
844
|
async setAttributeOf(selector, attribute, value) {
|
|
842
845
|
const script = `
|
|
846
|
+
(function() {
|
|
843
847
|
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
844
848
|
elements.forEach(el => {
|
|
845
849
|
el.setAttribute(${JSON.stringify(attribute)}, ${JSON.stringify(value)});
|
|
846
850
|
});
|
|
851
|
+
})();
|
|
847
852
|
`;
|
|
848
853
|
await this.evaluateJavaScript(script);
|
|
849
854
|
this.emit("attribute-updated", { selector, attribute, value });
|
|
@@ -857,10 +862,12 @@ async setAttributeOf(selector, attribute, value) {
|
|
|
857
862
|
*/
|
|
858
863
|
async removeAttributeOf(selector, attribute) {
|
|
859
864
|
const script = `
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
865
|
+
(function() {
|
|
866
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
867
|
+
elements.forEach(el => {
|
|
868
|
+
el.removeAttribute(${JSON.stringify(attribute)});
|
|
869
|
+
});
|
|
870
|
+
})();
|
|
864
871
|
`;
|
|
865
872
|
await this.evaluateJavaScript(script);
|
|
866
873
|
this.emit("attribute-removed", { selector, attribute });
|
|
@@ -875,12 +882,14 @@ async removeAttributeOf(selector, attribute) {
|
|
|
875
882
|
async removeStyleOf(selector, styleProperties) {
|
|
876
883
|
const propertiesString = styleProperties.map(prop => `${prop}:`).join("|");
|
|
877
884
|
const script = `
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
885
|
+
(function() {
|
|
886
|
+
const elements = document.querySelectorAll(${JSON.stringify(selector)});
|
|
887
|
+
elements.forEach(el => {
|
|
888
|
+
el.style.cssText = el.style.cssText.split(";").filter(rule => {
|
|
889
|
+
return !${JSON.stringify(propertiesString)}.includes(rule.trim().split(":")[0] + ":");
|
|
890
|
+
}).join(";");
|
|
891
|
+
});
|
|
892
|
+
})();
|
|
884
893
|
`;
|
|
885
894
|
await this.evaluateJavaScript(script);
|
|
886
895
|
this.emit("style-removed", { selector, styleProperties });
|
|
@@ -896,6 +905,7 @@ async removeStyleOf(selector, styleProperties) {
|
|
|
896
905
|
*/
|
|
897
906
|
async onClick(selector, channel, { replace = true } = {}) {
|
|
898
907
|
const script = `
|
|
908
|
+
(function() {
|
|
899
909
|
const selector = ${JSON.stringify(selector)};
|
|
900
910
|
const channel = ${JSON.stringify(channel)};
|
|
901
911
|
const replace = ${replace};
|
|
@@ -913,6 +923,7 @@ async onClick(selector, channel, { replace = true } = {}) {
|
|
|
913
923
|
});
|
|
914
924
|
}
|
|
915
925
|
})
|
|
926
|
+
})();
|
|
916
927
|
`;
|
|
917
928
|
|
|
918
929
|
await this.evaluateJavaScript(script);
|
|
@@ -933,6 +944,27 @@ async removeOnClick(selector) {
|
|
|
933
944
|
await this.evaluateJavaScript(script);
|
|
934
945
|
}
|
|
935
946
|
|
|
947
|
+
/**
|
|
948
|
+
* Displays a confirmation dialog with the given message. Returns a Promise that resolves to true if the user confirmed, or false if the user cancelled. Emits a "confirm" event with the message as data when done.
|
|
949
|
+
* @param {string} message The message to display in the confirmation dialog.
|
|
950
|
+
* @returns {Promise<boolean>} True if the user confirmed, false if the user cancelled.
|
|
951
|
+
*/
|
|
952
|
+
async confirm(message) {
|
|
953
|
+
const res = await this.request("confirm", `confirm-reply-${this.id}`, message);
|
|
954
|
+
this.emit("confirm", message);
|
|
955
|
+
return res?.confirmed === "true";
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Enables or disables swipe navigation for the window. When enabled, users can navigate back and forward through their history by swiping left or right on a trackpad or touchscreen. Emits a "swipe-navigation-updated" event with the new value when done.
|
|
960
|
+
* @param {boolean} enabled Whether swipe navigation should be enabled.
|
|
961
|
+
* @returns {Promise<void>} A Promise that resolves when the swipe navigation setting has been updated.
|
|
962
|
+
*/
|
|
963
|
+
async setSwipeNavigation(enabled) {
|
|
964
|
+
const res = await this.request("setSwipeNav", `setSwipeNav-reply-${this.id}`, String(enabled));
|
|
965
|
+
this.emit("swipe-navigation-updated", enabled);
|
|
966
|
+
}
|
|
967
|
+
|
|
936
968
|
}
|
|
937
969
|
|
|
938
970
|
const app = {
|
|
@@ -963,7 +995,7 @@ const app = {
|
|
|
963
995
|
},
|
|
964
996
|
|
|
965
997
|
/**
|
|
966
|
-
* Adds an event listener for application-level events.
|
|
998
|
+
* Adds an event listener for application-level events.
|
|
967
999
|
* @param {string} event The name of the event to listen for.
|
|
968
1000
|
* @param {Function} listener The callback function to invoke when the event is emitted.
|
|
969
1001
|
*/
|
|
@@ -989,11 +1021,19 @@ const app = {
|
|
|
989
1021
|
this.events.once(event, listener);
|
|
990
1022
|
},
|
|
991
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* Gets the currently focused window. Returns a Promise that resolves to the focused Window instance, or null if no windows are currently focused. Emits a "focused-window-retrieved" event with the focused window as data when done.
|
|
1026
|
+
* @returns {Promise<Window|null>} The currently focused window, or null if no windows are focused.
|
|
1027
|
+
*/
|
|
992
1028
|
async getFocusedWindow() {
|
|
993
1029
|
const results = await Promise.all([...activeWindows].map(win => win.isFocused().then(isFocused => ({ win, isFocused }))));
|
|
994
1030
|
return results.find(({ isFocused }) => isFocused)?.win || null;
|
|
995
1031
|
},
|
|
996
1032
|
|
|
1033
|
+
/**
|
|
1034
|
+
* Sets the application name, which is used for things like the user data directory and may be used by the native layer for other purposes.
|
|
1035
|
+
* @param {*} name The new name for the application. This will be converted to a string before being used.
|
|
1036
|
+
*/
|
|
997
1037
|
setName(name) {
|
|
998
1038
|
process.env.POSITRON_APP_NAME = name;
|
|
999
1039
|
this.events.emit("name-updated", name);
|
|
@@ -1005,6 +1045,10 @@ const app = {
|
|
|
1005
1045
|
},
|
|
1006
1046
|
|
|
1007
1047
|
userData: {
|
|
1048
|
+
/**
|
|
1049
|
+
* Gets the path to the user data directory for the application. The path is determined based on the operating system and the application name. If the directory does not exist, it will be created. Emits a "user-data-path-retrieved" event with the path as data when done.
|
|
1050
|
+
* @returns {string} The path to the user data directory.
|
|
1051
|
+
*/
|
|
1008
1052
|
getPath() {
|
|
1009
1053
|
let userPath = null;
|
|
1010
1054
|
|
|
@@ -1033,6 +1077,9 @@ userData: {
|
|
|
1033
1077
|
return userPath;
|
|
1034
1078
|
},
|
|
1035
1079
|
|
|
1080
|
+
/**
|
|
1081
|
+
* Creates the user data directory if it does not already exist. Emits a "user-data-created" event when the directory is created successfully.
|
|
1082
|
+
*/
|
|
1036
1083
|
create() {
|
|
1037
1084
|
const userPath = this.getPath();
|
|
1038
1085
|
|
|
@@ -1042,6 +1089,10 @@ userData: {
|
|
|
1042
1089
|
}
|
|
1043
1090
|
},
|
|
1044
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* Deletes the user data directory and all of its contents. Use with caution, as this will permanently remove all user data for the application. Emits a "user-data-deleted" event when the directory is deleted successfully.
|
|
1094
|
+
*/
|
|
1095
|
+
|
|
1045
1096
|
delete() {
|
|
1046
1097
|
const userPath = this.getPath();
|
|
1047
1098
|
|