diff --git a/apps/web/desktop/autolaunch.ts b/apps/web/desktop/autolaunch.ts
new file mode 100644
index 000000000..de858fb13
--- /dev/null
+++ b/apps/web/desktop/autolaunch.ts
@@ -0,0 +1,66 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+import { app } from "electron";
+import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
+import path from "path";
+
+const LINUX_DESKTOP_ENTRY = (hidden: boolean) => `[Desktop Entry]
+Type=Application
+Version=${app.getVersion()}
+Name=${app.getName()}
+Comment=${app.getName()} startup script
+Exec=${process.execPath}${hidden ? " --hidden" : ""}
+StartupNotify=false
+Terminal=false`;
+
+const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
+ app.getPath("home"),
+ ".config",
+ "autostart"
+);
+
+export class AutoLaunch {
+ static enable(hidden: boolean) {
+ if (process.platform === "linux") {
+ mkdirSync(LINUX_AUTOSTART_DIRECTORY_PATH, { recursive: true });
+ writeFileSync(
+ path.join(
+ LINUX_AUTOSTART_DIRECTORY_PATH,
+ `${app.getName().toLowerCase()}.desktop`
+ ),
+ LINUX_DESKTOP_ENTRY(hidden)
+ );
+ } else {
+ app.setLoginItemSettings({ openAtLogin: true, openAsHidden: hidden });
+ }
+ }
+
+ static disable() {
+ if (process.platform === "linux") {
+ const desktopFilePath = path.join(
+ LINUX_AUTOSTART_DIRECTORY_PATH,
+ `${app.getName().toLowerCase()}.desktop`
+ );
+ if (!existsSync(desktopFilePath)) return;
+ rmSync(desktopFilePath);
+ } else {
+ app.setLoginItemSettings({ openAtLogin: false, openAsHidden: false });
+ }
+ }
+}
diff --git a/apps/web/desktop/config/desktopIntegration.js b/apps/web/desktop/config/desktopIntegration.js
new file mode 100644
index 000000000..b7c26b49c
--- /dev/null
+++ b/apps/web/desktop/config/desktopIntegration.js
@@ -0,0 +1,35 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { JSONStorage } from "../jsonstorage";
+
+function getDesktopIntegration() {
+ return JSONStorage.get("desktopSettings", {
+ autoStart: false,
+ startMinimized: false,
+ minimizeToSystemTray: false,
+ closeToSystemTray: false
+ });
+}
+
+function setDesktopIntegration(settings) {
+ return JSONStorage.set("desktopSettings", settings);
+}
+
+export { getDesktopIntegration, setDesktopIntegration };
diff --git a/apps/web/desktop/electron.js b/apps/web/desktop/electron.js
index 537c1096a..99a1239b6 100644
--- a/apps/web/desktop/electron.js
+++ b/apps/web/desktop/electron.js
@@ -19,10 +19,8 @@ along with this program. If not, see .
/* global MAC_APP_STORE, RELEASE */
import "isomorphic-fetch";
-import { app, BrowserWindow, nativeTheme, shell } from "electron";
-import { join } from "path";
-import { platform } from "os";
-import { isDevelopment } from "./utils";
+import { app, BrowserWindow, Menu, nativeTheme, shell, Tray } from "electron";
+import { APP_ICON_PATH, isDevelopment } from "./utils";
import { registerProtocol, PROTOCOL_URL } from "./protocol";
import { configureAutoUpdater } from "./autoupdate";
import { getBackgroundColor, getTheme, setTheme } from "./config/theme";
@@ -36,6 +34,10 @@ import "./ipc/index.js";
import getPrivacyMode from "./ipc/calls/getPrivacyMode";
import setPrivacyMode from "./ipc/actions/setPrivacyMode";
import { getIsSpellCheckerEnabled } from "./config/spellChecker";
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { getDesktopIntegration } from "./config/desktopIntegration";
+import { AutoLaunch } from "./autolaunch";
if (!RELEASE) {
require("electron-reloader")(module);
@@ -51,20 +53,21 @@ if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
+var mainWindowState;
async function createWindow() {
- const mainWindowState = new WindowState({});
+ mainWindowState = new WindowState({});
setTheme(getTheme());
+
const mainWindow = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
+ fullscreen: mainWindowState.isFullScreen,
+
backgroundColor: getBackgroundColor(),
autoHideMenuBar: true,
- icon: join(
- __dirname,
- platform() === "win32" ? "app.ico" : "favicon-72x72.png"
- ),
+ icon: APP_ICON_PATH,
webPreferences: {
zoomFactor: getZoomFactor(),
devTools: true, // isDev,
@@ -76,10 +79,14 @@ async function createWindow() {
preload: __dirname + "/preload.js"
}
});
+
mainWindow.setAutoHideMenuBar(true);
mainWindowState.manage(mainWindow);
globalThis.window = mainWindow;
setupMenu();
+ setupJumplist();
+ setupTray();
+ setupDesktopIntegration();
if (isDevelopment())
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
@@ -88,10 +95,9 @@ async function createWindow() {
setPrivacyMode({ privacyMode: getPrivacyMode() });
}
+ const cliOptions = await parseCli();
try {
- await mainWindow.loadURL(
- isDevelopment() ? "http://localhost:3000" : PROTOCOL_URL
- );
+ await mainWindow.loadURL(`${createURL(cliOptions)}`);
} catch (e) {
logger.error(e);
}
@@ -134,3 +140,179 @@ app.on("activate", () => {
createWindow();
}
});
+
+function createURL(options) {
+ const url = new URL(isDevelopment() ? "http://localhost:3000" : PROTOCOL_URL);
+
+ if (options.note === true) url.hash = "/notes/create/1";
+ else if (options.notebook === true) url.hash = "/notebooks/create";
+ else if (options.reminder === true) url.hash = "/reminders/create";
+ else if (typeof options.note === "string")
+ url.hash = `/notes/${options.note}/edit`;
+ else if (typeof options.notebook === "string")
+ url.hash = `/notebooks/${options.notebook}`;
+
+ return url;
+}
+
+async function parseCli() {
+ const result = {
+ note: false,
+ notebook: false,
+ reminder: false
+ };
+ await yargs(hideBin(process.argv))
+ .command("new", "Create a new item", (yargs) => {
+ return yargs
+ .command("note", "Create a new note", {}, () => (result.note = true))
+ .command(
+ "notebook",
+ "Create a new notebook",
+ {},
+ () => (result.notebook = true)
+ )
+ .command(
+ "reminder",
+ "Add a new reminder",
+ {},
+ () => (result.reminder = true)
+ );
+ })
+ .command("open", "Open a specific item", (yargs) => {
+ return yargs
+ .command(
+ "note",
+ "Open a note",
+ { id: { string: true, description: "Id of the note" } },
+ (args) => (result.note = args.id)
+ )
+ .command(
+ "notebook",
+ "Open a notebook",
+ { id: { string: true, description: "Id of the notebook" } },
+ (args) => (result.notebook = args.id)
+ )
+ .command(
+ "topic",
+ "Open a topic",
+ {
+ id: { string: true, description: "Id of the topic" },
+ notebookId: { string: true, description: "Id of the notebook" }
+ },
+ (args) => (result.notebook = `${args.notebookId}/${args.id}`)
+ );
+ })
+ .parse();
+ return result;
+}
+
+function setupJumplist() {
+ if (process.platform === "win32") {
+ app.setJumpList([
+ { type: "frequent" },
+ {
+ type: "tasks",
+ items: [
+ {
+ program: process.execPath,
+ iconPath: process.execPath,
+ args: "new note",
+ description: "Create a new note",
+ title: "New note",
+ type: "task"
+ },
+ {
+ program: process.execPath,
+ iconPath: process.execPath,
+ args: "new notebook",
+ description: "Create a new notebook",
+ title: "New notebook",
+ type: "task"
+ },
+ {
+ program: process.execPath,
+ iconPath: process.execPath,
+ args: "new reminder",
+ description: "Add a new reminder",
+ title: "New reminder",
+ type: "task"
+ }
+ ]
+ }
+ ]);
+ }
+}
+
+function setupTray() {
+ const tray = new Tray(APP_ICON_PATH);
+
+ const contextMenu = Menu.buildFromTemplate([
+ {
+ label: "Show Notesnook",
+ type: "normal",
+ icon: APP_ICON_PATH,
+ click: showApp
+ },
+ { type: "separator" },
+ {
+ label: "New note",
+ type: "normal",
+ click: () => {
+ showApp();
+ sendMessageToRenderer(EVENTS.createItem, { itemType: "note" });
+ }
+ },
+ {
+ label: "New notebook",
+ type: "normal",
+ click: () => {
+ showApp();
+ sendMessageToRenderer(EVENTS.createItem, { itemType: "notebook" });
+ }
+ },
+ { type: "separator" },
+ {
+ label: "Quit Notesnook",
+ type: "normal",
+ click: () => {
+ app.exit(0);
+ }
+ }
+ ]);
+ tray.on("double-click", showApp);
+ tray.on("click", showApp);
+ tray.setToolTip("Notesnook");
+ tray.setContextMenu(contextMenu);
+}
+
+function showApp() {
+ if (globalThis.window.isMinimized()) {
+ if (mainWindowState.isMaximized) {
+ globalThis.window.maximize();
+ } else globalThis.window.restore();
+ }
+ globalThis.window.show();
+ globalThis.window.focus();
+ globalThis.window.moveTop();
+ globalThis.window.webContents.focus();
+}
+
+function setupDesktopIntegration() {
+ const desktopIntegration = getDesktopIntegration();
+
+ if (desktopIntegration.autoStart) {
+ AutoLaunch.enable(desktopIntegration.startMinimized);
+ }
+
+ globalThis.window.on("close", (e) => {
+ if (getDesktopIntegration().closeToSystemTray) {
+ e.preventDefault();
+ globalThis.window.minimize();
+ globalThis.window.hide();
+ }
+ });
+
+ globalThis.window.on("minimize", () => {
+ if (getDesktopIntegration().minimizeToSystemTray) globalThis.window.hide();
+ });
+}
diff --git a/apps/web/desktop/events.js b/apps/web/desktop/events.js
index b036ee6bc..f8a6d1cb4 100644
--- a/apps/web/desktop/events.js
+++ b/apps/web/desktop/events.js
@@ -24,5 +24,6 @@ export const EVENTS = {
updateDownloadCompleted: "updateDownloadCompleted",
updateNotAvailable: "updateNotAvailable",
themeChanged: "themeChanged",
- notificationClicked: "notificationClicked"
+ notificationClicked: "notificationClicked",
+ createItem: "createItem"
};
diff --git a/apps/web/desktop/ipc/actions/index.js b/apps/web/desktop/ipc/actions/index.js
index 40bb0d4a4..a1fbb220c 100644
--- a/apps/web/desktop/ipc/actions/index.js
+++ b/apps/web/desktop/ipc/actions/index.js
@@ -29,6 +29,7 @@ import showNotification from "./showNotification";
import bringToFront from "./bringToFront";
import setSpellCheckerLanguages from "./setSpellCheckerLanguages";
import toggleSpellChecker from "./toggleSpellChecker";
+import setDesktopIntegration from "./setDesktopIntegration";
const actions = {
changeAppTheme,
@@ -42,7 +43,8 @@ const actions = {
showNotification,
bringToFront,
setSpellCheckerLanguages,
- toggleSpellChecker
+ toggleSpellChecker,
+ setDesktopIntegration,
};
export function getAction(actionName) {
diff --git a/apps/web/desktop/ipc/actions/setDesktopIntegration.js b/apps/web/desktop/ipc/actions/setDesktopIntegration.js
new file mode 100644
index 000000000..e38162921
--- /dev/null
+++ b/apps/web/desktop/ipc/actions/setDesktopIntegration.js
@@ -0,0 +1,33 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { AutoLaunch } from "../../autolaunch";
+import { setDesktopIntegration } from "../../config/desktopIntegration";
+
+export default (args) => {
+ if (!globalThis.window) return;
+
+ if (args.autoStart) {
+ AutoLaunch.enable(args.startMinimized);
+ } else {
+ AutoLaunch.disable();
+ }
+
+ setDesktopIntegration(args);
+};
diff --git a/apps/web/desktop/ipc/calls/getDesktopIntegration.js b/apps/web/desktop/ipc/calls/getDesktopIntegration.js
new file mode 100644
index 000000000..9d6d9b604
--- /dev/null
+++ b/apps/web/desktop/ipc/calls/getDesktopIntegration.js
@@ -0,0 +1,24 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { getDesktopIntegration } from "../../config/desktopIntegration";
+
+export default function () {
+ return getDesktopIntegration();
+}
diff --git a/apps/web/desktop/ipc/calls/index.js b/apps/web/desktop/ipc/calls/index.js
index 02f0bbe24..e43efc46e 100644
--- a/apps/web/desktop/ipc/calls/index.js
+++ b/apps/web/desktop/ipc/calls/index.js
@@ -22,6 +22,7 @@ import getPrivacyMode from "./getPrivacyMode";
import selectDirectory from "./selectDirectory";
import { gunzip, gzip } from "./gzip";
import getSpellChecker from "./getSpellChecker";
+import getDesktopIntegration from "./getDesktopIntegration";
const calls = {
getZoomFactor,
@@ -29,7 +30,8 @@ const calls = {
selectDirectory,
gunzip,
gzip,
- getSpellChecker
+ getSpellChecker,
+ getDesktopIntegration,
};
export const getCall = function getAction(callName) {
diff --git a/apps/web/desktop/jsonstorage/index.js b/apps/web/desktop/jsonstorage/index.ts
similarity index 89%
rename from apps/web/desktop/jsonstorage/index.js
rename to apps/web/desktop/jsonstorage/index.ts
index 511acca62..93dd71561 100644
--- a/apps/web/desktop/jsonstorage/index.js
+++ b/apps/web/desktop/jsonstorage/index.ts
@@ -25,12 +25,12 @@ const directory = app.getPath("userData");
const filename = "config.json";
const filePath = join(directory, filename);
class JSONStorage {
- static get(key, def) {
+ static get(key: string, def?: T): T {
const json = this.readJson();
return json[key] === undefined ? def : json[key];
}
- static set(key, value) {
+ static set(key: string, value: unknown) {
const json = this.readJson();
json[key] = value;
this.writeJson(json);
@@ -40,10 +40,7 @@ class JSONStorage {
this.writeJson({});
}
- /**
- * @private
- */
- static readJson() {
+ private static readJson() {
try {
const json = readFileSync(filePath, "utf-8");
return JSON.parse(json);
@@ -53,10 +50,7 @@ class JSONStorage {
}
}
- /**
- * @private
- */
- static writeJson(json) {
+ private static writeJson(json: Record) {
try {
writeFileSync(filePath, JSON.stringify(json));
} catch (e) {
diff --git a/apps/web/desktop/package-lock.json b/apps/web/desktop/package-lock.json
index 5af09333d..7a731d2a9 100644
--- a/apps/web/desktop/package-lock.json
+++ b/apps/web/desktop/package-lock.json
@@ -10,7 +10,8 @@
"dependencies": {
"diary": "^0.3.1",
"electron-updater": "^5.3.0",
- "isomorphic-fetch": "^3.0.0"
+ "isomorphic-fetch": "^3.0.0",
+ "yargs": "^17.6.2"
},
"devDependencies": {
"@types/node-fetch": "^2.6.2",
@@ -514,7 +515,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -523,7 +523,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -1073,7 +1072,6 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
@@ -1096,7 +1094,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -1107,8 +1104,7 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/colors": {
"version": "1.0.3",
@@ -1801,8 +1797,7 @@
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
@@ -2209,7 +2204,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -2434,7 +2428,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
@@ -2846,7 +2839,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -3591,7 +3583,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3835,7 +3826,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -3849,7 +3839,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -4140,7 +4129,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -4172,7 +4160,6 @@
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
"engines": {
"node": ">=10"
}
@@ -4183,10 +4170,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yargs": {
- "version": "17.6.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
- "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
- "dev": true,
+ "version": "17.6.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
+ "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
@@ -4194,7 +4180,7 @@
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^21.0.0"
+ "yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
@@ -4204,7 +4190,6 @@
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "dev": true,
"engines": {
"node": ">=12"
}
@@ -4624,14 +4609,12 @@
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"requires": {
"color-convert": "^2.0.1"
}
@@ -5055,7 +5038,6 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
@@ -5075,7 +5057,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -5083,8 +5064,7 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"colors": {
"version": "1.0.3",
@@ -5642,8 +5622,7 @@
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"encodeurl": {
"version": "1.0.2",
@@ -5856,8 +5835,7 @@
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-string-regexp": {
"version": "4.0.0",
@@ -6024,8 +6002,7 @@
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.1.2",
@@ -6328,8 +6305,7 @@
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-glob": {
"version": "4.0.1",
@@ -6887,8 +6863,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"resolve": {
"version": "1.20.0",
@@ -7091,7 +7066,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -7102,7 +7076,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
@@ -7342,7 +7315,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -7364,8 +7336,7 @@
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yallist": {
"version": "4.0.0",
@@ -7373,10 +7344,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yargs": {
- "version": "17.6.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
- "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
- "dev": true,
+ "version": "17.6.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
+ "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
"requires": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
@@ -7384,14 +7354,13 @@
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^21.0.0"
+ "yargs-parser": "^21.1.1"
}
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "dev": true
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
},
"yauzl": {
"version": "2.10.0",
diff --git a/apps/web/desktop/package.json b/apps/web/desktop/package.json
index 1c80c9de3..c799ec716 100644
--- a/apps/web/desktop/package.json
+++ b/apps/web/desktop/package.json
@@ -11,7 +11,8 @@
"dependencies": {
"diary": "^0.3.1",
"electron-updater": "^5.3.0",
- "isomorphic-fetch": "^3.0.0"
+ "isomorphic-fetch": "^3.0.0",
+ "yargs": "^17.6.2"
},
"devDependencies": {
"@types/node-fetch": "^2.6.2",
diff --git a/apps/web/desktop/preload.js b/apps/web/desktop/preload.js
index d9cd5b7d8..7341702ef 100644
--- a/apps/web/desktop/preload.js
+++ b/apps/web/desktop/preload.js
@@ -61,6 +61,11 @@ contextBridge.exposeInMainWorld("config", {
return ipcRenderer.invoke("fromRenderer", {
type: "getSpellChecker"
});
+ },
+ desktopIntegration: () => {
+ return ipcRenderer.invoke("fromRenderer", {
+ type: "getDesktopIntegration"
+ });
}
});
diff --git a/apps/web/desktop/utils.js b/apps/web/desktop/utils.js
index 0d22fa244..1c8135354 100644
--- a/apps/web/desktop/utils.js
+++ b/apps/web/desktop/utils.js
@@ -21,6 +21,11 @@ import { app } from "electron";
import { join } from "path";
import { statSync } from "fs";
+const APP_ICON_PATH = join(
+ __dirname,
+ process.platform === "win32" ? "app.ico" : "favicon-72x72.png"
+);
+
function isDevelopment() {
if (typeof electron === "string") {
throw new TypeError("Not running in an Electron environment!");
@@ -47,4 +52,4 @@ function getPath(filePath) {
}
}
-export { getPath, isDevelopment };
+export { getPath, isDevelopment, APP_ICON_PATH };
diff --git a/apps/web/src/app-effects.js b/apps/web/src/app-effects.js
index ecc29fff2..4441e39bb 100644
--- a/apps/web/src/app-effects.js
+++ b/apps/web/src/app-effects.js
@@ -29,6 +29,7 @@ import { introduceFeatures, showUpgradeReminderDialogs } from "./common";
import { AppEventManager, AppEvents } from "./common/app-events";
import { db } from "./common/db";
import { EV, EVENTS } from "@notesnook/core/common";
+import { EVENTS as DESKTOP_APP_EVENTS } from "@notesnook/desktop/events";
import { registerKeyMap } from "./common/key-map";
import { isUserPremium } from "./hooks/use-is-user-premium";
import useAnnouncements from "./hooks/use-announcements";
@@ -45,6 +46,7 @@ import { updateStatus, removeStatus, getStatus } from "./hooks/use-status";
import { showToast } from "./utils/toast";
import { interruptedOnboarding } from "./components/dialogs/onboarding-dialog";
import { WebExtensionRelay } from "./utils/web-extension-relay";
+import { hashNavigate } from "./navigation";
const relay = new WebExtensionRelay();
@@ -197,6 +199,22 @@ export default function AppEffects({ setShow }) {
setTheme(isSystemThemeDark ? "dark" : "light");
}, [isSystemThemeDark, followSystemTheme, setTheme]);
+ useEffect(() => {
+ AppEventManager.subscribe(DESKTOP_APP_EVENTS.createItem, ({ itemType }) => {
+ switch (itemType) {
+ case "note":
+ hashNavigate("/notes/create", { addNonce: true, replace: true });
+ break;
+ case "notebook":
+ hashNavigate("/notebooks/create", { replace: true });
+ break;
+ case "reminder":
+ hashNavigate("/reminders/create", { replace: true });
+ break;
+ }
+ });
+ }, []);
+
return ;
}
diff --git a/apps/web/src/commands/set-desktop-integration.ts b/apps/web/src/commands/set-desktop-integration.ts
new file mode 100644
index 000000000..568ddc826
--- /dev/null
+++ b/apps/web/src/commands/set-desktop-integration.ts
@@ -0,0 +1,26 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { invokeCommand } from "./index";
+
+export default function setDesktopIntegration(
+ settings: DesktopIntegrationSettings
+) {
+ invokeCommand("setDesktopIntegration", settings);
+}
diff --git a/apps/web/src/common/index.js b/apps/web/src/common/index.js
index 088585578..e92fa9348 100644
--- a/apps/web/src/common/index.js
+++ b/apps/web/src/common/index.js
@@ -47,19 +47,19 @@ export const CREATE_BUTTON_MAP = {
},
notebooks: {
title: "Create a notebook",
- onClick: () => hashNavigate("/notebooks/create")
+ onClick: () => hashNavigate("/notebooks/create", { replace: true })
},
topics: {
title: "Create a topic",
- onClick: () => hashNavigate(`/topics/create`)
+ onClick: () => hashNavigate(`/topics/create`, { replace: true })
},
tags: {
title: "Create a tag",
- onClick: () => hashNavigate(`/tags/create`)
+ onClick: () => hashNavigate(`/tags/create`, { replace: true })
},
reminders: {
title: "Add a reminder",
- onClick: () => hashNavigate(`/reminders/create`)
+ onClick: () => hashNavigate(`/reminders/create`, { replace: true })
}
};
diff --git a/apps/web/src/global.d.ts b/apps/web/src/global.d.ts
index b76744c37..15fb1e490 100644
--- a/apps/web/src/global.d.ts
+++ b/apps/web/src/global.d.ts
@@ -23,9 +23,18 @@ type SpellCheckerOptions = {
enabledLanguages: Language[];
enabled: boolean;
};
+
+type DesktopIntegrationSettings = {
+ autoStart: boolean;
+ startMinimized: boolean;
+ minimizeToSystemTray: boolean;
+ closeToSystemTray: boolean;
+};
+
declare interface Window {
config: {
static spellChecker(): Promise;
+ static desktopIntegration(): Promise;
};
native: {
static gzip({
diff --git a/apps/web/src/hooks/use-desktop-integration.ts b/apps/web/src/hooks/use-desktop-integration.ts
new file mode 100644
index 000000000..ae531d6db
--- /dev/null
+++ b/apps/web/src/hooks/use-desktop-integration.ts
@@ -0,0 +1,50 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { useCallback, useEffect, useState } from "react";
+import setDesktopIntegration from "../commands/set-desktop-integration";
+
+export default function useDesktopIntegration() {
+ const [settings, changeSettings] = useState();
+
+ const setupDesktopIntegration = useCallback(async () => {
+ const settings = await window.config.desktopIntegration();
+ changeSettings(settings);
+ return settings;
+ }, []);
+
+ useEffect(() => {
+ if (!window.config) return;
+ (async function () {
+ await setupDesktopIntegration();
+ })();
+ }, [setupDesktopIntegration]);
+
+ const set = useCallback(
+ async (_settings: Partial) => {
+ if (!settings) return;
+
+ setDesktopIntegration({ ...settings, ..._settings });
+ await setupDesktopIntegration();
+ },
+ [settings, setupDesktopIntegration]
+ );
+
+ return [settings, set] as const;
+}
diff --git a/apps/web/src/views/settings.js b/apps/web/src/views/settings.js
index 98e32f7ed..d68a7b15e 100644
--- a/apps/web/src/views/settings.js
+++ b/apps/web/src/views/settings.js
@@ -77,6 +77,7 @@ import { scheduleBackups } from "../common/reminders";
import usePrivacyMode from "../hooks/use-privacy-mode";
import { useTelemetry } from "../hooks/use-telemetry";
import useSpellChecker from "../hooks/use-spell-checker";
+import useDesktopIntegration from "../hooks/use-desktop-integration";
function subscriptionStatusToString(user) {
const status = user?.subscription?.type;
@@ -147,6 +148,7 @@ function Settings() {
privacy: false,
developer: false,
notifications: false,
+ desktop: false,
other: true
});
const isVaultCreated = useAppStore((store) => store.isVaultCreated);
@@ -196,6 +198,8 @@ function Settings() {
const [privacyMode, setPrivacyMode] = usePrivacyMode();
const [showReminderNotifications, setShowReminderNotifications] =
usePersistentState("reminderNotifications", true);
+ const [desktopIntegration, changeDesktopIntegration] =
+ useDesktopIntegration();
const [corsProxy, setCorsProxy] = usePersistentState(
"corsProxy",
"https://cors.notesnook.com"
@@ -534,6 +538,61 @@ function Settings() {
>
)}
+ {isDesktop() && (
+ <>
+ {
+ setGroups((g) => ({ ...g, desktop: !g.desktop }));
+ }}
+ />
+ {groups.desktop && (
+ <>
+ {
+ changeDesktopIntegration({
+ autoStart: !desktopIntegration.autoStart
+ });
+ }}
+ isToggled={desktopIntegration.autoStart}
+ />
+ {desktopIntegration.autoStart && (
+ {
+ changeDesktopIntegration({
+ startMinimized: !desktopIntegration.startMinimized
+ });
+ }}
+ isToggled={desktopIntegration.startMinimized}
+ />
+ )}
+ {
+ changeDesktopIntegration({
+ minimizeToSystemTray:
+ !desktopIntegration.minimizeToSystemTray
+ });
+ }}
+ isToggled={desktopIntegration.minimizeToSystemTray}
+ />
+ {
+ changeDesktopIntegration({
+ closeToSystemTray: !desktopIntegration.closeToSystemTray
+ });
+ }}
+ isToggled={desktopIntegration.closeToSystemTray}
+ />
+ >
+ )}
+ >
+ )}
+