Make likable paths across desktop app work
This commit is contained in:
parent
33b9f9d76d
commit
6656a3749e
|
@ -2,10 +2,17 @@
|
||||||
// Electron script to run Hyperspace as an app
|
// Electron script to run Hyperspace as an app
|
||||||
// © 2018 Hyperspace developers. Licensed under NPL v1.
|
// © 2018 Hyperspace developers. Licensed under NPL v1.
|
||||||
|
|
||||||
const { app, Menu, protocol, BrowserWindow, shell, systemPreferences } = require('electron');
|
const {
|
||||||
const windowStateKeeper = require('electron-window-state');
|
app,
|
||||||
const { autoUpdater } = require('electron-updater');
|
Menu,
|
||||||
const path = require('path');
|
protocol,
|
||||||
|
BrowserWindow,
|
||||||
|
shell,
|
||||||
|
systemPreferences
|
||||||
|
} = require("electron");
|
||||||
|
const windowStateKeeper = require("electron-window-state");
|
||||||
|
const { autoUpdater } = require("electron-updater");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
// Check for any updates to the app
|
// Check for any updates to the app
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
autoUpdater.checkForUpdatesAndNotify();
|
||||||
|
@ -18,7 +25,7 @@ let mainWindow;
|
||||||
// file:// protocol, which is necessary for Mastodon to redirect
|
// file:// protocol, which is necessary for Mastodon to redirect
|
||||||
// to when authorizing Hyperspace.
|
// to when authorizing Hyperspace.
|
||||||
protocol.registerSchemesAsPrivileged([
|
protocol.registerSchemesAsPrivileged([
|
||||||
{ scheme: 'hyperspace', privileges: { standard: true, secure: true } }
|
{ scheme: "hyperspace", privileges: { standard: true, secure: true } }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,80 +40,81 @@ function isDarwin() {
|
||||||
* Register the protocol for Hyperspace
|
* Register the protocol for Hyperspace
|
||||||
*/
|
*/
|
||||||
function registerProtocol() {
|
function registerProtocol() {
|
||||||
protocol.registerFileProtocol('hyperspace', (request, callback) => {
|
protocol.registerFileProtocol(
|
||||||
|
"hyperspace",
|
||||||
// Check to make sure we're doing a GET request
|
(request, callback) => {
|
||||||
if (request.method !== "GET") {
|
// Check to make sure we're doing a GET request
|
||||||
callback({error: -322});
|
if (request.method !== "GET") {
|
||||||
return null;
|
callback({ error: -322 });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to make sure we're actually working with a hyperspace
|
||||||
|
// protocol and that the host is 'hyperspace'
|
||||||
|
const parsedUrl = new URL(request.url);
|
||||||
|
if (parsedUrl.protocol !== "hyperspace:") {
|
||||||
|
callback({ error: -302 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedUrl.host !== "hyperspace") {
|
||||||
|
callback({ error: -105 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the parsed URL to a list of strings.
|
||||||
|
const target = parsedUrl.pathname.split("/");
|
||||||
|
|
||||||
|
// Check that the target isn't trying to go somewhere
|
||||||
|
// else. If it is, throw a "FILE_NOT_FOUND" error
|
||||||
|
if (target[0] !== "") {
|
||||||
|
callback({ error: -6 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the last target item in the list is empty.
|
||||||
|
// If so, replace it with "index.html" so that it can
|
||||||
|
// load a page.
|
||||||
|
if (target[target.length - 1] === "") {
|
||||||
|
target[target.length - 1] = "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the middle target and redirect to the appropriate
|
||||||
|
// build files of the desktop app when running.
|
||||||
|
let baseDirectory;
|
||||||
|
if (target[1] === "app" || target[1] === "oauth") {
|
||||||
|
baseDirectory = __dirname + "/../build/";
|
||||||
|
} else {
|
||||||
|
// If it doesn't match above, throw a "FILE_NOT_FOUND" error.
|
||||||
|
callback({ error: -6 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a normalized version of the string.
|
||||||
|
baseDirectory = path.normalize(baseDirectory);
|
||||||
|
|
||||||
|
// Check to make sure the target isn't trying to go out of bounds.
|
||||||
|
// If it is, throw a "FILE_NOT_FOUND" error.
|
||||||
|
const relTarget = path.normalize(path.join(...target.slice(2)));
|
||||||
|
if (relTarget.startsWith("..")) {
|
||||||
|
callback({ error: -6 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the absolute target path and return it.
|
||||||
|
const absTarget = path.join(baseDirectory, relTarget);
|
||||||
|
callback({ path: absTarget });
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (error) console.error("Failed to register protocol");
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// Check to make sure we're actually working with a hyperspace
|
|
||||||
// protocol and that the host is 'hyperspace'
|
|
||||||
const parsedUrl = new URL(request.url);
|
|
||||||
if (parsedUrl.protocol !== "hyperspace:") {
|
|
||||||
callback({error: -302});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedUrl.host !== "hyperspace") {
|
|
||||||
callback({error: -105});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the parsed URL to a list of strings.
|
|
||||||
const target = parsedUrl.pathname.split("/");
|
|
||||||
|
|
||||||
// Check that the target isn't trying to go somewhere
|
|
||||||
// else. If it is, throw a "FILE_NOT_FOUND" error
|
|
||||||
if (target[0] !== "") {
|
|
||||||
callback({error: -6});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the last target item in the list is empty.
|
|
||||||
// If so, replace it with "index.html" so that it can
|
|
||||||
// load a page.
|
|
||||||
if (target[target.length -1] === "") {
|
|
||||||
target[target.length -1] = "index.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the middle target and redirect to the appropriate
|
|
||||||
// build files of the desktop app when running.
|
|
||||||
let baseDirectory;
|
|
||||||
if (target[1] === "app" || target[1] === "oauth") {
|
|
||||||
baseDirectory = __dirname + "/../build/";
|
|
||||||
} else {
|
|
||||||
// If it doesn't match above, throw a "FILE_NOT_FOUND" error.
|
|
||||||
callback({error: -6});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a normalized version of the string.
|
|
||||||
baseDirectory = path.normalize(baseDirectory);
|
|
||||||
|
|
||||||
// Check to make sure the target isn't trying to go out of bounds.
|
|
||||||
// If it is, throw a "FILE_NOT_FOUND" error.
|
|
||||||
const relTarget = path.normalize(path.join(...target.slice(2)));
|
|
||||||
if (relTarget.startsWith('..')) {
|
|
||||||
callback({error: -6});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the absolute target path and return it.
|
|
||||||
const absTarget = path.join(baseDirectory, relTarget);
|
|
||||||
callback({ path: absTarget });
|
|
||||||
|
|
||||||
}, (error) => {
|
|
||||||
if (error) console.error('Failed to register protocol');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the window and all of its properties
|
* Create the window and all of its properties
|
||||||
*/
|
*/
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
|
||||||
// Create a window state manager that keeps track of the width
|
// Create a window state manager that keeps track of the width
|
||||||
// and height of the main window.
|
// and height of the main window.
|
||||||
let mainWindowState = windowStateKeeper({
|
let mainWindowState = windowStateKeeper({
|
||||||
|
@ -115,68 +123,72 @@ function createWindow() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a browser window with some settings
|
// Create a browser window with some settings
|
||||||
mainWindow = new BrowserWindow(
|
mainWindow = new BrowserWindow({
|
||||||
{
|
// Use the values from the window state keeper
|
||||||
// Use the values from the window state keeper
|
// to draw the window exactly as it was left.
|
||||||
// to draw the window exactly as it was left.
|
// If not possible, derive it from the default
|
||||||
// If not possible, derive it from the default
|
// values defined earlier.
|
||||||
// values defined earlier.
|
x: mainWindowState.x,
|
||||||
x: mainWindowState.x,
|
y: mainWindowState.y,
|
||||||
y: mainWindowState.y,
|
width: mainWindowState.width,
|
||||||
width: mainWindowState.width,
|
height: mainWindowState.height,
|
||||||
height: mainWindowState.height,
|
|
||||||
|
|
||||||
// Set a minimum width to prevent element collisions.
|
|
||||||
minWidth: 300,
|
|
||||||
|
|
||||||
// Set important web preferences.
|
// Set a minimum width to prevent element collisions.
|
||||||
webPreferences: {nodeIntegration: true},
|
minWidth: 300,
|
||||||
|
|
||||||
// Set some preferences that are specific to macOS.
|
// Set important web preferences.
|
||||||
titleBarStyle: 'hiddenInset',
|
webPreferences: { nodeIntegration: true },
|
||||||
vibrancy: "sidebar",
|
|
||||||
transparent: isDarwin(),
|
|
||||||
backgroundColor: isDarwin()? "#80000000": "#FFF",
|
|
||||||
|
|
||||||
// Hide the window until the contents load
|
// Set some preferences that are specific to macOS.
|
||||||
show: false
|
titleBarStyle: "hiddenInset",
|
||||||
}
|
vibrancy: "sidebar",
|
||||||
);
|
transparent: isDarwin(),
|
||||||
|
backgroundColor: isDarwin() ? "#80000000" : "#FFF",
|
||||||
|
|
||||||
|
// Hide the window until the contents load
|
||||||
|
show: false
|
||||||
|
});
|
||||||
|
|
||||||
// Set up event listeners to track changes in the window state.
|
// Set up event listeners to track changes in the window state.
|
||||||
mainWindowState.manage(mainWindow);
|
mainWindowState.manage(mainWindow);
|
||||||
|
|
||||||
// Load the main app and open the index page.
|
// Load the main app and open the index page.
|
||||||
mainWindow.loadURL("hyperspace://hyperspace/app/");
|
mainWindow.loadURL("hyperspace://hyperspace/app/");
|
||||||
|
|
||||||
// Watch for a change in macOS's dark mode and reload the window to apply changes, as well as accent color
|
// Watch for a change in macOS's dark mode and reload the window to apply changes, as well as accent color
|
||||||
if (isDarwin()) {
|
if (isDarwin()) {
|
||||||
systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => {
|
systemPreferences.subscribeNotification(
|
||||||
if (mainWindow != null) {
|
"AppleInterfaceThemeChangedNotification",
|
||||||
mainWindow.webContents.reload();
|
() => {
|
||||||
|
if (mainWindow != null) {
|
||||||
|
mainWindow.webContents.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
systemPreferences.subscribeNotification('AppleColorPreferencesChangedNotification', () => {
|
systemPreferences.subscribeNotification(
|
||||||
if (mainWindow != null) {
|
"AppleColorPreferencesChangedNotification",
|
||||||
mainWindow.webContents.reload();
|
() => {
|
||||||
|
if (mainWindow != null) {
|
||||||
|
mainWindow.webContents.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show the window when ready
|
// Only show the window when ready
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once("ready-to-show", () => {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete the window when closed
|
// Delete the window when closed
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on("closed", () => {
|
||||||
mainWindow = null
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hijack any links with a blank target and open them in the default
|
// Hijack any links with a blank target and open them in the default
|
||||||
// browser instead of a new Electron window
|
// browser instead of a new Electron window
|
||||||
mainWindow.webContents.on('new-window', (event, url) => {
|
mainWindow.webContents.on("new-window", (event, url) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
});
|
});
|
||||||
|
@ -199,18 +211,17 @@ function safelyGoTo(url) {
|
||||||
* Create the menu bar and attach it to a window
|
* Create the menu bar and attach it to a window
|
||||||
*/
|
*/
|
||||||
function createMenubar() {
|
function createMenubar() {
|
||||||
|
|
||||||
// Create an instance of the Menu class
|
// Create an instance of the Menu class
|
||||||
let menu = Menu;
|
let menu = Menu;
|
||||||
|
|
||||||
// Create a menu bar template
|
// Create a menu bar template
|
||||||
const menuBar = [
|
const menuBar = [
|
||||||
{
|
{
|
||||||
label: 'File',
|
label: "File",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'New Window',
|
label: "New Window",
|
||||||
accelerator: 'CmdOrCtrl+N',
|
accelerator: "CmdOrCtrl+N",
|
||||||
click() {
|
click() {
|
||||||
if (mainWindow == null) {
|
if (mainWindow == null) {
|
||||||
registerProtocol();
|
registerProtocol();
|
||||||
|
@ -219,106 +230,110 @@ function createMenubar() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'New Post',
|
label: "New Post",
|
||||||
accelerator: 'Shift+CmdOrCtrl+N',
|
accelerator: "Shift+CmdOrCtrl+N",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#compose")
|
safelyGoTo("hyperspace://hyperspace/app/#compose");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: "Edit",
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'undo' },
|
{ role: "undo" },
|
||||||
{ role: 'redo' },
|
{ role: "redo" },
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{ role: 'cut' },
|
{ role: "cut" },
|
||||||
{ role: 'copy' },
|
{ role: "copy" },
|
||||||
{ role: 'paste' },
|
{ role: "paste" },
|
||||||
{ role: 'pasteandmatchstyle' },
|
{ role: "pasteandmatchstyle" },
|
||||||
{ role: 'delete' },
|
{ role: "delete" },
|
||||||
{ role: 'selectall' }
|
{ role: "selectall" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'View',
|
label: "View",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Back',
|
label: "Back",
|
||||||
accelerator: 'CmdOrCtrl+[',
|
accelerator: "CmdOrCtrl+[",
|
||||||
click() {
|
click() {
|
||||||
if (mainWindow != null && mainWindow.webContents.canGoBack()) {
|
if (
|
||||||
mainWindow.webContents.goBack()
|
mainWindow != null &&
|
||||||
|
mainWindow.webContents.canGoBack()
|
||||||
|
) {
|
||||||
|
mainWindow.webContents.goBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Forward',
|
label: "Forward",
|
||||||
accelerator: 'CmdOrCtrl+]',
|
accelerator: "CmdOrCtrl+]",
|
||||||
click() {
|
click() {
|
||||||
if (mainWindow != null && mainWindow.webContents.canGoForward()) {
|
if (
|
||||||
mainWindow.webContents.goForward()
|
mainWindow != null &&
|
||||||
|
mainWindow.webContents.canGoForward()
|
||||||
|
) {
|
||||||
|
mainWindow.webContents.goForward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ role: 'reload' },
|
{ role: "reload" },
|
||||||
{ role: 'forcereload' },
|
{ role: "forcereload" },
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: 'Open Dev Tools',
|
label: "Open Dev Tools",
|
||||||
click () {
|
click() {
|
||||||
try {
|
try {
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Couldn't open dev tools: " + err);
|
console.error("Couldn't open dev tools: " + err);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
accelerator: 'Shift+CmdOrCtrl+I'
|
accelerator: "Shift+CmdOrCtrl+I"
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{ role: 'togglefullscreen' }
|
{ role: "togglefullscreen" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Timelines",
|
label: "Timelines",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Home',
|
label: "Home",
|
||||||
accelerator: "CmdOrCtrl+0",
|
accelerator: "CmdOrCtrl+0",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/home")
|
safelyGoTo("hyperspace://hyperspace/app/#/home");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Local',
|
label: "Local",
|
||||||
accelerator: "CmdOrCtrl+1",
|
accelerator: "CmdOrCtrl+1",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/local")
|
safelyGoTo("hyperspace://hyperspace/app/#/local");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Public',
|
label: "Public",
|
||||||
accelerator: "CmdOrCtrl+2",
|
accelerator: "CmdOrCtrl+2",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/public")
|
safelyGoTo("hyperspace://hyperspace/app/#/public");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Messages',
|
label: "Messages",
|
||||||
accelerator: "CmdOrCtrl+3",
|
accelerator: "CmdOrCtrl+3",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/messages")
|
safelyGoTo("hyperspace://hyperspace/app/#/messages");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: 'Activity',
|
label: "Activity",
|
||||||
accelerator: 'Alt+CmdOrCtrl+A',
|
accelerator: "Alt+CmdOrCtrl+A",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/activity")
|
safelyGoTo("hyperspace://hyperspace/app/#/activity");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -327,127 +342,138 @@ function createMenubar() {
|
||||||
label: "Account",
|
label: "Account",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Notifications',
|
label: "Notifications",
|
||||||
accelerator: "Alt+CmdOrCtrl+N",
|
accelerator: "Alt+CmdOrCtrl+N",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/notifications")
|
safelyGoTo(
|
||||||
|
"hyperspace://hyperspace/app/#/notifications"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Recommendations',
|
label: "Recommendations",
|
||||||
accelerator: "Alt+CmdOrCtrl+R",
|
accelerator: "Alt+CmdOrCtrl+R",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/recommended")
|
safelyGoTo("hyperspace://hyperspace/app/#/recommended");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: 'Edit Profile',
|
label: "Edit Profile",
|
||||||
accelerator: "Shift+CmdOrCtrl+P",
|
accelerator: "Shift+CmdOrCtrl+P",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/you")
|
safelyGoTo("hyperspace://hyperspace/app/#/you");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Follow Requests',
|
label: "Follow Requests",
|
||||||
accelerator: "Alt+CmdOrCtrl+E",
|
accelerator: "Alt+CmdOrCtrl+E",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/requests")
|
safelyGoTo("hyperspace://hyperspace/app/#/requests");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Blocked Servers',
|
label: "Blocked Servers",
|
||||||
accelerator: "Shift+CmdOrCtrl+B",
|
accelerator: "Shift+CmdOrCtrl+B",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/blocked")
|
safelyGoTo("hyperspace://hyperspace/app/#/blocked");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator'},
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: 'Switch Accounts...',
|
label: "Switch Accounts...",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/welcome")
|
safelyGoTo("hyperspace://hyperspace/app/#/welcome");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'window',
|
role: "window",
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'minimize' },
|
{ role: "minimize" },
|
||||||
{ role: 'close' },
|
{ role: "close" },
|
||||||
{ type: 'separator' },
|
{ type: "separator" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'help',
|
role: "help",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Hyperspace Desktop Docs',
|
label: "Hyperspace Desktop Docs",
|
||||||
click () { require('electron').shell.openExternal('https://hyperspace.marquiskurt.net/docs/') }
|
click() {
|
||||||
|
require("electron").shell.openExternal(
|
||||||
|
"https://hyperspace.marquiskurt.net/docs/"
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Report a Bug',
|
label: "Report a Bug",
|
||||||
click () { require('electron').shell.openExternal('https://github.com/hyperspacedev/hyperspace/issues') }
|
click() {
|
||||||
|
require("electron").shell.openExternal(
|
||||||
|
"https://github.com/hyperspacedev/hyperspace/issues"
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: 'Acknowledgements',
|
label: "Acknowledgements",
|
||||||
click () { require('electron').shell.openExternal('https://github.com/hyperspacedev/hyperspace/blob/master/patreon.md') }
|
click() {
|
||||||
|
require("electron").shell.openExternal(
|
||||||
|
"https://github.com/hyperspacedev/hyperspace/blob/master/patreon.md"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === "darwin") {
|
||||||
menuBar.unshift({
|
menuBar.unshift({
|
||||||
label: app.getName(),
|
label: app.getName(),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: `About ${app.getName()}`,
|
label: `About ${app.getName()}`,
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/about")
|
safelyGoTo("hyperspace://hyperspace/app/#/about");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: "Preferences...",
|
label: "Preferences...",
|
||||||
accelerator: 'Cmd+,',
|
accelerator: "Cmd+,",
|
||||||
click() {
|
click() {
|
||||||
safelyGoTo("hyperspace://hyperspace/app/#/settings");
|
safelyGoTo("hyperspace://hyperspace/app/#/settings");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{ role: 'services' },
|
{ role: "services" },
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{ role: 'hide' },
|
{ role: "hide" },
|
||||||
{ role: 'hideothers' },
|
{ role: "hideothers" },
|
||||||
{ role: 'unhide' },
|
{ role: "unhide" },
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{ role: 'quit' }
|
{ role: "quit" }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edit menu
|
// Edit menu
|
||||||
menuBar[2].submenu.push(
|
menuBar[2].submenu.push(
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: 'Speech',
|
label: "Speech",
|
||||||
submenu: [
|
submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }]
|
||||||
{ role: 'startspeaking' },
|
|
||||||
{ role: 'stopspeaking' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Window menu
|
// Window menu
|
||||||
menuBar[6].submenu = [
|
menuBar[6].submenu = [
|
||||||
{ role: 'close' },
|
{ role: "close" },
|
||||||
{ role: 'minimize' },
|
{ role: "minimize" },
|
||||||
{ role: 'zoom' },
|
{ role: "zoom" },
|
||||||
{ type: 'separator' },
|
{ type: "separator" },
|
||||||
{ role: 'front' }
|
{ role: "front" }
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the template for the menu and attach it to the application
|
// Create the template for the menu and attach it to the application
|
||||||
|
@ -456,21 +482,21 @@ function createMenubar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the app is ready, create the window and menu bar
|
// When the app is ready, create the window and menu bar
|
||||||
app.on('ready', () => {
|
app.on("ready", () => {
|
||||||
registerProtocol();
|
registerProtocol();
|
||||||
createWindow();
|
createWindow();
|
||||||
createMenubar();
|
createMenubar();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Standard quit behavior changes for macOS
|
// Standard quit behavior changes for macOS
|
||||||
app.on('window-all-closed', () => {
|
app.on("window-all-closed", () => {
|
||||||
if (!isDarwin()) {
|
if (!isDarwin()) {
|
||||||
app.quit()
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// When the app is activated, create the window and menu bar
|
// When the app is activated, create the window and menu bar
|
||||||
app.on('activate', () => {
|
app.on("activate", () => {
|
||||||
if (mainWindow === null) {
|
if (mainWindow === null) {
|
||||||
createWindow();
|
createWindow();
|
||||||
createMenubar();
|
createMenubar();
|
||||||
|
|
|
@ -45,6 +45,7 @@ import { Account } from "../types/Account";
|
||||||
import { Relationship } from "../types/Relationship";
|
import { Relationship } from "../types/Relationship";
|
||||||
import { withSnackbar } from "notistack";
|
import { withSnackbar } from "notistack";
|
||||||
import { Dictionary } from "../interfaces/utils";
|
import { Dictionary } from "../interfaces/utils";
|
||||||
|
import { linkablePath } from "../utilities/desktop";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state interface for the notifications page.
|
* The state interface for the notifications page.
|
||||||
|
@ -616,7 +617,9 @@ class NotificationsPage extends Component<any, INotificationsPageState> {
|
||||||
style={{ textAlign: "center" }}
|
style={{ textAlign: "center" }}
|
||||||
>
|
>
|
||||||
<Typography>
|
<Typography>
|
||||||
<Link href="/#/settings#sp-notifications">
|
<Link
|
||||||
|
href={linkablePath("/#/settings#sp-notifications")}
|
||||||
|
>
|
||||||
Manage notification settings
|
Manage notification settings
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
|
@ -56,3 +56,11 @@ export function getElectronApp() {
|
||||||
const { remote } = eWin.require("electron");
|
const { remote } = eWin.require("electron");
|
||||||
return remote.app;
|
return remote.app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the linkable version of a path for the web and desktop.
|
||||||
|
* @param path The path to make a linkable version of
|
||||||
|
*/
|
||||||
|
export function linkablePath(path: string): string {
|
||||||
|
return isDesktopApp() ? "/app" + path : path;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue