mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			230 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { Menu, Tray } from "electron";
 | 
						|
import path from "path";
 | 
						|
import windowService from "./window.js";
 | 
						|
import optionService from "./options.js";
 | 
						|
import { fileURLToPath } from "url";
 | 
						|
import type { KeyboardActionNames } from "./keyboard_actions_interface.js";
 | 
						|
import date_notes from "./date_notes.js";
 | 
						|
import type BNote from "../becca/entities/bnote.js";
 | 
						|
import becca from "../becca/becca.js";
 | 
						|
import becca_service from "../becca/becca_service.js";
 | 
						|
import type BRecentNote from "../becca/entities/brecent_note.js";
 | 
						|
import { ipcMain, nativeTheme } from "electron/main";
 | 
						|
import { default as i18next, t } from "i18next";
 | 
						|
import { isDev, isMac } from "./utils.js";
 | 
						|
import cls from "./cls.js";
 | 
						|
 | 
						|
let tray: Tray;
 | 
						|
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
 | 
						|
// is minimized
 | 
						|
let isVisible = true;
 | 
						|
 | 
						|
function getTrayIconPath() {
 | 
						|
    let name: string;
 | 
						|
    if (isMac) {
 | 
						|
        name = "icon-blackTemplate";
 | 
						|
    } else if (isDev) {
 | 
						|
        name = "icon-purple";
 | 
						|
    } else {
 | 
						|
        name = "icon-color";
 | 
						|
    }
 | 
						|
 | 
						|
    return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}.png`);
 | 
						|
}
 | 
						|
 | 
						|
function getIconPath(name: string) {
 | 
						|
    const suffix = !isMac && nativeTheme.shouldUseDarkColors ? "-inverted" : "";
 | 
						|
    return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}Template${suffix}.png`);
 | 
						|
}
 | 
						|
 | 
						|
function registerVisibilityListener() {
 | 
						|
    const mainWindow = windowService.getMainWindow();
 | 
						|
    if (!mainWindow) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    // They need to be registered before the tray updater is registered
 | 
						|
    mainWindow.on("show", () => {
 | 
						|
        isVisible = true;
 | 
						|
        updateTrayMenu();
 | 
						|
    });
 | 
						|
    mainWindow.on("hide", () => {
 | 
						|
        isVisible = false;
 | 
						|
        updateTrayMenu();
 | 
						|
    });
 | 
						|
 | 
						|
    mainWindow.on("minimize", updateTrayMenu);
 | 
						|
    mainWindow.on("maximize", updateTrayMenu);
 | 
						|
    if (!isMac) {
 | 
						|
        // macOS uses template icons which work great on dark & light themes.
 | 
						|
        nativeTheme.on("updated", updateTrayMenu);
 | 
						|
    }
 | 
						|
    ipcMain.on("reload-tray", updateTrayMenu);
 | 
						|
    i18next.on("languageChanged", updateTrayMenu);
 | 
						|
}
 | 
						|
 | 
						|
