mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-23 19:49:56 +01:00
desktop: add handler for nn:// urls
This commit is contained in:
@@ -96,6 +96,7 @@ module.exports = {
|
||||
"node_modules/sodium-native/package.json"
|
||||
],
|
||||
afterPack: "./scripts/removeLocales.js",
|
||||
protocols: [{ name: "Notesnook", schemes: ["nn"] }],
|
||||
mac: {
|
||||
bundleVersion: "240",
|
||||
minimumSystemVersion: "10.12.0",
|
||||
@@ -181,6 +182,7 @@ module.exports = {
|
||||
icon: "assets/icons/app.icns",
|
||||
description: "Your private note taking space",
|
||||
executableName: linuxExecutableName,
|
||||
mimeTypes: ["x-scheme-handler/nn"],
|
||||
desktop: {
|
||||
desktopActions: {
|
||||
"new-note": {
|
||||
|
||||
@@ -24,6 +24,7 @@ import TypedEventEmitter from "typed-emitter";
|
||||
|
||||
export type AppEvents = {
|
||||
onCreateItem(name: "note" | "notebook" | "reminder"): void;
|
||||
onOpenLink(url: string): void;
|
||||
};
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
@@ -31,7 +32,8 @@ const typedEmitter = emitter as TypedEventEmitter<AppEvents>;
|
||||
const t = initTRPC.create();
|
||||
|
||||
export const bridgeRouter = t.router({
|
||||
onCreateItem: createSubscription("onCreateItem")
|
||||
onCreateItem: createSubscription("onCreateItem"),
|
||||
onOpenLink: createSubscription("onOpenLink")
|
||||
});
|
||||
|
||||
export const bridge: AppEvents = new Proxy({} as AppEvents, {
|
||||
|
||||
@@ -52,6 +52,10 @@ locale.then(({ default: locale }) => {
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
// Pending nn:// link to open once the window is ready (used on Windows/Linux
|
||||
// when the app is launched via the nn:// protocol for the first time).
|
||||
let pendingNNLink: string | undefined = findNNLink(process.argv);
|
||||
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
console.log("Another instance is already running!");
|
||||
@@ -141,6 +145,11 @@ async function createWindow() {
|
||||
await mainWindow.webContents.loadURL(`${createURL(cliOptions, "/")}`);
|
||||
mainWindow.setOpacity(1);
|
||||
|
||||
if (pendingNNLink) {
|
||||
bridge.onOpenLink(pendingNNLink);
|
||||
pendingNNLink = undefined;
|
||||
}
|
||||
|
||||
if (config.privacyMode) {
|
||||
await api.integration.setPrivacyMode({ enabled: config.privacyMode });
|
||||
}
|
||||
@@ -196,6 +205,8 @@ app.once("ready", async () => {
|
||||
if (config.customDns) enableCustomDns();
|
||||
else disableCustomDns();
|
||||
|
||||
if (!MAC_APP_STORE) app.setAsDefaultProtocolClient("nn");
|
||||
|
||||
if (!isDevelopment()) registerProtocol();
|
||||
await createWindow();
|
||||
configureAutoUpdater();
|
||||
@@ -209,6 +220,12 @@ app.once("window-all-closed", () => {
|
||||
|
||||
app.on("second-instance", async (_ev, argv) => {
|
||||
if (!globalThis.window) return;
|
||||
const nnLink = findNNLink(argv);
|
||||
if (nnLink) {
|
||||
bridge.onOpenLink(nnLink);
|
||||
bringToFront();
|
||||
return;
|
||||
}
|
||||
const cliOptions = await parseArguments(argv);
|
||||
if (cliOptions.note) bridge.onCreateItem("note");
|
||||
if (cliOptions.notebook) bridge.onCreateItem("notebook");
|
||||
@@ -216,12 +233,29 @@ app.on("second-instance", async (_ev, argv) => {
|
||||
bringToFront();
|
||||
});
|
||||
|
||||
// macOS opens URLs via this event. The app may or may not be fully loaded yet.
|
||||
app.on("open-url", (event, url) => {
|
||||
event.preventDefault();
|
||||
if (!url.startsWith("nn://")) return;
|
||||
if (globalThis.window) {
|
||||
bridge.onOpenLink(url);
|
||||
bringToFront();
|
||||
} else {
|
||||
// Window not ready yet — store for when createWindow finishes loading.
|
||||
pendingNNLink = url;
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (globalThis.window === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
function findNNLink(argv: string[]): string | undefined {
|
||||
return argv.find((arg) => arg.startsWith("nn://"));
|
||||
}
|
||||
|
||||
function createURL(options: CLIOptions, path = "/") {
|
||||
const url = new URL(isDevelopment() ? "http://localhost:3000" : PROTOCOL_URL);
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ import {
|
||||
} from "./common";
|
||||
import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { db } from "./common/db";
|
||||
import { EVENTS } from "@notesnook/core";
|
||||
import { EVENTS, parseInternalLink } from "@notesnook/core";
|
||||
import { registerKeyMap } from "./common/key-map";
|
||||
import { updateStatus, removeStatus, getStatus } from "./hooks/use-status";
|
||||
import { hashNavigate } from "./navigation";
|
||||
import { hashNavigate, navigate } from "./navigation";
|
||||
import { desktop } from "./common/desktop-bridge";
|
||||
import { FeatureDialog } from "./dialogs/feature-dialog";
|
||||
import { AnnouncementDialog } from "./dialogs/announcement-dialog";
|
||||
@@ -224,6 +224,31 @@ export default function AppEffects() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } =
|
||||
desktop?.bridge.onOpenLink.subscribe(undefined, {
|
||||
onData(url) {
|
||||
const link = parseInternalLink(url);
|
||||
if (!link) return;
|
||||
if (link.type === "note") {
|
||||
useEditorStore.getState().openSession(link.id, {
|
||||
activeBlockId: link.params?.blockId || undefined
|
||||
});
|
||||
} else if (link.type === "notebook") {
|
||||
navigate(`/notebooks/${link.id}`);
|
||||
} else if (link.type === "tag") {
|
||||
navigate(`/tags/${link.id}`);
|
||||
} else if (link.type === "color") {
|
||||
navigate(`/colors/${link.id}`);
|
||||
}
|
||||
}
|
||||
}) || {};
|
||||
|
||||
return () => {
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <React.Fragment />;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,16 +17,24 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const InternalLinkTypes = ["note"] as const;
|
||||
const InternalLinkTypes = ["note", "notebook", "tag", "color"] as const;
|
||||
type InternalLinkType = (typeof InternalLinkTypes)[number];
|
||||
export type InternalLink<T extends InternalLinkType = InternalLinkType> = {
|
||||
type NoteLink = BaseInternalLink<"note">;
|
||||
type NotebookLink = BaseInternalLink<"notebook">;
|
||||
type TagLink = BaseInternalLink<"tag">;
|
||||
type ColorLink = BaseInternalLink<"color">;
|
||||
type BaseInternalLink<
|
||||
T extends InternalLinkType = InternalLinkType,
|
||||
TParams extends InternalLinkParams[T] = InternalLinkParams[T]
|
||||
> = {
|
||||
type: T;
|
||||
id: string;
|
||||
params?: Partial<InternalLinkParams[T]>;
|
||||
params?: Partial<TParams>;
|
||||
};
|
||||
export type InternalLink = NoteLink | NotebookLink | TagLink | ColorLink;
|
||||
export type InternalLinkWithOffset<
|
||||
T extends InternalLinkType = InternalLinkType
|
||||
> = InternalLink<T> & {
|
||||
> = BaseInternalLink<T> & {
|
||||
start: number;
|
||||
end: number;
|
||||
text: string;
|
||||
@@ -34,6 +42,9 @@ export type InternalLinkWithOffset<
|
||||
|
||||
type InternalLinkParams = {
|
||||
note: { blockId: string };
|
||||
notebook: {};
|
||||
tag: {};
|
||||
color: {};
|
||||
};
|
||||
export function createInternalLink<T extends InternalLinkType>(
|
||||
type: T,
|
||||
|
||||
Reference in New Issue
Block a user