mirror of
https://github.com/zadam/trilium.git
synced 2025-11-04 20:36:13 +01:00
Merge branch 'develop' into feature/MFA
This commit is contained in:
@@ -190,8 +190,11 @@
|
||||
--right-pane-item-hover-background: #ffffff26;
|
||||
--right-pane-item-hover-color: white;
|
||||
|
||||
--scrollbar-border-color: #666;
|
||||
--scrollbar-background-color: #333;
|
||||
--scrollbar-thumb-color: #fdfdfd5c;
|
||||
--scrollbar-thumb-color-hover: #ffffff7d;
|
||||
--scrollbar-border-color: unset; /* Deprecated */
|
||||
--scrollbar-background-color: unset; /* Deprecated */
|
||||
|
||||
--link-color: lightskyblue;
|
||||
|
||||
--mermaid-theme: dark;
|
||||
|
||||
@@ -189,8 +189,11 @@
|
||||
--right-pane-item-hover-background: #ececec;
|
||||
--right-pane-item-hover-color: inherit;
|
||||
|
||||
--scrollbar-border-color: #ddd;
|
||||
--scrollbar-background-color: #ddd;
|
||||
--scrollbar-thumb-color: #0000005c;
|
||||
--scrollbar-thumb-color-hover: #00000066;
|
||||
--scrollbar-border-color: unset; /* Deprecated */
|
||||
--scrollbar-background-color: unset; /* Deprecated */
|
||||
|
||||
--link-color: blue;
|
||||
|
||||
--mermaid-theme: default;
|
||||
|
||||
@@ -660,3 +660,83 @@ a.tn-link:hover[href^="https://"]:not(.no-arrow)::after,
|
||||
input[type="range"] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
* WebKit scrollbars
|
||||
*/
|
||||
|
||||
:root {
|
||||
--scrollbar-thickness: 10px;
|
||||
--scrollbar-thumb-thickness: 3px;
|
||||
--scrollbar-thumb-hover-thickness: 6px;
|
||||
--scrollbar-start-end-gap: 8px;
|
||||
}
|
||||
|
||||
/* Scrollbar's body */
|
||||
|
||||
::-webkit-scrollbar:vertical {
|
||||
width: var(--scrollbar-thickness) !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar:horizontal {
|
||||
height: var(--scrollbar-thickness) !important;
|
||||
}
|
||||
|
||||
/* Scrollbar's thumb */
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
--s-thumb-thickness: var(--scrollbar-thumb-thickness);
|
||||
--s-thumb-color: var(--scrollbar-thumb-color);
|
||||
|
||||
--s-gradient-angle: 90deg;
|
||||
--s-gradient-p1: calc((var(--scrollbar-thickness) - var(--s-thumb-thickness)) / 2);
|
||||
--s-gradient-p2: calc(var(--s-gradient-p1) + var(--s-thumb-thickness));
|
||||
|
||||
border: none !important;
|
||||
background: linear-gradient(var(--s-gradient-angle),
|
||||
transparent, transparent var(--s-gradient-p1),
|
||||
var(--s-thumb-color) 0px, var(--s-thumb-color) var(--s-gradient-p2),
|
||||
transparent 0) !important;
|
||||
|
||||
border-radius: calc(var(--scrollbar-thickness) / 2) !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:horizontal {
|
||||
--s-gradient-angle: 0deg;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
--s-thumb-thickness: var(--scrollbar-thumb-hover-thickness);
|
||||
--s-thumb-color: var(--scrollbar-thumb-color-hover);
|
||||
}
|
||||
|
||||
/* Scrollbar's increment/decrement buttons (repurposed as a scrollbar start/end gap) */
|
||||
|
||||
::-webkit-scrollbar-button:vertical {
|
||||
height: var(--scrollbar-start-end-gap);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:horizontal {
|
||||
width: var(--scrollbar-start-end-gap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Firefox scrollbars
|
||||
*
|
||||
* Unsupported features: --scrollbar-thumb-thickness, --scrollbar-thumb-hover-thickness,
|
||||
* --scrollbar-start-end-gap, --scrollbar-thumb-color-hover.
|
||||
*/
|
||||
|
||||
:root {
|
||||
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
||||
scrollbar-width: var(--scrollbar-thickness);
|
||||
}
|
||||
|
||||
@supports selector(::-webkit-scrollbar) {
|
||||
/* Prevent the scrollbar-color and scrollbar-width properties to override the custom styles
|
||||
* defined using ::-webkit-scrollbar. */
|
||||
:root {
|
||||
scrollbar-color: unset;
|
||||
scrollbar-width: unset;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,12 @@ function getOptionOrNull(name: OptionNames): string | null {
|
||||
option = becca.getOption(name);
|
||||
} else {
|
||||
// e.g. in initial sync becca is not loaded because DB is not initialized
|
||||
option = sql.getRow<OptionRow>("SELECT * FROM options WHERE name = ?", [name]);
|
||||
try {
|
||||
option = sql.getRow<OptionRow>("SELECT * FROM options WHERE name = ?", [name]);
|
||||
} catch (e: unknown) {
|
||||
// DB is not initialized.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return option ? option.value : null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Menu, Tray } from "electron";
|
||||
import { Menu, Tray, BrowserWindow } from "electron";
|
||||
import path from "path";
|
||||
import windowService from "./window.js";
|
||||
import optionService from "./options.js";
|
||||
@@ -17,7 +17,7 @@ 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;
|
||||
let windowVisibilityMap: Record<number, boolean> = {};; // Dictionary for storing window ID and its visibility status
|
||||
|
||||
function getTrayIconPath() {
|
||||
let name: string;
|
||||
@@ -37,53 +37,93 @@ function getIconPath(name: string) {
|
||||
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) {
|
||||
function registerVisibilityListener(window: BrowserWindow) {
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// They need to be registered before the tray updater is registered
|
||||
mainWindow.on("show", () => {
|
||||
isVisible = true;
|
||||
window.on("show", () => {
|
||||
windowVisibilityMap[window.id] = true;
|
||||
updateTrayMenu();
|
||||
});
|
||||
mainWindow.on("hide", () => {
|
||||
isVisible = false;
|
||||
window.on("hide", () => {
|
||||
windowVisibilityMap[window.id] = 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);
|
||||
window.on("minimize", updateTrayMenu);
|
||||
window.on("maximize", updateTrayMenu);
|
||||
}
|
||||
|
||||
function updateTrayMenu() {
|
||||
const mainWindow = windowService.getMainWindow();
|
||||
if (!mainWindow) {
|
||||
function getWindowTitle(window: BrowserWindow | null) {
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
const title = window.getTitle();
|
||||
const titleWithoutAppName = title.replace(/\s-\s[^-]+$/, ''); // Remove the name of the app
|
||||
|
||||
function ensureVisible() {
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
// Limit title maximum length to 17
|
||||
if (titleWithoutAppName.length > 20) {
|
||||
return titleWithoutAppName.substring(0, 17) + '...';
|
||||
}
|
||||
|
||||
return titleWithoutAppName;
|
||||
}
|
||||
|
||||
function updateWindowVisibilityMap(allWindows: BrowserWindow[]) {
|
||||
const currentWindowIds: number[] = allWindows.map(window => window.id);
|
||||
|
||||
// Deleting closed windows from windowVisibilityMap
|
||||
for (const [id, visibility] of Object.entries(windowVisibilityMap)) {
|
||||
const windowId = Number(id);
|
||||
if (!currentWindowIds.includes(windowId)) {
|
||||
delete windowVisibilityMap[windowId];
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through allWindows to make sure the ID of each window exists in windowVisibilityMap
|
||||
allWindows.forEach(window => {
|
||||
const windowId = window.id;
|
||||
if (!(windowId in windowVisibilityMap)) {
|
||||
// If it does not exist, it is the newly created window
|
||||
windowVisibilityMap[windowId] = true;
|
||||
registerVisibilityListener(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function updateTrayMenu() {
|
||||
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
||||
const allWindows = windowService.getAllWindows();
|
||||
updateWindowVisibilityMap(allWindows);
|
||||
|
||||
function ensureVisible(win: BrowserWindow) {
|
||||
if (win) {
|
||||
win.show();
|
||||
win.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function openNewWindow() {
|
||||
if (lastFocusedWindow){
|
||||
lastFocusedWindow.webContents.send("globalShortcut", "openNewWindow");
|
||||
}
|
||||
}
|
||||
|
||||
function triggerKeyboardAction(actionName: KeyboardActionNames) {
|
||||
mainWindow?.webContents.send("globalShortcut", actionName);
|
||||
ensureVisible();
|
||||
if (lastFocusedWindow){
|
||||
lastFocusedWindow.webContents.send("globalShortcut", actionName);
|
||||
ensureVisible(lastFocusedWindow);
|
||||
}
|
||||
}
|
||||
|
||||
function openInSameTab(note: BNote | BRecentNote) {
|
||||
mainWindow?.webContents.send("openInSameTab", note.noteId);
|
||||
ensureVisible();
|
||||
if (lastFocusedWindow){
|
||||
lastFocusedWindow.webContents.send("openInSameTab", note.noteId);
|
||||
ensureVisible(lastFocusedWindow);
|
||||
}
|
||||
}
|
||||
|
||||
function buildBookmarksMenu() {
|
||||
@@ -144,20 +184,44 @@ function updateTrayMenu() {
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: t("tray.show-windows"),
|
||||
const windowVisibilityMenuItems: Electron.MenuItemConstructorOptions[] = [];
|
||||
|
||||
// Only call getWindowTitle if windowVisibilityMap has more than one window
|
||||
const showTitle = Object.keys(windowVisibilityMap).length > 1;
|
||||
|
||||
for (const idStr in windowVisibilityMap) {
|
||||
const id = parseInt(idStr, 10); // Get the ID of the window and make sure it is a number
|
||||
const isVisible = windowVisibilityMap[id];
|
||||
const win = allWindows.find(w => w.id === id);
|
||||
if (!win) {
|
||||
continue;
|
||||
}
|
||||
windowVisibilityMenuItems.push({
|
||||
label: showTitle ? `${t("tray.show-windows")}: ${getWindowTitle(win)}` : t("tray.show-windows"),
|
||||
type: "checkbox",
|
||||
checked: isVisible,
|
||||
click: () => {
|
||||
if (isVisible) {
|
||||
mainWindow.hide();
|
||||
win.hide();
|
||||
windowVisibilityMap[id] = false;
|
||||
} else {
|
||||
ensureVisible();
|
||||
ensureVisible(win);
|
||||
windowVisibilityMap[id] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
...windowVisibilityMenuItems,
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: t("tray.open_new_window"),
|
||||
type: "normal",
|
||||
icon: getIconPath("new-window"),
|
||||
click: () => openNewWindow()
|
||||
},
|
||||
{
|
||||
label: t("tray.new-note"),
|
||||
type: "normal",
|
||||
@@ -188,7 +252,10 @@ function updateTrayMenu() {
|
||||
type: "normal",
|
||||
icon: getIconPath("close"),
|
||||
click: () => {
|
||||
mainWindow.close();
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
windows.forEach(window => {
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -197,16 +264,18 @@ function updateTrayMenu() {
|
||||
}
|
||||
|
||||
function changeVisibility() {
|
||||
const window = windowService.getMainWindow();
|
||||
if (!window) {
|
||||
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
||||
|
||||
if (!lastFocusedWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
window.hide();
|
||||
// If the window is visible, hide it
|
||||
if (windowVisibilityMap[lastFocusedWindow.id]) {
|
||||
lastFocusedWindow.hide();
|
||||
} else {
|
||||
window.show();
|
||||
window.focus();
|
||||
lastFocusedWindow.show();
|
||||
lastFocusedWindow.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +290,15 @@ function createTray() {
|
||||
tray.on("click", changeVisibility);
|
||||
updateTrayMenu();
|
||||
|
||||
registerVisibilityListener();
|
||||
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);
|
||||
}
|
||||
|
||||
export default {
|
||||
createTray
|
||||
createTray,
|
||||
updateTrayMenu
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import remoteMain from "@electron/remote/main/index.js";
|
||||
import { BrowserWindow, shell, type App, type BrowserWindowConstructorOptions, type WebContents } from "electron";
|
||||
import { dialog, ipcMain } from "electron";
|
||||
import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js";
|
||||
import tray from "./tray.js";
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
@@ -19,6 +20,26 @@ import { t } from "i18next";
|
||||
// Prevent the window being garbage collected
|
||||
let mainWindow: BrowserWindow | null;
|
||||
let setupWindow: BrowserWindow | null;
|
||||
let allWindows: BrowserWindow[] = []; // // Used to store all windows, sorted by the order of focus.
|
||||
|
||||
function trackWindowFocus(win: BrowserWindow) {
|
||||
// We need to get the last focused window from allWindows. If the last window is closed, we return the previous window.
|
||||
// Therefore, we need to push the window into the allWindows array every time it gets focused.
|
||||
win.on("focus", () => {
|
||||
allWindows = allWindows.filter(w => !w.isDestroyed() && w !== win);
|
||||
allWindows.push(win);
|
||||
if (!optionService.getOptionBool("disableTray")) {
|
||||
tray.updateTrayMenu();
|
||||
}
|
||||
});
|
||||
|
||||
win.on("closed", () => {
|
||||
allWindows = allWindows.filter(w => !w.isDestroyed());
|
||||
if (!optionService.getOptionBool("disableTray")) {
|
||||
tray.updateTrayMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createExtraWindow(extraWindowHash: string) {
|
||||
const spellcheckEnabled = optionService.getOptionBool("spellCheckEnabled");
|
||||
@@ -42,6 +63,8 @@ async function createExtraWindow(extraWindowHash: string) {
|
||||
win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1${extraWindowHash}`);
|
||||
|
||||
configureWebContents(win.webContents, spellcheckEnabled);
|
||||
|
||||
trackWindowFocus(win);
|
||||
}
|
||||
|
||||
ipcMain.on("create-extra-window", (event, arg) => {
|
||||
@@ -154,18 +177,21 @@ async function createMainWindow(app: App) {
|
||||
configureWebContents(mainWindow.webContents, spellcheckEnabled);
|
||||
|
||||
app.on("second-instance", (event, commandLine) => {
|
||||
const lastFocusedWindow = getLastFocusedWindow();
|
||||
if (commandLine.includes("--new-window")) {
|
||||
createExtraWindow("");
|
||||
} else if (mainWindow) {
|
||||
} else if (lastFocusedWindow) {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
// see www.ts "requestSingleInstanceLock" for the rest of this logic with explanation
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
if (lastFocusedWindow.isMinimized()) {
|
||||
lastFocusedWindow.restore();
|
||||
}
|
||||
|
||||
mainWindow.focus();
|
||||
lastFocusedWindow.show();
|
||||
lastFocusedWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
trackWindowFocus(mainWindow);
|
||||
}
|
||||
|
||||
function getWindowExtraOpts() {
|
||||
@@ -296,10 +322,20 @@ function getMainWindow() {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
function getLastFocusedWindow() {
|
||||
return allWindows.length > 0 ? allWindows[allWindows.length - 1] : null;
|
||||
}
|
||||
|
||||
function getAllWindows(){
|
||||
return allWindows;
|
||||
}
|
||||
|
||||
export default {
|
||||
createMainWindow,
|
||||
createSetupWindow,
|
||||
closeSetupWindow,
|
||||
registerGlobalShortcuts,
|
||||
getMainWindow
|
||||
getMainWindow,
|
||||
getLastFocusedWindow,
|
||||
getAllWindows
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user