function updateTrayMenu() {
 | 
						|
    const mainWindow = windowService.getMainWindow();
 | 
						|
    if (!mainWindow) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    function ensureVisible() {
 | 
						|
        if (mainWindow) {
 | 
						|
            mainWindow.show();
 | 
						|
            mainWindow.focus();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    function triggerKeyboardAction(actionName: KeyboardActionNames) {
 | 
						|
        mainWindow?.webContents.send("globalShortcut", actionName);
 | 
						|
        ensureVisible();
 | 
						|
    }
 | 
						|
 | 
						|
    function openInSameTab(note: BNote | BRecentNote) {
 | 
						|
        mainWindow?.webContents.send("openInSameTab", note.noteId);
 | 
						|
        ensureVisible();
 | 
						|
    }
 | 
						|
 | 
						|
    function buildBookmarksMenu() {
 | 
						|
        const parentNote = becca.getNoteOrThrow("_lbBookmarks");
 | 
						|
        const menuItems: Electron.MenuItemConstructorOptions[] = [];
 | 
						|
 | 
						|
        for (const bookmarkNote of parentNote?.children) {
 | 
						|
            if (bookmarkNote.isLabelTruthy("bookmarkFolder")) {
 | 
						|
                menuItems.push({
 | 
						|
                    label: bookmarkNote.title,
 | 
						|
                    type: "submenu",
 | 
						|
                    submenu: bookmarkNote.children.map((subitem) => {
 | 
						|
                        return {
 | 
						|
                            label: subitem.title,
 | 
						|
                            type: "normal",
 | 
						|
                            click: () => openInSameTab(subitem)
 | 
						|
                        };
 | 
						|
                    })
 | 
						|
                });
 | 
						|
            } else {
 | 
						|
                menuItems.push({
 | 
						|
                    label: bookmarkNote.title,
 | 
						|
                    type: "normal",
 | 
						|
                    click: () => openInSameTab(bookmarkNote)
 | 
						|
                });
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return menuItems;
 | 
						|
    }
 | 
						|
 | 
						|
    function buildRecentNotesMenu() {
 | 
						|
        const recentNotes = becca.getRecentNotesFromQuery(`
 | 
						|
            SELECT recent_notes.*
 | 
						|
            FROM recent_notes
 | 
						|
            JOIN notes USING(noteId)
 | 
						|
            WHERE notes.isDeleted = 0
 | 
						|
            ORDER BY utcDateCreated DESC
 | 
						|
            LIMIT 10
 | 
						|
        `);
 | 
						|
        const menuItems: Electron.MenuItemConstructorOptions[] = [];
 | 
						|
        const formatter = new Intl.DateTimeFormat(undefined, {
 | 
						|
            dateStyle: "short",
 | 
						|
            timeStyle: "short"
 | 
						|
        });
 | 
						|
 | 
						|
        for (const recentNote of recentNotes) {
 | 
						|
            const date = new Date(recentNote.utcDateCreated);
 | 
						|
 | 
						|
            menuItems.push({
 | 
						|
                label: becca_service.getNoteTitle(recentNote.noteId),
 | 
						|
                type: "normal",
 | 
						|
                sublabel: formatter.format(date),
 | 
						|
                click: () => openInSameTab(recentNote)
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        return menuItems;
 | 
						|
    }
 | 
						|
 | 
						|
    const contextMenu = Menu.buildFromTemplate([
 | 
						|
        {
 | 
						|
            label: t("tray.show-windows"),
 | 
						|
            type: "checkbox",
 | 
						|
            checked: isVisible,
 | 
						|
            click: () => {
 | 
						|
                if (isVisible) {
 | 
						|
                    mainWindow.hide();
 | 
						|
                } else {
 | 
						|
                    ensureVisible();
 | 
						|
                }
 | 
						|
            }
 | 
						|
        },
 | 
						|
        { type: "separator" },
 | 
						|
        {
 | 
						|
            label: t("tray.new-note"),
 | 
						|
            type: "normal",
 | 
						|
            icon: getIconPath("new-note"),
 | 
						|
            click: () => triggerKeyboardAction("createNoteIntoInbox")
 | 
						|
        },
 | 
						|
        {
 | 
						|
            label: t("tray.today"),
 | 
						|
            type: "normal",
 | 
						|
            icon: getIconPath("today"),
 | 
						|
            click: cls.wrap(() => openInSameTab(date_notes.getTodayNote()))
 | 
						|
        },
 | 
						|
        {
 | 
						|
            label: t("tray.bookmarks"),
 | 
						|
            type: "submenu",
 | 
						|
            icon: getIconPath("bookmarks"),
 | 
						|
            submenu: buildBookmarksMenu()
 | 
						|
        },
 | 
						|
        {
 | 
						|
            label: t("tray.recents"),
 | 
						|
            type: "submenu",
 | 
						|
            icon: getIconPath("recents"),
 | 
						|
            submenu: buildRecentNotesMenu()
 | 
						|
        },
 | 
						|
        { type: "separator" },
 | 
						|
        {
 | 
						|
            label: t("tray.close"),
 | 
						|
            type: "normal",
 | 
						|
            icon: getIconPath("close"),
 | 
						|
            click: () => {
 | 
						|
                mainWindow.close();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    ]);
 | 
						|
 | 
						|
    tray?.setContextMenu(contextMenu);
 | 
						|
}
 | 
						|
 | 
						|
function changeVisibility() {
 | 
						|
    const window = windowService.getMainWindow();
 | 
						|
    if (!window) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (isVisible) {
 | 
						|
        window.hide();
 | 
						|
    } else {
 | 
						|
        window.show();
 | 
						|
        window.focus();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function createTray() {
 | 
						|
    if (optionService.getOptionBool("disableTray")) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    tray = new Tray(getTrayIconPath());
 | 
						|
    tray.setToolTip(t("tray.tooltip"));
 | 
						|
    // Restore focus
 | 
						|
    tray.on("click", changeVisibility);
 | 
						|
    updateTrayMenu();
 | 
						|
 | 
						|
    registerVisibilityListener();
 | 
						|
}
 | 
						|
 | 
						|
export default {
 | 
						|
    createTray
 | 
						|
};
 |