refactor: tidgiminiwindow

This commit is contained in:
lin onetwo 2025-10-19 22:52:41 +08:00
parent 5d468b80ca
commit b376a06b21
42 changed files with 682 additions and 677 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

View file

@ -1,37 +0,0 @@
@menubar
Feature: TidGi Menubar Window
As a user
I want to enable and use the TidGi menubar window
So that I can quickly access TidGi from the system menubar
Scenario: Enable menubar window and test keyboard shortcut
Given I launch the TidGi application
And I wait for the page to load completely
And I click on an "open preferences button" element with selector "#open-preferences-button"
And I switch to "preferences" window
When I click on a "menubar section" element with selector "[data-testid='preference-section-menubar']"
And I confirm the "menubar" window does not exist
When I click on an "attach to menubar switch" element with selector "[data-testid='attach-to-menubar-switch']"
And I confirm the "menubar" window exists
And I confirm the "menubar" window not visible
Then I should see "sidebar toggle and always on top toggle and workspace sync toggle" elements with selectors:
| [data-testid='sidebar-on-menubar-switch'] |
| [data-testid='menubar-always-on-top-switch'] |
| [data-testid='menubar-sync-workspace-switch'] |
Then I click on a "shortcut register button" element with selector "[data-testid='shortcut-register-button']"
And I press the key combination "CommandOrControl+Shift+M"
And I click on a "shortcut confirm button" element with selector "[data-testid='shortcut-confirm-button']"
And I close "preferences" window
Then I switch to "main" window
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "menubar" window exists
And I confirm the "menubar" window visible
And I confirm the "menubar" window browser view is positioned within visible window bounds
And I switch to "menubar" window
Then the browser view should be loaded and visible
And I should see " TiddlyWiki" in the browser view content
Then I switch to "main" window
When I press the key combination "CommandOrControl+Shift+M"
And I wait for 2 seconds
And I confirm the "menubar" window exists
And I confirm the "menubar" window not visible

View file

@ -1,55 +0,0 @@
@menubar
Feature: TidGi Menubar Window Workspace Switching
As a user with menubar already enabled
I want to test menubar window behavior with different workspace configurations
So that I can verify workspace switching and fixed workspace features
Background:
Given I configure menubar with shortcut
Then I launch the TidGi application
And I wait for the page to load completely
Then I switch to "main" window
Scenario: Menubar window syncs with main window switching to agent workspace
# Switch main window to agent workspace
When I click on an "agent workspace button" element with selector "[data-testid='workspace-agent']"
# Verify menubar window exists in background (created but not visible)
And I wait for 0.2 seconds
Then I confirm the "menubar" window exists
And I confirm the "menubar" window not visible
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "menubar" window visible
And I confirm the "menubar" window browser view is not positioned within visible window bounds
Then I switch to "menubar" window
And I should see a "new tab button" element with selector "[data-tab-id='new-tab-button']"
Scenario: Menubar window with fixed agent workspace shows no view and fixed wiki workspace shows browser view
# Configure fixed agent workspace through UI
And I click on an "open preferences button" element with selector "#open-preferences-button"
And I switch to "preferences" window
When I click on a "menubar section" element with selector "[data-testid='preference-section-menubar']"
When I click on a "Disable menubar sync workspace switch" element with selector "[data-testid='menubar-sync-workspace-switch']"
# Select agent workspace (which is a page type workspace)
And I select "agent" from MUI Select with test id "menubar-fixed-workspace-select"
And I wait for 0.2 seconds
# Open menubar window - should show agent workspace and no browser view
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "menubar" window visible
And I confirm the "menubar" window browser view is not positioned within visible window bounds
Then I switch to "menubar" window
And I should see a "new tab button" element with selector "[data-tab-id='new-tab-button']"
# Close menubar and switch to wiki workspace
And I wait for 0.2 seconds
Then I switch to "preferences" window
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "menubar" window not visible
# Get the first wiki workspace ID and select it
And I select "wiki" from MUI Select with test id "menubar-fixed-workspace-select"
And I wait for 0.2 seconds
# Open menubar window again - should show wiki workspace with browser view
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "menubar" window visible
And I confirm the "menubar" window browser view is positioned within visible window bounds
Then I switch to "menubar" window
And the browser view should be loaded and visible
And I should see " TiddlyWiki" in the browser view content

View file

@ -8,7 +8,7 @@ import { MockOpenAIServer } from '../supports/mockOpenAI';
import { logsDirectory, makeSlugPath, screenshotsDirectory } from '../supports/paths';
import { getPackedAppPath } from '../supports/paths';
import { clearAISettings } from './agent';
import { clearMenubarSettings } from './menuar';
import { clearTidgiMiniWindowSettings } from './tidgiMiniWindow';
export class ApplicationWorld {
app: ElectronApplication | undefined;
@ -39,40 +39,40 @@ export class ApplicationWorld {
if (mainWindow) return mainWindow;
} else if (windowType === 'current') {
if (this.currentWindow) return this.currentWindow;
} else if (windowType.toLowerCase() === 'menubar') {
// Special handling for menubar window
// Menubar window is typically the smaller window (500x600)
// We identify it by getting all windows and finding the one with menubar URL
} else if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for tidgi mini window
// TidGi mini window is typically the smaller window (500x600)
// We identify it by getting all windows and finding the one with tidgi mini window URL
const menubarInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const tidgiMiniWindowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows();
// Find the window with menubar dimensions (500x600)
const menubarWindow = allWindows.find(win => {
// Find the window with tidgi mini window dimensions (500x600)
const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600;
});
if (menubarWindow) {
if (tidgiMiniWindow) {
return {
url: menubarWindow.webContents.getURL(),
id: menubarWindow.id,
url: tidgiMiniWindow.webContents.getURL(),
id: tidgiMiniWindow.id,
};
}
return null;
});
if (menubarInfo) {
if (tidgiMiniWindowInfo) {
// Wait a bit for the window to be registered in Playwright
await new Promise(resolve => setTimeout(resolve, 500));
const refreshedPages = this.app.windows();
// Find the page with matching URL (exact match)
const menubarPage = refreshedPages.find(page => {
return page.url() === menubarInfo.url;
const tidgiMiniWindowPage = refreshedPages.find(page => {
return page.url() === tidgiMiniWindowInfo.url;
});
if (menubarPage) {
return menubarPage;
if (tidgiMiniWindowPage) {
return tidgiMiniWindowPage;
}
}
} else {
@ -116,15 +116,15 @@ Before(function(this: ApplicationWorld, { pickle }) {
if (pickle.tags.some((tag) => tag.name === '@setup')) {
clearAISettings();
}
if (pickle.tags.some((tag) => tag.name === '@menubar')) {
clearMenubarSettings();
if (pickle.tags.some((tag) => tag.name === '@tidgiminiwindow')) {
clearTidgiMiniWindowSettings();
}
});
After(async function(this: ApplicationWorld, { pickle }) {
if (this.app) {
try {
// Close all windows including menubar window before closing the app, otherwise it might hang, and refused to exit until ctrl+C
// Close all windows including tidgi mini window before closing the app, otherwise it might hang, and refused to exit until ctrl+C
const allWindows = this.app.windows();
for (const window of allWindows) {
try {
@ -143,8 +143,8 @@ After(async function(this: ApplicationWorld, { pickle }) {
this.mainWindow = undefined;
this.currentWindow = undefined;
}
if (pickle.tags.some((tag) => tag.name === '@menubar')) {
clearMenubarSettings();
if (pickle.tags.some((tag) => tag.name === '@tidgiminiwindow')) {
clearTidgiMiniWindowSettings();
}
if (pickle.tags.some((tag) => tag.name === '@setup')) {
clearAISettings();

View file

@ -5,7 +5,7 @@ import path from 'path';
import type { ISettingFile } from '../../src/services/database/interface';
import { settingsPath } from '../supports/paths';
Given('I configure menubar with shortcut', async function() {
Given('I configure tidgi mini window with shortcut', async function() {
let existing = {} as ISettingFile;
if (await fs.pathExists(settingsPath)) {
existing = await fs.readJson(settingsPath) as ISettingFile;
@ -26,35 +26,35 @@ Given('I configure menubar with shortcut', async function() {
const updatedPreferences = {
...existing.preferences,
attachToMenubar: true,
attachToTidgiMiniWindow: true,
keyboardShortcuts: {
...(existing.preferences?.keyboardShortcuts || {}),
'Window.toggleMenubarWindow': shortcut,
'Window.toggleTidgiMiniWindow': shortcut,
},
};
const finalSettings = { ...existing, preferences: updatedPreferences } as ISettingFile;
await fs.writeJson(settingsPath, finalSettings, { spaces: 2 });
});
// Cleanup function to be called after menubar tests (after app closes)
function clearMenubarSettings() {
// Cleanup function to be called after tidgi mini window tests (after app closes)
function clearTidgiMiniWindowSettings() {
if (!fs.existsSync(settingsPath)) return;
const parsed = fs.readJsonSync(settingsPath) as ISettingFile;
// Remove menubar-related preferences to avoid affecting other tests
// Remove tidgi mini window-related preferences to avoid affecting other tests
const cleanedPreferences = omit(parsed.preferences || {}, [
'attachToMenubar',
'menubarSyncWorkspaceWithMainWindow',
'menubarFixedWorkspaceId',
'menuBarAlwaysOnTop',
'sidebarOnMenubar',
'showMenubarWindowTitleBar',
'attachToTidgiMiniWindow',
'tidgiMiniWindowSyncWorkspaceWithMainWindow',
'tidgiMiniWindowFixedWorkspaceId',
'tidgiMiniWindowAlwaysOnTop',
'sidebarOnTidgiMiniWindow',
'showTidgiMiniWindowTitleBar',
]);
// Also clean up the menubar shortcut from keyboardShortcuts
// Also clean up the tidgi mini window shortcut from keyboardShortcuts
if (cleanedPreferences.keyboardShortcuts) {
cleanedPreferences.keyboardShortcuts = omit(cleanedPreferences.keyboardShortcuts, ['Window.toggleMenubarWindow']);
cleanedPreferences.keyboardShortcuts = omit(cleanedPreferences.keyboardShortcuts, ['Window.toggleTidgiMiniWindow']);
}
const cleaned = { ...parsed, preferences: cleanedPreferences };
fs.writeJsonSync(settingsPath, cleaned, { spaces: 2 });
}
export { clearMenubarSettings };
export { clearTidgiMiniWindowSettings };

View file

@ -11,23 +11,23 @@ const DEFAULT_RETRY_INTERVAL_MS = 250;
async function findWindowByType(app: ElectronApplication, windowType: string): Promise<Page | undefined> {
const pages = app.windows();
if (windowType.toLowerCase() === 'menubar') {
// Special handling for menubar window
// Menubar window uses menubar library and may not have a standard route
if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for tidgi mini window
// TidGi mini window may not have a standard route
// Look for window by its title or other properties
const allWindows = pages.filter(page => !page.isClosed());
// Try to find by URL pattern first
let menubarWindow = allWindows.find(page => {
let tidgiMiniWindow = allWindows.find(page => {
const url = page.url() || '';
return url.includes('#/menuBar') || url.includes('#/main');
return url.includes('#/tidgiMiniWindow') || url.includes('#/main');
});
// If not found by URL, try by window properties
if (!menubarWindow && allWindows.length > 1) {
// Menubar is typically a smaller window
if (!tidgiMiniWindow && allWindows.length > 1) {
// TidGi mini window is typically a smaller window
// Get the second window (first is main)
menubarWindow = allWindows[1];
tidgiMiniWindow = allWindows[1];
}
return menubarWindow;
return tidgiMiniWindow;
} else {
// For regular windows (preferences, about, addWorkspace, etc.)
return pages.find(page => {
@ -76,27 +76,27 @@ When('I confirm the {string} window exists', async function(this: ApplicationWor
throw new Error('Application is not launched');
}
if (windowType.toLowerCase() === 'menubar') {
// For menubar, check via Electron API since it's a special tray window
// Menubar is created by the menubar library and may take time to initialize
if (windowType.toLowerCase() === 'tidgiminiwindow') {
// For tidgi mini window, check via Electron API since it's a special tray window
// TidGi mini window may take time to initialize
const maxWaitAttempts = 10; // Wait up to 10 seconds
const waitInterval = 1000; // Check every second
for (let attempt = 0; attempt < maxWaitAttempts; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows();
// Look for menubar window specifically by its dimensions (500x600)
const menubarWindow = allWindows.find(win => {
// Look for tidgi mini window specifically by its dimensions (500x600)
const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600;
});
return {
hasMenubar: menubarWindow !== undefined,
hasTidgiMiniWindow: tidgiMiniWindow !== undefined,
};
});
if (windowInfo.hasMenubar) {
if (windowInfo.hasTidgiMiniWindow) {
return;
}
@ -123,27 +123,27 @@ When('I confirm the {string} window visible', async function(this: ApplicationWo
throw new Error('Application is not launched');
}
if (windowType.toLowerCase() === 'menubar') {
// Special handling for menubar window visibility
// Menubar window may take longer to show after shortcut key
const menubarMaxAttempts = 10; // Wait up to 10 seconds
const menubarWaitInterval = 1000; // Check every second
if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for tidgi mini window visibility
// TidGi mini window may take longer to show after shortcut key
const tidgiMiniWindowMaxAttempts = 10; // Wait up to 10 seconds
const tidgiMiniWindowWaitInterval = 1000; // Check every second
for (let attempt = 0; attempt < menubarMaxAttempts; attempt++) {
for (let attempt = 0; attempt < tidgiMiniWindowMaxAttempts; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows();
const menubarWindow = allWindows.find(win => {
const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600;
});
if (!menubarWindow) {
if (!tidgiMiniWindow) {
return { found: false, visible: false };
}
return {
found: true,
visible: menubarWindow.isVisible(),
visible: tidgiMiniWindow.isVisible(),
};
});
@ -151,9 +151,9 @@ When('I confirm the {string} window visible', async function(this: ApplicationWo
return;
}
await new Promise(resolve => setTimeout(resolve, menubarWaitInterval));
await new Promise(resolve => setTimeout(resolve, tidgiMiniWindowWaitInterval));
}
throw new Error(`${windowType} window was not visible after ${menubarMaxAttempts} attempts`);
throw new Error(`${windowType} window was not visible after ${tidgiMiniWindowMaxAttempts} attempts`);
}
const success = await waitForWindowCondition(
@ -174,23 +174,23 @@ When('I confirm the {string} window not visible', async function(this: Applicati
throw new Error('Application is not launched');
}
if (windowType.toLowerCase() === 'menubar') {
// Special handling for menubar window visibility check
if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for tidgi mini window visibility check
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows();
const menubarWindow = allWindows.find(win => {
const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600;
});
if (!menubarWindow) {
if (!tidgiMiniWindow) {
return { found: false, visible: false };
}
return {
found: true,
visible: menubarWindow.isVisible(),
visible: tidgiMiniWindow.isVisible(),
};
});
@ -219,23 +219,23 @@ When('I confirm the {string} window does not exist', async function(this: Applic
throw new Error('Application is not launched');
}
if (windowType.toLowerCase() === 'menubar') {
// Special handling for menubar window
if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for tidgi mini window
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows();
// Look for menubar window specifically by its dimensions (500x600)
const menubarWindow = allWindows.find(win => {
// Look for tidgi mini window specifically by its dimensions (500x600)
const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600;
});
return {
hasMenubar: menubarWindow !== undefined,
hasTidgiMiniWindow: tidgiMiniWindow !== undefined,
};
});
if (!windowInfo.hasMenubar) {
if (!windowInfo.hasTidgiMiniWindow) {
return;
}
@ -268,7 +268,7 @@ When('I confirm the {string} window browser view is positioned within visible wi
}
// Get the window dimensions to identify it
const windowDimensions = windowType.toLowerCase() === 'menubar'
const windowDimensions = windowType.toLowerCase() === 'tidgiminiwindow'
? { width: 500, height: 600 }
: { width: 1178, height: 686 }; // main window default
@ -349,7 +349,7 @@ When('I confirm the {string} window browser view is not positioned within visibl
}
// Get the window dimensions to identify it
const windowDimensions = windowType.toLowerCase() === 'menubar'
const windowDimensions = windowType.toLowerCase() === 'tidgiminiwindow'
? { width: 500, height: 600 }
: { width: 1178, height: 686 }; // main window default

View file

@ -0,0 +1,37 @@
@tidgiminiwindow
Feature: TidGi Mini Window
As a user
I want to enable and use the TidGi mini window
So that I can quickly access TidGi from the system tray
Scenario: Enable tidgi mini window and test keyboard shortcut
Given I launch the TidGi application
And I wait for the page to load completely
And I click on an "open preferences button" element with selector "#open-preferences-button"
And I switch to "preferences" window
When I click on a "tidgi mini window section" element with selector "[data-testid='preference-section-tidgiMiniWindow']"
And I confirm the "tidgiminiwindow" window does not exist
When I click on an "attach to tidgi mini window switch" element with selector "[data-testid='attach-to-tidgi-mini-window-switch']"
And I confirm the "tidgiminiwindow" window exists
And I confirm the "tidgiminiwindow" window not visible
Then I should see "sidebar toggle and always on top toggle and workspace sync toggle" elements with selectors:
| [data-testid='sidebar-on-tidgi-mini-window-switch'] |
| [data-testid='tidgi-mini-window-always-on-top-switch'] |
| [data-testid='tidgi-mini-window-sync-workspace-switch'] |
Then I click on a "shortcut register button" element with selector "[data-testid='shortcut-register-button']"
And I press the key combination "CommandOrControl+Shift+M"
And I click on a "shortcut confirm button" element with selector "[data-testid='shortcut-confirm-button']"
And I close "preferences" window
Then I switch to "main" window
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "tidgiminiwindow" window exists
And I confirm the "tidgiminiwindow" window visible
And I confirm the "tidgiminiwindow" window browser view is positioned within visible window bounds
And I switch to "tidgiminiwindow" window
Then the browser view should be loaded and visible
And I should see " TiddlyWiki" in the browser view content
Then I switch to "main" window
When I press the key combination "CommandOrControl+Shift+M"
And I wait for 2 seconds
And I confirm the "tidgiminiwindow" window exists
And I confirm the "tidgiminiwindow" window not visible

View file

@ -0,0 +1,55 @@
@tidgiminiwindow
Feature: TidGi Mini Window Workspace Switching
As a user with tidgi mini window already enabled
I want to test tidgi mini window behavior with different workspace configurations
So that I can verify workspace switching and fixed workspace features
Background:
Given I configure tidgi mini window with shortcut
Then I launch the TidGi application
And I wait for the page to load completely
Then I switch to "main" window
Scenario: TidGi mini window syncs with main window switching to agent workspace
# Switch main window to agent workspace
When I click on an "agent workspace button" element with selector "[data-testid='workspace-agent']"
# Verify tidgi mini window exists in background (created but not visible)
And I wait for 0.2 seconds
Then I confirm the "tidgiminiwindow" window exists
And I confirm the "tidgiminiwindow" window not visible
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "tidgiminiwindow" window visible
And I confirm the "tidgiminiwindow" window browser view is not positioned within visible window bounds
Then I switch to "tidgiminiwindow" window
And I should see a "new tab button" element with selector "[data-tab-id='new-tab-button']"
Scenario: TidGi mini window with fixed agent workspace shows no view and fixed wiki workspace shows browser view
# Configure fixed agent workspace through UI
And I click on an "open preferences button" element with selector "#open-preferences-button"
And I switch to "preferences" window
When I click on a "tidgi mini window section" element with selector "[data-testid='preference-section-tidgiMiniWindow']"
When I click on a "Disable tidgi mini window sync workspace switch" element with selector "[data-testid='tidgi-mini-window-sync-workspace-switch']"
# Select agent workspace (which is a page type workspace)
And I select "agent" from MUI Select with test id "tidgi-mini-window-fixed-workspace-select"
And I wait for 0.2 seconds
# Open tidgi mini window - should show agent workspace and no browser view
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "tidgiminiwindow" window visible
And I confirm the "tidgiminiwindow" window browser view is not positioned within visible window bounds
Then I switch to "tidgiminiwindow" window
And I should see a "new tab button" element with selector "[data-tab-id='new-tab-button']"
# Close tidgi mini window and switch to wiki workspace
And I wait for 0.2 seconds
Then I switch to "preferences" window
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "tidgiminiwindow" window not visible
# Get the first wiki workspace ID and select it
And I select "wiki" from MUI Select with test id "tidgi-mini-window-fixed-workspace-select"
And I wait for 0.2 seconds
# Open tidgi mini window again - should show wiki workspace with browser view
When I press the key combination "CommandOrControl+Shift+M"
And I confirm the "tidgiminiwindow" window visible
And I confirm the "tidgiminiwindow" window browser view is positioned within visible window bounds
Then I switch to "tidgiminiwindow" window
And the browser view should be loaded and visible
And I should see " TiddlyWiki" in the browser view content

View file

@ -32,7 +32,7 @@ const config: ForgeConfig = {
// Unpack worker files, native modules path, and ALL .node binaries (including better-sqlite3)
unpack: '{**/.webpack/main/*.worker.*,**/.webpack/main/native_modules/path.txt,**/{.**,**}/**/*.node}',
},
extraResource: ['localization', 'template/wiki', 'build-resources/menubar@2x.png', 'build-resources/menubarTemplate@2x.png'],
extraResource: ['localization', 'template/wiki', 'build-resources/tidgiMiniWindow@2x.png', 'build-resources/tidgiMiniWindowTemplate@2x.png'],
// @ts-expect-error - mac config is valid
mac: {
category: 'productivity',

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "Open CommandPalette",
"OpenLinkInBrowser": "Open Link in Browser",
"OpenTidGi": "Open TidGi",
"OpenTidGiMenuBar": "Open TidGi MenuBar",
"OpenTidGiMiniWindow": "Open TidGi Mini Window",
"OpenWorkspaceInNewWindow": "Open Workspace in New Window",
"Paste": "Paste",
"Preferences": "Preferences...",
@ -309,7 +309,7 @@
"SelectNextWorkspace": "Select Next Workspace",
"SelectPreviousWorkspace": "Select Previous Workspace",
"TidGi": "TidGi",
"TidGiMenuBar": "TidGi MenuBar",
"TidGiMiniWindow": "TidGi Mini Window",
"View": "View",
"Wiki": "Wiki",
"Window": "Window",
@ -324,10 +324,10 @@
"AlwaysOnTopDetail": "Keep TidGis main window always on top of other windows, and will not be covered by other windows",
"AntiAntiLeech": "Some website has Anti-Leech, will prevent some images from being displayed on your wiki, we simulate a request header that looks like visiting that website to bypass this protection.",
"AskDownloadLocation": "Ask where to save each file before downloading",
"AttachToMenuBar": "Attach to menu bar",
"AttachToMenuBarShowSidebar": "Attach To Menu Bar Show Sidebar",
"AttachToMenuBarShowSidebarTip": "Generally, TidGi small window is only used to quickly view the current workspace, so the default synchronization with the main window workspace, do not need a sidebar, the default hidden sidebar.",
"AttachToMenuBarTip": "Make a small TidGi popup window that pop when you click appbar mini icon. Tip: Right-click on mini app icon to access context menu.",
"AttachToTidgiMiniWindow": "Attach to TidGi mini window",
"AttachToTidgiMiniWindowShowSidebar": "Attach To TidGi Mini Window Show Sidebar",
"AttachToTidgiMiniWindowShowSidebarTip": "Generally, TidGi mini window is only used to quickly view the current workspace, so the default synchronization with the main window workspace, do not need a sidebar, the default hidden sidebar.",
"AttachToTidgiMiniWindowTip": "Make a small TidGi popup window that pop when you click system tray mini icon. Tip: Right-click on mini app icon to access context menu.",
"AttachToTaskbar": "Attach to taskbar",
"AttachToTaskbarShowSidebar": "Attach To Taskbar Show Sidebar",
"ChooseLanguage": "Choose Language 选择语言",
@ -362,16 +362,16 @@
"ItIsWorking": "It is working!",
"Languages": "Lang/语言",
"LightTheme": "Light Theme",
"MenubarAlwaysOnTop": "Menubar Always on top",
"MenubarAlwaysOnTopDetail": "Keep TidGi's Menubar always on top of other windows, and will not be covered by other windows",
"MenubarFixedWorkspace": "Select workspace for fixed menubar window",
"MenubarShortcutKey": "Set shortcut key to toggle TidGi menubar window",
"MenubarShortcutKeyHelperText": "Set a shortcut key to quickly open or close TidGi menubar window",
"MenubarShortcutKeyPlaceholder": "e.g.: Ctrl+Shift+D",
"MenubarSyncWorkspaceWithMainWindow": "Menubar window syncs with main window workspace",
"MenubarSyncWorkspaceWithMainWindowDetail": "When checked, menubar window will display the same workspace content as main window",
"ShowMenubarWindowTitleBar": "Show title bar on menubar window",
"ShowMenubarWindowTitleBarDetail": "Show draggable title bar on menubar window",
"TidgiMiniWindowAlwaysOnTop": "TidGi Mini Window Always on top",
"TidgiMiniWindowAlwaysOnTopDetail": "Keep TidGi's Mini Window always on top of other windows, and will not be covered by other windows",
"TidgiMiniWindowFixedWorkspace": "Select workspace for fixed TidGi Mini Window",
"TidgiMiniWindowShortcutKey": "Set shortcut key to toggle TidGi Mini Window",
"TidgiMiniWindowShortcutKeyHelperText": "Set a shortcut key to quickly open or close TidGi Mini Window",
"TidgiMiniWindowShortcutKeyPlaceholder": "e.g.: Ctrl+Shift+D",
"TidgiMiniWindowSyncWorkspaceWithMainWindow": "TidGi Mini Window syncs with main window workspace",
"TidgiMiniWindowSyncWorkspaceWithMainWindowDetail": "When checked, TidGi Mini Window will display the same workspace content as main window",
"ShowTidgiMiniWindowTitleBar": "Show title bar on TidGi Mini Window",
"ShowTidgiMiniWindowTitleBarDetail": "Show draggable title bar on TidGi Mini Window",
"Miscellaneous": "Miscellaneous",
"MoreWorkspaceSyncSettings": "More Workspace Sync Settings",
"MoreWorkspaceSyncSettingsDescription": "Please right-click the workspace icon, open its workspace setting by click on \"Edit Workspace\" context menu item, and configure its independent synchronization settings in it.",
@ -438,7 +438,7 @@
"TestNotificationDescription": "<0>If notifications dont show up, make sure you enable notifications in<1>macOS Preferences → Notifications → TidGi</1>.</0>",
"Theme": "Theme",
"TiddlyWiki": "TiddlyWiki",
"ToggleMenuBar": "Toggle Menu Bar",
"ToggleTidgiMiniWindow": "Toggle TidGi Mini Window",
"Token": "Git credentials",
"TokenDescription": "The credentials used to authenticate to the Git server so you can securely synchronize content. Can be obtained by logging in to storage services (e.g., Github), or manually obtain \"personal access token\" and filled in here.",
"Translatium": "Translatium",

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "Ouvrir la palette de commandes",
"OpenLinkInBrowser": "Ouvrir le lien dans le navigateur",
"OpenTidGi": "Ouvrir TidGi",
"OpenTidGiMenuBar": "Ouvrir la barre de menu TidGi",
"OpenTidGiMiniWindow": "Ouvrir la mini-fenêtre TidGi",
"OpenWorkspaceInNewWindow": "Ouvrir l'espace de travail dans une nouvelle fenêtre",
"Paste": "Coller",
"Preferences": "Préférences...",
@ -309,7 +309,7 @@
"SelectNextWorkspace": "Sélectionner l'espace de travail suivant",
"SelectPreviousWorkspace": "Sélectionner l'espace de travail précédent",
"TidGi": "TidGi",
"TidGiMenuBar": "Barre de menu TidGi",
"TidGiMiniWindow": "Mini-fenêtre TidGi",
"View": "Vue",
"Wiki": "Wiki",
"Window": "Fenêtre",
@ -324,10 +324,10 @@
"AlwaysOnTopDetail": "Garder la fenêtre principale de TidGi toujours au-dessus des autres fenêtres, et ne sera pas couverte par d'autres fenêtres",
"AntiAntiLeech": "Certains sites web ont une protection anti-leech, empêchant certaines images d'être affichées sur votre wiki, nous simulons un en-tête de requête qui ressemble à la visite de ce site web pour contourner cette protection.",
"AskDownloadLocation": "Demander où enregistrer chaque fichier avant de télécharger",
"AttachToMenuBar": "Attacher à la barre de menu",
"AttachToMenuBarShowSidebar": "Attacher à la barre de menu Afficher la barre latérale",
"AttachToMenuBarShowSidebarTip": "En général, la petite fenêtre TidGi est uniquement utilisée pour visualiser rapidement l'espace de travail actuel, donc la synchronisation avec l'espace de travail de la fenêtre principale n'est pas nécessaire, la barre latérale est masquée par défaut.",
"AttachToMenuBarTip": "Créer une petite fenêtre contextuelle TidGi qui apparaît lorsque vous cliquez sur l'icône mini de la barre d'application. Astuce : Cliquez avec le bouton droit sur l'icône mini de l'application pour accéder au menu contextuel.",
"AttachToTidgiMiniWindow": "Attacher à la mini-fenêtre TidGi",
"AttachToTidgiMiniWindowShowSidebar": "Attacher à la mini-fenêtre TidGi Afficher la barre latérale",
"AttachToTidgiMiniWindowShowSidebarTip": "En général, la petite fenêtre TidGi est uniquement utilisée pour visualiser rapidement l'espace de travail actuel, donc la synchronisation avec l'espace de travail de la fenêtre principale n'est pas nécessaire, la barre latérale est masquée par défaut.",
"AttachToTidgiMiniWindowTip": "Créer une petite fenêtre contextuelle TidGi qui apparaît lorsque vous cliquez sur l'icône mini de la barre d'application. Astuce : Cliquez avec le bouton droit sur l'icône mini de l'application pour accéder au menu contextuel.",
"AttachToTaskbar": "Attacher à la barre des tâches",
"AttachToTaskbarShowSidebar": "Attacher à la barre des tâches Afficher la barre latérale",
"ChooseLanguage": "Choisir la langue 选择语言",
@ -349,8 +349,8 @@
"General": "UI & Interact",
"HibernateAllUnusedWorkspaces": "Mettre en veille les espaces de travail inutilisés au lancement de l'application",
"HibernateAllUnusedWorkspacesDescription": "Mettre en veille tous les espaces de travail au lancement, sauf le dernier espace de travail actif.",
"HideMenuBar": "Masquer la barre de menu",
"HideMenuBarDetail": "Masquer la barre de menu sauf si Alt+M est pressé.",
"HideTidgiMiniWindow": "Masquer la mini-fenêtre TidGi",
"HideTidgiMiniWindowDetail": "Masquer la mini-fenêtre TidGi sauf si Alt+M est pressé.",
"HideSideBar": "Masquer la barre latérale",
"HideSideBarIconDetail": "Masquer l'icône et n'afficher que le nom de l'espace de travail pour rendre la liste des espaces de travail plus compacte",
"HideTitleBar": "Masquer la barre de titre",
@ -360,8 +360,8 @@
"ItIsWorking": "Ça fonctionne !",
"Languages": "Lang/语言",
"LightTheme": "Thème clair",
"MenubarAlwaysOnTop": "Barre de menu toujours au-dessus",
"MenubarAlwaysOnTopDetail": "Garder la barre de menu de TidGi toujours au-dessus des autres fenêtres, et ne sera pas couverte par d'autres fenêtres",
"TidgiMiniWindowAlwaysOnTop": "TidGi Mini Window toujours au-dessus",
"TidgiMiniWindowAlwaysOnTopDetail": "Garder la Mini Window de TidGi toujours au-dessus des autres fenêtres, et ne sera pas couverte par d'autres fenêtres",
"Miscellaneous": "Divers",
"MoreWorkspaceSyncSettings": "Plus de paramètres de synchronisation de l'espace de travail",
"MoreWorkspaceSyncSettingsDescription": "Veuillez cliquer avec le bouton droit sur l'icône de l'espace de travail, ouvrir ses paramètres d'espace de travail en cliquant sur l'élément de menu contextuel \"Modifier l'espace de travail\", et configurer ses paramètres de synchronisation indépendants.",
@ -414,7 +414,7 @@
"TestNotificationDescription": "<0>Si les notifications ne s'affichent pas, assurez-vous d'activer les notifications dans<1>Préférences macOS → Notifications → TidGi</1>.</0>",
"Theme": "Thème",
"TiddlyWiki": "TiddlyWiki",
"ToggleMenuBar": "Basculer la barre de menu",
"ToggleTidgiMiniWindow": "Basculer la mini-fenêtre TidGi",
"Token": "Informations d'identification Git",
"TokenDescription": "Les informations d'identification utilisées pour s'authentifier auprès du serveur Git afin de pouvoir synchroniser le contenu en toute sécurité. Peut être obtenu en se connectant à des services de stockage (par exemple, Github), ou en obtenant manuellement un \"jeton d'accès personnel\" et en le remplissant ici.",
"Translatium": "Translatium",

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "コマンドパレットを開く",
"OpenLinkInBrowser": "ブラウザでリンクを開く",
"OpenTidGi": "TidGiを開く",
"OpenTidGiMenuBar": "TidGiメニューバーを開く",
"OpenTidGiMiniWindow": "TidGiミニウィンドウを開く",
"OpenWorkspaceInNewWindow": "ワークスペースを新しいウィンドウで開く",
"Paste": "貼り付け",
"Preferences": "設定...",
@ -359,8 +359,8 @@
"ItIsWorking": "使いやすい!",
"Languages": "言語/ランゲージ",
"LightTheme": "明るい色のテーマ",
"MenubarAlwaysOnTop": "メニューバーの小ウィンドウを他のウィンドウの上に保持する",
"MenubarAlwaysOnTopDetail": "太記のメニューバーウィンドウを常に他のウィンドウの上に表示させ、他のウィンドウで覆われないようにします。",
"TidgiMiniWindowAlwaysOnTop": "太記小ウィンドウを他のウィンドウの上に保持する",
"TidgiMiniWindowAlwaysOnTopDetail": "太記の小ウィンドウを常に他のウィンドウの上に表示させ、他のウィンドウで覆われないようにします。",
"Miscellaneous": "その他の設定",
"MoreWorkspaceSyncSettings": "さらに多くのワークスペース同期設定",
"MoreWorkspaceSyncSettingsDescription": "ワークスペースアイコンを右クリックし、右クリックメニューから「ワークスペースの編集」を選択して、ワークスペース設定を開いてください。そこで各ワークスペースの同期設定を行います。",

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "Открыть палитру команд",
"OpenLinkInBrowser": "Открыть ссылку в браузере",
"OpenTidGi": "Открыть TidGi",
"OpenTidGiMenuBar": "Открыть меню TidGi",
"OpenTidGiMiniWindow": "Открыть мини-окно TidGi",
"OpenWorkspaceInNewWindow": "Открыть рабочее пространство в новом окне",
"Paste": "Вставить",
"Preferences": "Настройки...",
@ -309,7 +309,7 @@
"SelectNextWorkspace": "Выбрать следующее рабочее пространство",
"SelectPreviousWorkspace": "Выбрать предыдущее рабочее пространство",
"TidGi": "TidGi",
"TidGiMenuBar": "Слишком помню маленькое окно.",
"TidGiMiniWindow": "Мини-окно TidGi",
"View": "Просмотр",
"Wiki": "Wiki",
"Window": "Окно",
@ -359,8 +359,8 @@
"ItIsWorking": "Работает!",
"Languages": "Языки",
"LightTheme": "Светлая тема",
"MenubarAlwaysOnTop": "Меню всегда сверху",
"MenubarAlwaysOnTopDetail": "Детали меню всегда сверху",
"TidgiMiniWindowAlwaysOnTop": "TidGi мини-окно всегда сверху",
"TidgiMiniWindowAlwaysOnTopDetail": "Держать мини-окно TidGi всегда поверх других окон",
"Miscellaneous": "Разное",
"MoreWorkspaceSyncSettings": "Дополнительные настройки синхронизации рабочего пространства",
"MoreWorkspaceSyncSettingsDescription": "Описание дополнительных настроек синхронизации рабочего пространства",

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "打开搜索与命令面板",
"OpenLinkInBrowser": "在浏览器中打开链接",
"OpenTidGi": "打开太记",
"OpenTidGiMenuBar": "打开太记小窗口",
"OpenTidGiMiniWindow": "打开太记小窗口",
"OpenWorkspaceInNewWindow": "在新窗口中打开工作区",
"Paste": "粘贴",
"Preferences": "设置...",
@ -317,7 +317,7 @@
"SelectNextWorkspace": "选择下一个工作区",
"SelectPreviousWorkspace": "选择前一个工作区",
"TidGi": "太记",
"TidGiMenuBar": "太记小窗",
"TidGiMiniWindow": "太记小窗",
"View": "查看",
"Wiki": "知识库",
"Window": "窗口",
@ -332,10 +332,10 @@
"AlwaysOnTopDetail": "让太记的主窗口永远保持在其它窗口上方,不会被其他窗口覆盖",
"AntiAntiLeech": "有的网站做了防盗链,会阻止某些图片在你的知识库上显示,我们通过模拟访问该网站的请求头来绕过这种限制。",
"AskDownloadLocation": "下载前询问每个文件的保存位置",
"AttachToMenuBar": "附加到菜单栏",
"AttachToMenuBarShowSidebar": "附加到菜单栏的窗口包含侧边栏",
"AttachToMenuBarShowSidebarTip": "一般太记小窗仅用于快速查看当前工作区,所以默认与主窗口工作区同步,不需要侧边栏,默认隐藏侧边栏。",
"AttachToMenuBarTip": "创建一个点击菜单栏/任务栏图标会弹出的小太记窗口。提示:右键单击小图标以访问上下文菜单。",
"AttachToTidgiMiniWindow": "附加到太记小窗",
"AttachToTidgiMiniWindowShowSidebar": "太记小窗包含侧边栏",
"AttachToTidgiMiniWindowShowSidebarTip": "一般太记小窗仅用于快速查看当前工作区,所以默认与主窗口工作区同步,不需要侧边栏,默认隐藏侧边栏。",
"AttachToTidgiMiniWindowTip": "创建一个点击系统托盘图标会弹出的小太记窗口。提示:右键单击小图标以访问上下文菜单。",
"AttachToTaskbar": "附加到任务栏",
"AttachToTaskbarShowSidebar": "附加到任务栏的窗口包含侧边栏",
"ChooseLanguage": "选择语言 Choose Language",
@ -371,16 +371,16 @@
"ItIsWorking": "好使的!",
"Languages": "语言/Lang",
"LightTheme": "亮色主题",
"MenubarAlwaysOnTop": "保持菜单栏小窗口在其他窗口上方",
"MenubarAlwaysOnTopDetail": "让太记的菜单栏小窗口永远保持在其它窗口上方,不会被其他窗口覆盖",
"MenubarFixedWorkspace": "选择太记小窗固定展示的工作区",
"MenubarShortcutKey": "设置切换开启太记小窗的快捷键",
"MenubarShortcutKeyHelperText": "设置一个快捷键来快速打开或关闭太记小窗",
"MenubarShortcutKeyPlaceholder": "例如Ctrl+Shift+D",
"MenubarSyncWorkspaceWithMainWindow": "小窗和主窗口展示同样的工作区",
"MenubarSyncWorkspaceWithMainWindowDetail": "勾选后,小窗将与主窗口同步显示相同的工作区内容",
"ShowMenubarWindowTitleBar": "小窗显示标题栏",
"ShowMenubarWindowTitleBarDetail": "在菜单栏小窗口上显示可拖动的标题栏",
"TidgiMiniWindowAlwaysOnTop": "保持太记小窗口在其他窗口上方",
"TidgiMiniWindowAlwaysOnTopDetail": "让太记的小窗口永远保持在其它窗口上方,不会被其他窗口覆盖",
"TidgiMiniWindowFixedWorkspace": "为固定的太记小窗口选择工作区",
"TidgiMiniWindowShortcutKey": "设置快捷键来切换太记小窗口",
"TidgiMiniWindowShortcutKeyHelperText": "设置一个快捷键来快速打开或关闭太记小窗",
"TidgiMiniWindowShortcutKeyPlaceholder": "例如Ctrl+Shift+D",
"TidgiMiniWindowSyncWorkspaceWithMainWindow": "小窗和主窗口展示同样的工作区",
"TidgiMiniWindowSyncWorkspaceWithMainWindowDetail": "勾选后,小窗将与主窗口同步显示相同的工作区内容",
"ShowTidgiMiniWindowTitleBar": "小窗显示标题栏",
"ShowTidgiMiniWindowTitleBarDetail": "在太记小窗口上显示可拖动的标题栏",
"Miscellaneous": "其他设置",
"MoreWorkspaceSyncSettings": "更多工作区同步设置",
"MoreWorkspaceSyncSettingsDescription": "请右键工作区图标,点右键菜单里的「编辑工作区」来打开工作区设置,在里面配各个工作区的同步设置。",
@ -448,7 +448,7 @@
"TestNotificationDescription": "<0>如果通知未显示,请确保在<1>macOS首选项 → 通知 → TidGi中启用通知</1></0>",
"Theme": "主题色",
"TiddlyWiki": "太微",
"ToggleMenuBar": "切换显隐菜单栏",
"ToggleTidgiMiniWindow": "切换太记小窗",
"Token": "Git身份凭证",
"TokenDescription": "用于向Git服务器验证身份并同步内容的凭证可通过登录在线存储服务如Github来取得也可以手动获取「Personal Access Token」后填到这里。",
"Translatium": "翻译素APP",

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "打開搜索與命令面板",
"OpenLinkInBrowser": "在瀏覽器中打開連結",
"OpenTidGi": "打開太記",
"OpenTidGiMenuBar": "打開太記小窗口",
"OpenTidGiMiniWindow": "打開太記小窗口",
"OpenWorkspaceInNewWindow": "在新窗口中打開工作區",
"Paste": "黏貼",
"Preferences": "設置...",
@ -309,7 +309,7 @@
"SelectNextWorkspace": "選擇下一個工作區",
"SelectPreviousWorkspace": "選擇前一個工作區",
"TidGi": "太記",
"TidGiMenuBar": "太記小窗",
"TidGiMiniWindow": "太記小窗",
"View": "查看",
"Wiki": "知識庫",
"Window": "窗口",
@ -324,10 +324,10 @@
"AlwaysOnTopDetail": "讓太記的主窗口永遠保持在其它窗口上方,不會被其他窗口覆蓋",
"AntiAntiLeech": "有的網站做了防盜鏈,會阻止某些圖片在你的知識庫上顯示,我們透過模擬訪問該網站的請求頭來繞過這種限制。",
"AskDownloadLocation": "下載前詢問每個文件的保存位置",
"AttachToMenuBar": "附加到選單欄",
"AttachToMenuBarShowSidebar": "附加到選單欄的窗口包含側邊欄",
"AttachToMenuBarShowSidebarTip": "一般太記小窗僅用於快速查看當前工作區,所以默認與主窗口工作區同步,不需要側邊欄,默認隱藏側邊欄。",
"AttachToMenuBarTip": "創建一個點擊選單欄/任務欄圖示會彈出的小太記窗口。提示:右鍵單擊小圖示以訪問上下文菜單。",
"AttachToTidgiMiniWindow": "附加到太記小窗",
"AttachToTidgiMiniWindowShowSidebar": "太記小窗包含側邊欄",
"AttachToTidgiMiniWindowShowSidebarTip": "一般太記小窗僅用於快速查看當前工作區,所以默認與主窗口工作區同步,不需要側邊欄,默認隱藏側邊欄。",
"AttachToTidgiMiniWindowTip": "創建一個點擊系統托盤圖示會彈出的小太記窗口。提示:右鍵單擊小圖示以訪問上下文菜單。",
"AttachToTaskbar": "附加到任務欄",
"AttachToTaskbarShowSidebar": "附加到任務欄的窗口包含側邊欄",
"ChooseLanguage": "選擇語言 Choose Language",
@ -363,8 +363,8 @@
"ItIsWorking": "好使的!",
"Languages": "語言/Lang",
"LightTheme": "亮色主題",
"MenubarAlwaysOnTop": "保持選單欄小窗口在其他窗口上方",
"MenubarAlwaysOnTopDetail": "讓太記的選單欄小窗口永遠保持在其它窗口上方,不會被其他窗口覆蓋",
"TidgiMiniWindowAlwaysOnTop": "保持太記小窗口在其他窗口上方",
"TidgiMiniWindowAlwaysOnTopDetail": "讓太記的小窗口永遠保持在其它窗口上方,不會被其他窗口覆蓋",
"Miscellaneous": "其他設置",
"MoreWorkspaceSyncSettings": "更多工作區同步設定",
"MoreWorkspaceSyncSettingsDescription": "請右鍵工作區圖示,點右鍵菜單裡的「編輯工作區」來打開工作區設置,在裡面配各個工作區的同步設定。",
@ -431,7 +431,7 @@
"TestNotificationDescription": "<0>如果通知未顯示,請確保在<1>macOS首選項 → 通知 → TidGi中啟用通知</1></0>",
"Theme": "主題色",
"TiddlyWiki": "太微",
"ToggleMenuBar": "切換顯隱選單欄",
"ToggleTidgiMiniWindow": "切換太記小窗",
"Token": "Git身份憑證",
"TokenDescription": "用於向Git伺服器驗證身份並同步內容的憑證可透過登錄在線儲存服務如Github來取得也可以手動獲取「Personal Access Token」後填到這裡。",
"Translatium": "翻譯素APP",

View file

@ -18,7 +18,7 @@
"test": "pnpm run test:unit && pnpm run test:prepare-e2e && pnpm run test:e2e",
"test:unit": "cross-env ELECTRON_RUN_AS_NODE=1 ./node_modules/.bin/electron ./node_modules/vitest/vitest.mjs run",
"test:unit:coverage": "pnpm run test:unit --coverage",
"test:prepare-e2e": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=test DEBUG=electron-forge:* electron-forge package",
"test:prepare-e2e": "cross-env READ_DOC_BEFORE_USING='docs/Testing.md' && pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=test DEBUG=electron-forge:* electron-forge package",
"test:e2e": "rimraf -- ./userData-test ./wiki-test && cross-env NODE_ENV=test pnpm dlx tsx scripts/developmentMkdir.ts && cross-env NODE_ENV=test cucumber-js --config features/cucumber.config.js",
"make": "pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make",
"make:analyze": "cross-env ANALYZE=true pnpm run make",

View file

@ -28,11 +28,11 @@ export const sourcePath = isPackaged
export const buildResourcePath = path.resolve(sourcePath, 'build-resources');
export const developmentImageFolderPath = path.resolve(sourcePath, 'images');
// Menubar icon
const menuBarIconFileName = isMac ? 'menubarTemplate@2x.png' : 'menubar@2x.png';
export const MENUBAR_ICON_PATH = isPackaged
? path.resolve(process.resourcesPath, menuBarIconFileName) // Packaged: resources/<icon>
: path.resolve(buildResourcePath, menuBarIconFileName); // Dev/Unit test: <project-root>/build-resources/<icon>
// TidGi Mini Window icon
const tidgiMiniWindowIconFileName = isMac ? 'tidgiMiniWindowTemplate@2x.png' : 'tidgiMiniWindow@2x.png';
export const TIDGI_MINI_WINDOW_ICON_PATH = isPackaged
? path.resolve(process.resourcesPath, tidgiMiniWindowIconFileName) // Packaged: resources/<icon>
: path.resolve(buildResourcePath, tidgiMiniWindowIconFileName); // Dev/Unit test: <project-root>/build-resources/<icon>
// System paths
export const CHROME_ERROR_PATH = 'chrome-error://chromewebdata/';

View file

@ -118,7 +118,7 @@ const commonInit = async (): Promise<void> => {
externalAPIService.initialize(),
]);
// if user want a menubar, we create a new window for that
// if user want a tidgi mini window, we create a new window for that
// handle workspace name + tiddler name in uri https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app
deepLinkService.initializeDeepLink('tidgi');
@ -139,9 +139,9 @@ const commonInit = async (): Promise<void> => {
await workspaceService.initializeDefaultPageWorkspaces();
// perform wiki startup and git sync for each workspace
await workspaceViewService.initializeAllWorkspaceView();
const attachToMenubar = await preferenceService.get('attachToMenubar');
if (attachToMenubar) {
await windowService.openMenubarWindow(true, false);
const attachToTidgiMiniWindow = await preferenceService.get('attachToTidgiMiniWindow');
if (attachToTidgiMiniWindow) {
await windowService.openTidgiMiniWindow(true, false);
}
ipcMain.emit('request-update-pause-notifications-info');

View file

@ -14,7 +14,7 @@ import Main from '../index';
// Mock window.observables to provide realistic API behavior
const preferencesSubject = new BehaviorSubject({
sidebar: true,
sidebarOnMenubar: true,
sidebarOnTidgiMiniWindow: true,
showSideBarText: true,
showSideBarIcon: true,
});

View file

@ -63,7 +63,7 @@ export default function Main(): React.JSX.Element {
const { t } = useTranslation();
useInitialPage();
const preferences = usePreferenceObservable();
const showSidebar = (windowName === WindowNames.menuBar ? preferences?.sidebarOnMenubar : preferences?.sidebar) ?? true;
const showSidebar = (windowName === WindowNames.tidgiMiniWindow ? preferences?.sidebarOnTidgiMiniWindow : preferences?.sidebar) ?? true;
return (
<OuterRoot>
<Helmet>

View file

@ -19,14 +19,14 @@ export function useInitialPage() {
let targetWorkspace = workspacesList.find(workspace => workspace.active);
// For menubar window, determine which workspace to show based on preferences
if (windowName === WindowNames.menuBar && preferences) {
const { menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId } = preferences;
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
// For tidgi mini window, determine which workspace to show based on preferences
if (windowName === WindowNames.tidgiMiniWindow && preferences) {
const { tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId } = preferences;
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
if (!shouldSync && menubarFixedWorkspaceId) {
// Use fixed workspace if not syncing
const fixedWorkspace = workspacesList.find(ws => ws.id === menubarFixedWorkspaceId);
if (!shouldSync && tidgiMiniWindowFixedWorkspaceId) {
// If not syncing with main window, use fixed workspace
const fixedWorkspace = workspacesList.find(ws => ws.id === tidgiMiniWindowFixedWorkspaceId);
if (fixedWorkspace) {
targetWorkspace = fixedWorkspace;
}
@ -51,19 +51,19 @@ export function useInitialPage() {
}
}, [location, workspacesList, preferences, windowName, setLocation]);
// For menubar window, also listen to active workspace changes
// For tidgi mini window, also listen to active workspace changes
useEffect(() => {
if (windowName !== WindowNames.menuBar || !workspacesList || !preferences) {
if (windowName !== WindowNames.tidgiMiniWindow || !workspacesList || !preferences) {
return;
}
const { menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId } = preferences;
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
const { tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId } = preferences;
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
// Determine target workspace
let targetWorkspace = workspacesList.find(workspace => workspace.active);
if (!shouldSync && menubarFixedWorkspaceId) {
const fixedWorkspace = workspacesList.find(ws => ws.id === menubarFixedWorkspaceId);
if (!shouldSync && tidgiMiniWindowFixedWorkspaceId) {
const fixedWorkspace = workspacesList.find(ws => ws.id === tidgiMiniWindowFixedWorkspaceId);
if (fixedWorkspace) {
targetWorkspace = fixedWorkspace;
}

View file

@ -34,7 +34,7 @@ export const remoteMethods = {
* @returns the index of the clicked button. -1 means unknown or errored. 0 if canceled (this can be configured by `cancelId` in the options).
*/
showElectronMessageBoxSync: (options: Electron.MessageBoxSyncOptions): number => {
// only main window can show message box, view window (browserView) can't. Currently didn't handle menubar window, hope it won't show message box...
// only main window can show message box, view window (browserView) can't. Currently didn't handle tidgi mini window, hope it won't show message box...
const clickedButtonIndex = ipcRenderer.sendSync(NativeChannel.showElectronMessageBoxSync, options, WindowNames.main) as unknown;
if (typeof clickedButtonIndex === 'number') {
return clickedButtonIndex;

View file

@ -11,7 +11,7 @@ export interface IPaths {
LOCALIZATION_FOLDER: string;
LOG_FOLDER: string;
MAIN_WINDOW_WEBPACK_ENTRY: string;
MENUBAR_ICON_PATH: string;
TIDGI_MINI_WINDOW_ICON_PATH: string;
SETTINGS_FOLDER: string;
TIDDLERS_PATH: string;
TIDDLYWIKI_TEMPLATE_FOLDER_PATH: string;

View file

@ -53,7 +53,7 @@ export class DatabaseService implements IDatabaseService {
hasContent: !!this.settingFileContent,
keys: this.settingFileContent ? Object.keys(this.settingFileContent).length : 0,
hasPreferences: !!this.settingFileContent?.preferences,
attachToMenubar: this.settingFileContent?.preferences?.attachToMenubar,
attachToTidgiMiniWindow: this.settingFileContent?.preferences?.attachToTidgiMiniWindow,
settingsFilePath: settings.file(),
function: 'DatabaseService.initializeForApp',
});

View file

@ -11,8 +11,8 @@ export default async function getViewBounds(
): Promise<{ height: number; width: number; x: number; y: number }> {
const { findInPage = false, windowName } = config;
const preferencesService = container.get<IPreferenceService>(serviceIdentifier.Preference);
const [sidebar, sidebarOnMenubar] = await Promise.all([preferencesService.get('sidebar'), preferencesService.get('sidebarOnMenubar')]);
const showSidebar = windowName === WindowNames.menuBar ? sidebarOnMenubar : sidebar;
const [sidebar, sidebarOnTidgiMiniWindow] = await Promise.all([preferencesService.get('sidebar'), preferencesService.get('sidebarOnTidgiMiniWindow')]);
const showSidebar = windowName === WindowNames.tidgiMiniWindow ? sidebarOnTidgiMiniWindow : sidebar;
// Now showing sidebar on secondary window
const secondary = windowName === WindowNames.secondary;
const x = (showSidebar && !secondary) ? 68 : 0;

View file

@ -7,7 +7,7 @@ export const defaultPreferences: IPreferences = {
allowPrerelease: Boolean(semver.prerelease(app.getVersion())),
alwaysOnTop: false,
askForDownloadPath: true,
attachToMenubar: false,
attachToTidgiMiniWindow: false,
disableAntiAntiLeech: false,
disableAntiAntiLeechForUrls: [],
downloadPath: DEFAULT_DOWNLOADS_PATH,
@ -16,10 +16,10 @@ export const defaultPreferences: IPreferences = {
hideMenuBar: false,
ignoreCertificateErrors: false,
language: 'zh-Hans',
menuBarAlwaysOnTop: false,
menubarFixedWorkspaceId: '',
menubarSyncWorkspaceWithMainWindow: true,
showMenubarWindowTitleBar: false,
tidgiMiniWindowAlwaysOnTop: false,
tidgiMiniWindowFixedWorkspaceId: '',
tidgiMiniWindowSyncWorkspaceWithMainWindow: true,
showTidgiMiniWindowTitleBar: false,
keyboardShortcuts: {},
pauseNotifications: '',
pauseNotificationsBySchedule: false,
@ -32,7 +32,7 @@ export const defaultPreferences: IPreferences = {
showSideBarIcon: true,
showSideBarText: true,
sidebar: true,
sidebarOnMenubar: false,
sidebarOnTidgiMiniWindow: false,
spellcheck: true,
spellcheckLanguages: ['en-US'],
swipeToNavigate: true,

View file

@ -8,7 +8,7 @@ export interface IPreferences {
allowPrerelease: boolean;
alwaysOnTop: boolean;
askForDownloadPath: boolean;
attachToMenubar: boolean;
attachToTidgiMiniWindow: boolean;
/**
*
*/
@ -26,7 +26,7 @@ export interface IPreferences {
hideMenuBar: boolean;
ignoreCertificateErrors: boolean;
language: string;
menuBarAlwaysOnTop: boolean;
tidgiMiniWindowAlwaysOnTop: boolean;
pauseNotifications: string | undefined;
pauseNotificationsBySchedule: boolean;
pauseNotificationsByScheduleFrom: string;
@ -42,24 +42,24 @@ export interface IPreferences {
*/
sidebar: boolean;
/**
* Should show sidebar on menubar window?
* Should show sidebar on tidgi mini window?
*/
sidebarOnMenubar: boolean;
sidebarOnTidgiMiniWindow: boolean;
spellcheck: boolean;
spellcheckLanguages: HunspellLanguages[];
swipeToNavigate: boolean;
/**
* Whether menubar window should show the same workspace as main window
*/
menubarSyncWorkspaceWithMainWindow: boolean;
tidgiMiniWindowSyncWorkspaceWithMainWindow: boolean;
/**
* The workspace ID that menubar window should always show when menubarSyncWorkspaceWithMainWindow is false
* The workspace ID that tidgi mini window should always show when tidgiMiniWindowSyncWorkspaceWithMainWindow is false
*/
menubarFixedWorkspaceId: string | undefined;
tidgiMiniWindowFixedWorkspaceId: string | undefined;
/**
* Whether to show title bar on menubar window (independent of main window's titleBar setting)
* Whether to show title bar on tidgi mini window (independent of main window's titleBar setting)
*/
showMenubarWindowTitleBar: boolean;
showTidgiMiniWindowTitleBar: boolean;
/**
* Keyboard shortcuts configuration stored as serviceIdentifier.methodName -> shortcut
*/
@ -82,7 +82,7 @@ export enum PreferenceSections {
friendLinks = 'friendLinks',
general = 'general',
languages = 'languages',
menubar = 'menubar',
tidgiMiniWindow = 'tidgiMiniWindow',
misc = 'misc',
network = 'network',
notifications = 'notifications',

View file

@ -247,7 +247,7 @@ export class View implements IViewService {
};
const checkNotExistResult = await Promise.all([
checkNotExist(workspace, WindowNames.main),
this.preferenceService.get('attachToMenubar').then((attachToMenubar) => attachToMenubar && checkNotExist(workspace, WindowNames.menuBar)),
this.preferenceService.get('attachToTidgiMiniWindow').then((attachToTidgiMiniWindow) => attachToTidgiMiniWindow && checkNotExist(workspace, WindowNames.tidgiMiniWindow)),
]);
return checkNotExistResult.every((result) => !result);
}
@ -315,8 +315,8 @@ export class View implements IViewService {
// Add view to window if:
// 1. workspace is active (main window)
// 2. windowName is secondary (always add)
// 3. windowName is menuBar (menubar can have fixed workspace independent of main window's active workspace)
if (workspace.active || windowName === WindowNames.secondary || windowName === WindowNames.menuBar) {
// 3. windowName is tidgiMiniWindow (tidgi mini window can have fixed workspace independent of main window's active workspace)
if (workspace.active || windowName === WindowNames.secondary || windowName === WindowNames.tidgiMiniWindow) {
browserWindow.contentView.addChildView(view);
const contentSize = browserWindow.getContentSize();
const newViewBounds = await getViewBounds(contentSize as [number, number], { windowName });
@ -330,7 +330,7 @@ export class View implements IViewService {
if (updatedWorkspace === undefined) return;
// Prevent update non-active (hiding) wiki workspace, so it won't pop up to cover other active agent workspace
if (windowName === WindowNames.main && !updatedWorkspace.active) return;
if ([WindowNames.secondary, WindowNames.main, WindowNames.menuBar].includes(windowName)) {
if ([WindowNames.secondary, WindowNames.main, WindowNames.tidgiMiniWindow].includes(windowName)) {
const contentSize = browserWindow.getContentSize();
const newViewBounds = await getViewBounds(contentSize as [number, number], { windowName });
view.setBounds(newViewBounds);
@ -412,36 +412,36 @@ export class View implements IViewService {
public async setActiveViewForAllBrowserViews(workspaceID: string): Promise<void> {
// Set main window workspace
const mainWindowTask = this.setActiveView(workspaceID, WindowNames.main);
const [attachToMenubar, menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('attachToMenubar'),
this.preferenceService.get('menubarSyncWorkspaceWithMainWindow'),
this.preferenceService.get('menubarFixedWorkspaceId'),
const [attachToTidgiMiniWindow, tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('attachToTidgiMiniWindow'),
this.preferenceService.get('tidgiMiniWindowSyncWorkspaceWithMainWindow'),
this.preferenceService.get('tidgiMiniWindowFixedWorkspaceId'),
]);
// For menubar window, decide which workspace to show based on preferences
let menubarTask = Promise.resolve();
if (attachToMenubar) {
// For tidgi mini window, decide which workspace to show based on preferences
let tidgiMiniWindowTask = Promise.resolve();
if (attachToTidgiMiniWindow) {
// Default to sync (undefined or true), otherwise use fixed workspace ID (fallback to main if not set)
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
const menubarWorkspaceId = shouldSync ? workspaceID : (menubarFixedWorkspaceId || workspaceID);
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
const tidgiMiniWindowWorkspaceId = shouldSync ? workspaceID : (tidgiMiniWindowFixedWorkspaceId || workspaceID);
// Check if the target workspace is a pageType workspace (which doesn't have a view)
if (!shouldSync && menubarFixedWorkspaceId) {
const fixedWorkspace = await this.workspaceService.get(menubarFixedWorkspaceId);
if (!shouldSync && tidgiMiniWindowFixedWorkspaceId) {
const fixedWorkspace = await this.workspaceService.get(tidgiMiniWindowFixedWorkspaceId);
if (fixedWorkspace?.pageType) {
logger.debug(
`setActiveViewForAllBrowserViews: skip menu bar window because fixed workspace ${menubarFixedWorkspaceId} is a page type workspace.`,
`setActiveViewForAllBrowserViews: skip tidgi mini window because fixed workspace ${tidgiMiniWindowFixedWorkspaceId} is a page type workspace.`,
);
// Don't set any view for pageType workspaces - menubar task remains as Promise.resolve()
// Don't set any view for pageType workspaces - tidgi mini window task remains as Promise.resolve()
} else {
menubarTask = this.setActiveView(menubarWorkspaceId, WindowNames.menuBar);
tidgiMiniWindowTask = this.setActiveView(tidgiMiniWindowWorkspaceId, WindowNames.tidgiMiniWindow);
}
} else {
menubarTask = this.setActiveView(menubarWorkspaceId, WindowNames.menuBar);
tidgiMiniWindowTask = this.setActiveView(tidgiMiniWindowWorkspaceId, WindowNames.tidgiMiniWindow);
}
}
await Promise.all([mainWindowTask, menubarTask]);
await Promise.all([mainWindowTask, tidgiMiniWindowTask]);
}
public async setActiveView(workspaceID: string, windowName: WindowNames): Promise<void> {
@ -611,9 +611,9 @@ export class View implements IViewService {
const workspace = await workspaceService.getActiveWorkspace();
if (workspace !== undefined) {
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const isMenubarOpen = await windowService.isMenubarOpen();
if (isMenubarOpen) {
return this.getView(workspace.id, WindowNames.menuBar);
const isTidgiMiniWindowOpen = await windowService.isTidgiMiniWindowOpen();
if (isTidgiMiniWindowOpen) {
return this.getView(workspace.id, WindowNames.tidgiMiniWindow);
} else {
return this.getView(workspace.id, WindowNames.main);
}
@ -624,7 +624,7 @@ export class View implements IViewService {
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const workspace = await workspaceService.getActiveWorkspace();
if (workspace !== undefined) {
return [this.getView(workspace.id, WindowNames.main), this.getView(workspace.id, WindowNames.menuBar)];
return [this.getView(workspace.id, WindowNames.main), this.getView(workspace.id, WindowNames.tidgiMiniWindow)];
}
logger.error(`getActiveBrowserViews workspace !== undefined`, { stack: new Error('stack').stack?.replace('Error:', '') });
return [];

View file

@ -30,11 +30,11 @@ export interface IViewService {
createViewAddToWindow(workspace: IWorkspace, browserWindow: BrowserWindow, sharedWebPreferences: WebPreferences, windowName: WindowNames): Promise<WebContentsView>;
forEachView: (functionToRun: (view: WebContentsView, workspaceID: string, windowName: WindowNames) => void) => void;
/**
* If menubar is open, we get menubar browser view, else we get main window browser view
* If tidgi mini window is open, we get tidgi mini window browser view, else we get main window browser view
*/
getActiveBrowserView: () => Promise<WebContentsView | undefined>;
/**
* Get active workspace's main window and menubar browser view.
* Get active workspace's main window and tidgi mini window browser view.
*/
getActiveBrowserViews: () => Promise<Array<WebContentsView | undefined>>;
getLoadedViewEnsure(workspaceID: string, windowName: WindowNames): Promise<WebContentsView>;
@ -74,7 +74,7 @@ export interface IViewService {
/**
* Bring an already created view to the front. If it happened to not created, will call `addView()` to create one.
* @param workspaceID id, can only be main workspace id, because only main workspace will have view created.
* @param windowName you can control main window or menubar window to have this view.
* @param windowName you can control main window or tidgi mini window to have this view.
* @returns
*/
setActiveView: (workspaceID: string, windowName: WindowNames) => Promise<void>;

View file

@ -15,7 +15,7 @@ export enum WindowNames {
* We only have a single instance of main window, that is the app window.
*/
main = 'main',
menuBar = 'menuBar',
tidgiMiniWindow = 'tidgiMiniWindow',
notifications = 'notifications',
preferences = 'preferences',
/**
@ -46,7 +46,7 @@ export const windowDimension: Record<WindowNames, { height?: number; width?: num
width: 1200,
height: 768,
},
[WindowNames.menuBar]: {
[WindowNames.tidgiMiniWindow]: {
width: 500,
height: 600,
},
@ -100,7 +100,7 @@ export interface WindowMeta {
[WindowNames.auth]: undefined;
[WindowNames.editWorkspace]: { workspaceID?: string };
[WindowNames.main]: { forceClose?: boolean };
[WindowNames.menuBar]: undefined;
[WindowNames.tidgiMiniWindow]: undefined;
[WindowNames.notifications]: undefined;
[WindowNames.preferences]: IPreferenceWindowMeta;
[WindowNames.spellcheck]: undefined;

View file

@ -1,4 +1,4 @@
import { MENUBAR_ICON_PATH } from '@/constants/paths';
import { TIDGI_MINI_WINDOW_ICON_PATH } from '@/constants/paths';
import { isMac } from '@/helpers/system';
import { container } from '@services/container';
import { i18n } from '@services/libs/i18n';
@ -15,14 +15,17 @@ import type { IWindowService } from './interface';
import { getMainWindowEntry } from './viteEntry';
import { WindowNames } from './WindowProperties';
export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstructorOptions, windowWithBrowserViewState: windowStateKeeper.State | undefined): Promise<Menubar> {
export async function handleAttachToTidgiMiniWindow(
windowConfig: BrowserWindowConstructorOptions,
windowWithBrowserViewState: windowStateKeeper.State | undefined,
): Promise<Menubar> {
const menuService = container.get<IMenuService>(serviceIdentifier.MenuService);
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const viewService = container.get<IViewService>(serviceIdentifier.View);
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
// Get menubar-specific titleBar preference
const showMenubarWindowTitleBar = await preferenceService.get('showMenubarWindowTitleBar');
// Get tidgi mini window-specific titleBar preference
const showTidgiMiniWindowTitleBar = await preferenceService.get('showTidgiMiniWindowTitleBar');
// setImage after Tray instance is created to avoid
// "Segmentation fault (core dumped)" bug on Linux
@ -30,71 +33,71 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
// https://github.com/atomery/translatium/issues/164
const tray = new Tray(nativeImage.createEmpty());
// icon template is not supported on Windows & Linux
tray.setImage(MENUBAR_ICON_PATH);
tray.setImage(nativeImage.createFromPath(TIDGI_MINI_WINDOW_ICON_PATH));
// Create menubar-specific window configuration
// Override titleBar settings from windowConfig with menubar-specific preference
const menubarWindowConfig: BrowserWindowConstructorOptions = {
// Create tidgi mini window-specific window configuration
// Override titleBar settings from windowConfig with tidgi mini window-specific preference
const tidgiMiniWindowConfig: BrowserWindowConstructorOptions = {
...windowConfig,
show: false,
minHeight: 100,
minWidth: 250,
// Use menubar-specific titleBar setting instead of inheriting from main window
titleBarStyle: showMenubarWindowTitleBar ? 'default' : 'hidden',
frame: showMenubarWindowTitleBar,
// Use tidgi mini window-specific titleBar setting instead of inheriting from main window
titleBarStyle: showTidgiMiniWindowTitleBar ? 'default' : 'hidden',
frame: showTidgiMiniWindowTitleBar,
// Always hide the menu bar (File, Edit, View menu), even when showing title bar
autoHideMenuBar: true,
};
logger.info('Creating menubar with titleBar configuration', {
function: 'handleAttachToMenuBar',
showMenubarWindowTitleBar,
titleBarStyle: menubarWindowConfig.titleBarStyle,
frame: menubarWindowConfig.frame,
logger.info('Creating tidgi mini window with titleBar configuration', {
function: 'handleAttachToTidgiMiniWindow',
showTidgiMiniWindowTitleBar,
titleBarStyle: tidgiMiniWindowConfig.titleBarStyle,
frame: tidgiMiniWindowConfig.frame,
});
const menuBar = menubar({
const tidgiMiniWindow = menubar({
index: getMainWindowEntry(),
tray,
activateWithApp: false,
showDockIcon: true,
preloadWindow: true,
tooltip: i18n.t('Menu.TidGiMenuBar'),
browserWindow: menubarWindowConfig,
tooltip: i18n.t('Menu.TidGiMiniWindow'),
browserWindow: tidgiMiniWindowConfig,
});
menuBar.on('after-create-window', () => {
if (menuBar.window !== undefined) {
menuBar.window.on('focus', async () => {
logger.debug('restore window position', { function: 'handleAttachToMenuBar' });
tidgiMiniWindow.on('after-create-window', () => {
if (tidgiMiniWindow.window !== undefined) {
tidgiMiniWindow.window.on('focus', async () => {
logger.debug('restore window position', { function: 'handleAttachToTidgiMiniWindow' });
if (windowWithBrowserViewState === undefined) {
logger.debug('windowWithBrowserViewState is undefined for menuBar', { function: 'handleAttachToMenuBar' });
logger.debug('windowWithBrowserViewState is undefined for tidgiMiniWindow', { function: 'handleAttachToTidgiMiniWindow' });
} else {
if (menuBar.window === undefined) {
logger.debug('menuBar.window is undefined', { function: 'handleAttachToMenuBar' });
if (tidgiMiniWindow.window === undefined) {
logger.debug('tidgiMiniWindow.window is undefined', { function: 'handleAttachToTidgiMiniWindow' });
} else {
const haveXYValue = [windowWithBrowserViewState.x, windowWithBrowserViewState.y].every((value) => Number.isFinite(value));
const haveWHValue = [windowWithBrowserViewState.width, windowWithBrowserViewState.height].every((value) => Number.isFinite(value));
if (haveXYValue) {
menuBar.window.setPosition(windowWithBrowserViewState.x, windowWithBrowserViewState.y, false);
tidgiMiniWindow.window.setPosition(windowWithBrowserViewState.x, windowWithBrowserViewState.y, false);
}
if (haveWHValue) {
menuBar.window.setSize(windowWithBrowserViewState.width, windowWithBrowserViewState.height, false);
tidgiMiniWindow.window.setSize(windowWithBrowserViewState.width, windowWithBrowserViewState.height, false);
}
}
}
const view = await viewService.getActiveBrowserView();
view?.webContents.focus();
});
menuBar.window.removeAllListeners('close');
menuBar.window.on('close', (event) => {
tidgiMiniWindow.window.removeAllListeners('close');
tidgiMiniWindow.window.on('close', (event) => {
event.preventDefault();
menuBar.hideWindow();
tidgiMiniWindow.hideWindow();
});
}
});
menuBar.on('hide', async () => {
// on mac, calling `menuBar.app.hide()` with main window open will bring background main window up, which we don't want. We want to bring previous other app up. So close main window first.
tidgiMiniWindow.on('hide', async () => {
// on mac, calling `tidgiMiniWindow.app.hide()` with main window open will bring background main window up, which we don't want. We want to bring previous other app up. So close main window first.
if (isMac) {
const mainWindow = windowService.get(WindowNames.main);
if (mainWindow?.isVisible() === true) {
@ -103,29 +106,29 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
}
});
// https://github.com/maxogden/menubar/issues/120
menuBar.on('after-hide', () => {
tidgiMiniWindow.on('after-hide', () => {
if (isMac) {
menuBar.app.hide();
tidgiMiniWindow.app.hide();
}
});
// manually save window state https://github.com/mawie81/electron-window-state/issues/64
const debouncedSaveWindowState = debounce(
() => {
if (menuBar.window !== undefined) {
windowWithBrowserViewState?.saveState(menuBar.window);
if (tidgiMiniWindow.window !== undefined) {
windowWithBrowserViewState?.saveState(tidgiMiniWindow.window);
}
},
500,
);
// menubar is hide, not close, so not managed by windowStateKeeper, need to save manually
menuBar.window?.on('resize', debouncedSaveWindowState);
menuBar.window?.on('move', debouncedSaveWindowState);
// tidgi mini window is hide, not close, so not managed by windowStateKeeper, need to save manually
tidgiMiniWindow.window?.on('resize', debouncedSaveWindowState);
tidgiMiniWindow.window?.on('move', debouncedSaveWindowState);
return await new Promise<Menubar>((resolve) => {
menuBar.on('ready', async () => {
tidgiMiniWindow.on('ready', async () => {
// right on tray icon
menuBar.tray.on('right-click', () => {
tidgiMiniWindow.tray.on('right-click', () => {
const contextMenu = Menu.buildFromTemplate([
{
label: i18n.t('ContextMenu.OpenTidGi'),
@ -134,9 +137,9 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
},
},
{
label: i18n.t('ContextMenu.OpenTidGiMenuBar'),
label: i18n.t('ContextMenu.OpenTidGiMiniWindow'),
click: async () => {
await menuBar.showWindow();
await tidgiMiniWindow.showWindow();
},
},
{
@ -165,23 +168,23 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
{
label: i18n.t('ContextMenu.Quit'),
click: () => {
menuBar.app.quit();
tidgiMiniWindow.app.quit();
},
},
]);
menuBar.tray.popUpContextMenu(contextMenu);
tidgiMiniWindow.tray.popUpContextMenu(contextMenu);
});
// right click on window content
if (menuBar.window?.webContents !== undefined) {
const unregisterContextMenu = await menuService.initContextMenuForWindowWebContents(menuBar.window.webContents);
menuBar.on('after-close', () => {
if (tidgiMiniWindow.window?.webContents !== undefined) {
const unregisterContextMenu = await menuService.initContextMenuForWindowWebContents(tidgiMiniWindow.window.webContents);
tidgiMiniWindow.on('after-close', () => {
unregisterContextMenu();
});
}
resolve(menuBar);
resolve(tidgiMiniWindow);
});
});
}

View file

@ -21,7 +21,7 @@ import { container } from '@services/container';
import getViewBounds from '@services/libs/getViewBounds';
import { logger } from '@services/libs/log';
import type { IThemeService } from '@services/theme/interface';
import { handleAttachToMenuBar } from './handleAttachToMenuBar';
import { handleAttachToTidgiMiniWindow } from './handleAttachToTidgiMiniWindow';
import { handleCreateBasicWindow } from './handleCreateBasicWindow';
import type { IWindowOpenConfig, IWindowService } from './interface';
import { registerBrowserViewWindowListeners } from './registerBrowserViewWindowListeners';
@ -32,8 +32,8 @@ import { getPreloadPath } from './viteEntry';
export class Window implements IWindowService {
private readonly windows = new Map<WindowNames, BrowserWindow>();
private windowMeta = {} as Partial<WindowMeta>;
/** menubar version of main window, if user set openInMenubar to true in preferences */
private mainWindowMenuBar?: Menubar;
/** tidgi mini window version of main window, if user set attachToTidgiMiniWindow to true in preferences */
private tidgiMiniWindowMenubar?: Menubar;
constructor(
@inject(serviceIdentifier.Preference) private readonly preferenceService: IPreferenceService,
@ -77,8 +77,8 @@ export class Window implements IWindowService {
}
public get(windowName: WindowNames = WindowNames.main): BrowserWindow | undefined {
if (windowName === WindowNames.menuBar) {
return this.mainWindowMenuBar?.window;
if (windowName === WindowNames.tidgiMiniWindow) {
return this.tidgiMiniWindowMenubar?.window;
}
return this.windows.get(windowName);
}
@ -125,8 +125,8 @@ export class Window implements IWindowService {
this.windows.clear();
}
public async isMenubarOpen(): Promise<boolean> {
return this.mainWindowMenuBar?.window?.isVisible() ?? false;
public async isTidgiMiniWindowOpen(): Promise<boolean> {
return this.tidgiMiniWindowMenubar?.window?.isVisible() ?? false;
}
public async open<N extends WindowNames>(windowName: N, meta?: WindowMeta[N], config?: IWindowOpenConfig<N>): Promise<undefined>;
@ -165,11 +165,11 @@ export class Window implements IWindowService {
// create new window
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
const { hideMenuBar: autoHideMenuBar, titleBar: showTitleBar, menuBarAlwaysOnTop, alwaysOnTop } = preferenceService.getPreferences();
const { hideMenuBar: autoHideMenuBar, titleBar: showTitleBar, tidgiMiniWindowAlwaysOnTop, alwaysOnTop } = preferenceService.getPreferences();
let windowWithBrowserViewConfig: Partial<BrowserWindowConstructorOptions> = {};
let windowWithBrowserViewState: windowStateKeeperState | undefined;
const WindowToKeepPositionState = [WindowNames.main, WindowNames.menuBar];
const WindowWithBrowserView = [WindowNames.main, WindowNames.menuBar, WindowNames.secondary];
const WindowToKeepPositionState = [WindowNames.main, WindowNames.tidgiMiniWindow];
const WindowWithBrowserView = [WindowNames.main, WindowNames.tidgiMiniWindow, WindowNames.secondary];
const isWindowWithBrowserView = WindowWithBrowserView.includes(windowName);
if (WindowToKeepPositionState.includes(windowName)) {
windowWithBrowserViewState = windowStateKeeper({
@ -186,7 +186,7 @@ export class Window implements IWindowService {
};
}
// hide titleBar should not take effect on setting window
const hideTitleBar = [WindowNames.main, WindowNames.menuBar].includes(windowName) && !showTitleBar;
const hideTitleBar = [WindowNames.main, WindowNames.tidgiMiniWindow].includes(windowName) && !showTitleBar;
const windowConfig: BrowserWindowConstructorOptions = {
...windowDimension[windowName],
...windowWithBrowserViewConfig,
@ -198,7 +198,7 @@ export class Window implements IWindowService {
titleBarStyle: hideTitleBar ? 'hidden' : 'default',
// https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#add-native-window-controls-windows-linux
...(hideTitleBar && process.platform !== 'darwin' ? { titleBarOverlay: true } : {}),
alwaysOnTop: windowName === WindowNames.menuBar ? menuBarAlwaysOnTop : alwaysOnTop,
alwaysOnTop: windowName === WindowNames.tidgiMiniWindow ? tidgiMiniWindowAlwaysOnTop : alwaysOnTop,
webPreferences: {
devTools: !isTest,
nodeIntegration: false,
@ -215,12 +215,12 @@ export class Window implements IWindowService {
parent: isWindowWithBrowserView ? undefined : this.get(WindowNames.main),
};
let newWindow: BrowserWindow;
if (windowName === WindowNames.menuBar) {
this.mainWindowMenuBar = await handleAttachToMenuBar(windowConfig, windowWithBrowserViewState);
if (this.mainWindowMenuBar.window === undefined) {
throw new Error('MenuBar failed to create window.');
if (windowName === WindowNames.tidgiMiniWindow) {
this.tidgiMiniWindowMenubar = await handleAttachToTidgiMiniWindow(windowConfig, windowWithBrowserViewState);
if (this.tidgiMiniWindowMenubar.window === undefined) {
throw new Error('TidgiMiniWindow failed to create window.');
}
newWindow = this.mainWindowMenuBar.window;
newWindow = this.tidgiMiniWindowMenubar.window;
} else {
newWindow = await handleCreateBasicWindow(windowName, windowConfig, meta, config);
if (isWindowWithBrowserView) {
@ -339,114 +339,114 @@ export class Window implements IWindowService {
}
}
public async toggleMenubarWindow(): Promise<void> {
logger.info('toggleMenubarWindow called', { function: 'toggleMenubarWindow' });
public async toggleTidgiMiniWindow(): Promise<void> {
logger.info('toggleTidgiMiniWindow called', { function: 'toggleTidgiMiniWindow' });
try {
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
const isOpen = await this.isMenubarOpen();
logger.debug('Menubar open status checked', { function: 'toggleMenubarWindow', isOpen });
const isOpen = await this.isTidgiMiniWindowOpen();
logger.debug('TidgiMiniWindow open status checked', { function: 'toggleTidgiMiniWindow', isOpen });
if (isOpen) {
logger.info('Closing menubar window', { function: 'toggleMenubarWindow' });
await this.closeMenubarWindow();
logger.info('Closing tidgi mini window', { function: 'toggleTidgiMiniWindow' });
await this.closeTidgiMiniWindow();
} else {
const attachToMenubar = await preferenceService.get('attachToMenubar');
logger.debug('attachToMenubar preference checked', { function: 'toggleMenubarWindow', attachToMenubar });
if (attachToMenubar) {
logger.info('Opening menubar window', { function: 'toggleMenubarWindow' });
await this.openMenubarWindow(true, true); // Explicitly show window when toggling
const attachToTidgiMiniWindow = await preferenceService.get('attachToTidgiMiniWindow');
logger.debug('attachToTidgiMiniWindow preference checked', { function: 'toggleTidgiMiniWindow', attachToTidgiMiniWindow });
if (attachToTidgiMiniWindow) {
logger.info('Opening tidgi mini window', { function: 'toggleTidgiMiniWindow' });
await this.openTidgiMiniWindow(true, true); // Explicitly show window when toggling
} else {
logger.warn('Cannot open menubar window: attachToMenubar preference is disabled', { function: 'toggleMenubarWindow' });
logger.warn('Cannot open tidgi mini window: attachToTidgiMiniWindow preference is disabled', { function: 'toggleTidgiMiniWindow' });
}
}
} catch (error) {
logger.error('Failed to open/hide menubar window', { error });
logger.error('Failed to open/hide tidgi mini window', { error });
}
}
public async openMenubarWindow(enableIt = true, showWindow = true): Promise<void> {
public async openTidgiMiniWindow(enableIt = true, showWindow = true): Promise<void> {
try {
// Check if menubar is already enabled
if (this.mainWindowMenuBar?.window !== undefined) {
logger.debug('Menubar is already enabled, bring it to front', { function: 'openMenubarWindow' });
// Check if tidgi mini window is already enabled
if (this.tidgiMiniWindowMenubar?.window !== undefined) {
logger.debug('TidGi mini window is already enabled, bring it to front', { function: 'openTidgiMiniWindow' });
if (showWindow) {
// Before showing, get the target workspace
const [menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('menubarSyncWorkspaceWithMainWindow'),
this.preferenceService.get('menubarFixedWorkspaceId'),
const [tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('tidgiMiniWindowSyncWorkspaceWithMainWindow'),
this.preferenceService.get('tidgiMiniWindowFixedWorkspaceId'),
]);
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
const targetWorkspaceId = shouldSync
? (await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace())?.id
: menubarFixedWorkspaceId;
: tidgiMiniWindowFixedWorkspaceId;
logger.info('openMenubarWindow: preparing to show window', {
function: 'openMenubarWindow',
logger.info('openTidgiMiniWindow: preparing to show window', {
function: 'openTidgiMiniWindow',
shouldSync,
targetWorkspaceId,
menubarSyncWorkspaceWithMainWindow,
menubarFixedWorkspaceId,
tidgiMiniWindowSyncWorkspaceWithMainWindow,
tidgiMiniWindowFixedWorkspaceId,
});
// Ensure view exists for the target workspace before realigning
if (targetWorkspaceId) {
const targetWorkspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(targetWorkspaceId);
if (targetWorkspace && !targetWorkspace.pageType) {
// This is a wiki workspace - ensure it has a view for menubar window
// This is a wiki workspace - ensure it has a view for tidgi mini window
const viewService = container.get<IViewService>(serviceIdentifier.View);
const existingView = viewService.getView(targetWorkspace.id, WindowNames.menuBar);
const existingView = viewService.getView(targetWorkspace.id, WindowNames.tidgiMiniWindow);
if (!existingView) {
logger.info('openMenubarWindow: creating missing menubar view', {
function: 'openMenubarWindow',
logger.info('openTidgiMiniWindow: creating missing tidgi mini window view', {
function: 'openTidgiMiniWindow',
workspaceId: targetWorkspace.id,
});
await viewService.addView(targetWorkspace, WindowNames.menuBar);
await viewService.addView(targetWorkspace, WindowNames.tidgiMiniWindow);
}
}
logger.info('openMenubarWindow: calling realignActiveWorkspace', {
function: 'openMenubarWindow',
logger.info('openTidgiMiniWindow: calling realignActiveWorkspace', {
function: 'openTidgiMiniWindow',
targetWorkspaceId,
});
await container.get<IWorkspaceViewService>(serviceIdentifier.WorkspaceView).realignActiveWorkspace(targetWorkspaceId);
logger.info('openMenubarWindow: realignActiveWorkspace completed', {
function: 'openMenubarWindow',
logger.info('openTidgiMiniWindow: realignActiveWorkspace completed', {
function: 'openTidgiMiniWindow',
targetWorkspaceId,
});
}
// Use menuBar.showWindow() instead of direct window.show() for proper menubar behavior
void this.mainWindowMenuBar.showWindow();
// Use menuBar.showWindow() instead of direct window.show() for proper tidgi mini window behavior
void this.tidgiMiniWindowMenubar.showWindow();
}
return;
}
// Create menubar window (create and open when enableIt is true)
await this.open(WindowNames.menuBar);
// Create tidgi mini window (create and open when enableIt is true)
await this.open(WindowNames.tidgiMiniWindow);
if (enableIt) {
logger.debug('Menubar enabled', { function: 'openMenubarWindow' });
// After creating the menubar, show it if requested
if (showWindow && this.mainWindowMenuBar) {
logger.debug('Showing newly created menubar window', { function: 'openMenubarWindow' });
void this.mainWindowMenuBar.showWindow();
logger.debug('TidGi mini window enabled', { function: 'openTidgiMiniWindow' });
// After creating the tidgi mini window, show it if requested
if (showWindow && this.tidgiMiniWindowMenubar) {
logger.debug('Showing newly created tidgi mini window', { function: 'openTidgiMiniWindow' });
void this.tidgiMiniWindowMenubar.showWindow();
}
}
} catch (error) {
logger.error('Failed to open menubar', { error, function: 'openMenubarWindow' });
logger.error('Failed to open tidgi mini window', { error, function: 'openTidgiMiniWindow' });
throw error;
}
}
public async closeMenubarWindow(disableIt = false): Promise<void> {
public async closeTidgiMiniWindow(disableIt = false): Promise<void> {
try {
// Check if menubar exists
if (this.mainWindowMenuBar === undefined) {
logger.debug('Menubar is already disabled', { function: 'closeMenubarWindow' });
// Check if tidgi mini window exists
if (this.tidgiMiniWindowMenubar === undefined) {
logger.debug('TidGi mini window is already disabled', { function: 'closeTidgiMiniWindow' });
return;
}
const menuBar = this.mainWindowMenuBar;
const menuBar = this.tidgiMiniWindowMenubar;
if (disableIt) {
// Fully destroy menubar: destroy window and tray, then clear reference
// Fully destroy tidgi mini window: destroy window and tray, then clear reference
if (menuBar.window) {
// remove custom close listener so destroy will actually close
menuBar.window.removeAllListeners('close');
@ -457,16 +457,16 @@ export class Window implements IWindowService {
if (menuBar.tray && !menuBar.tray.isDestroyed()) {
menuBar.tray.destroy();
}
this.mainWindowMenuBar = undefined;
logger.debug('Menubar disabled successfully without restart', { function: 'closeMenubarWindow' });
this.tidgiMiniWindowMenubar = undefined;
logger.debug('TidGi mini window disabled successfully without restart', { function: 'closeTidgiMiniWindow' });
} else {
// Only hide the menubar window (keep tray and instance for re-open)
// Use menuBar.hideWindow() for proper menubar behavior
// Only hide the tidgi mini window (keep tray and instance for re-open)
// Use menuBar.hideWindow() for proper tidgi mini window behavior
menuBar.hideWindow();
logger.debug('Menubar closed (kept enabled)', { function: 'closeMenubarWindow' });
logger.debug('TidGi mini window closed (kept enabled)', { function: 'closeTidgiMiniWindow' });
}
} catch (error) {
logger.error('Failed to close menubar', { error });
logger.error('Failed to close tidgi mini window', { error });
throw error;
}
}
@ -484,10 +484,10 @@ export class Window implements IWindowService {
logger.info(`Updated ${windowName} alwaysOnTop to ${properties.alwaysOnTop}`);
}
// Handle menubar specific properties
if (windowName === WindowNames.menuBar && this.mainWindowMenuBar?.window) {
// Handle tidgi mini window specific properties
if (windowName === WindowNames.tidgiMiniWindow && this.tidgiMiniWindowMenubar?.window) {
if (properties.alwaysOnTop !== undefined) {
this.mainWindowMenuBar.window.setAlwaysOnTop(properties.alwaysOnTop);
this.tidgiMiniWindowMenubar.window.setAlwaysOnTop(properties.alwaysOnTop);
}
}
} catch (error) {
@ -498,79 +498,79 @@ export class Window implements IWindowService {
public async reactWhenPreferencesChanged(key: string, value: unknown): Promise<void> {
switch (key) {
case 'attachToMenubar': {
case 'attachToTidgiMiniWindow': {
if (value) {
// Enable menubar without showing the window; visibility controlled by toggle/shortcut
await this.openMenubarWindow(true, false);
// Enable tidgi mini window without showing the window; visibility controlled by toggle/shortcut
await this.openTidgiMiniWindow(true, false);
// After enabling menubar, create view for the current active workspace (if it's a wiki workspace)
// After enabling tidgi mini window, create view for the current active workspace (if it's a wiki workspace)
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const viewService = container.get<IViewService>(serviceIdentifier.View);
const activeWorkspace = await workspaceService.getActiveWorkspace();
if (activeWorkspace && !activeWorkspace.pageType) {
// This is a wiki workspace - ensure it has a view for menubar window
const existingView = viewService.getView(activeWorkspace.id, WindowNames.menuBar);
// This is a wiki workspace - ensure it has a view for tidgi mini window
const existingView = viewService.getView(activeWorkspace.id, WindowNames.tidgiMiniWindow);
if (!existingView) {
await viewService.addView(activeWorkspace, WindowNames.menuBar);
await viewService.addView(activeWorkspace, WindowNames.tidgiMiniWindow);
}
}
} else {
await this.closeMenubarWindow(true);
await this.closeTidgiMiniWindow(true);
}
return;
}
case 'menubarSyncWorkspaceWithMainWindow':
case 'menubarFixedWorkspaceId': {
case 'tidgiMiniWindowSyncWorkspaceWithMainWindow':
case 'tidgiMiniWindowFixedWorkspaceId': {
logger.info('Preference changed', { function: 'reactWhenPreferencesChanged', key, value: JSON.stringify(value) });
// When menubar workspace settings change, hide all views and let the next window show trigger realignment
const menuBarWindow = this.get(WindowNames.menuBar);
if (menuBarWindow) {
// When tidgi mini window workspace settings change, hide all views and let the next window show trigger realignment
const tidgiMiniWindow = this.get(WindowNames.tidgiMiniWindow);
if (tidgiMiniWindow) {
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const viewService = container.get<IViewService>(serviceIdentifier.View);
const allWorkspaces = await workspaceService.getWorkspacesAsList();
logger.debug(`Hiding all menubar views (${allWorkspaces.length} workspaces)`, { function: 'reactWhenPreferencesChanged', key });
logger.debug(`Hiding all tidgi mini window views (${allWorkspaces.length} workspaces)`, { function: 'reactWhenPreferencesChanged', key });
// Hide all views - the correct view will be shown when window is next opened
await Promise.all(
allWorkspaces.map(async (workspace) => {
const view = viewService.getView(workspace.id, WindowNames.menuBar);
const view = viewService.getView(workspace.id, WindowNames.tidgiMiniWindow);
if (view) {
await viewService.hideView(menuBarWindow, WindowNames.menuBar, workspace.id);
await viewService.hideView(tidgiMiniWindow, WindowNames.tidgiMiniWindow, workspace.id);
}
}),
);
// View creation is handled by openMenubarWindow when the window is shown
// View creation is handled by openTidgiMiniWindow when the window is shown
} else {
logger.warn('menuBarWindow not found, skipping view management', { function: 'reactWhenPreferencesChanged', key });
logger.warn('tidgiMiniWindow not found, skipping view management', { function: 'reactWhenPreferencesChanged', key });
}
return;
}
case 'menuBarAlwaysOnTop': {
await this.updateWindowProperties(WindowNames.menuBar, { alwaysOnTop: value as boolean });
case 'tidgiMiniWindowAlwaysOnTop': {
await this.updateWindowProperties(WindowNames.tidgiMiniWindow, { alwaysOnTop: value as boolean });
return;
}
case 'showMenubarWindowTitleBar': {
case 'showTidgiMiniWindowTitleBar': {
// Title bar style requires recreating the window
// We need to fully destroy and recreate the menubar window with new titleBar settings
logger.info('showMenubarWindowTitleBar changed, recreating menubar window', {
// We need to fully destroy and recreate the tidgi mini window with new titleBar settings
logger.info('showTidgiMiniWindowTitleBar changed, recreating tidgi mini window', {
function: 'reactWhenPreferencesChanged',
newValue: value,
});
const wasVisible = await this.isMenubarOpen();
logger.debug('Current menubar visibility', {
const wasVisible = await this.isTidgiMiniWindowOpen();
logger.debug('Current tidgi mini window visibility', {
function: 'reactWhenPreferencesChanged',
wasVisible,
});
// Fully destroy current menubar window (disableIt = true)
await this.closeMenubarWindow(true);
logger.debug('Menubar window destroyed', { function: 'reactWhenPreferencesChanged' });
// Fully destroy current tidgi mini window (disableIt = true)
await this.closeTidgiMiniWindow(true);
logger.debug('Tidgi mini window destroyed', { function: 'reactWhenPreferencesChanged' });
// Reopen menubar window with new titleBar setting from updated preferences
// Reopen tidgi mini window with new titleBar setting from updated preferences
// enableIt = true to recreate, showWindow = wasVisible to restore visibility
await this.openMenubarWindow(true, wasVisible);
logger.info('Menubar window recreated with new titleBar setting', {
await this.openTidgiMiniWindow(true, wasVisible);
logger.info('Tidgi mini window recreated with new titleBar setting', {
function: 'reactWhenPreferencesChanged',
showWindow: wasVisible,
});

View file

@ -37,7 +37,7 @@ export interface IWindowService {
*/
hide(windowName: WindowNames): Promise<void>;
isFullScreen(windowName?: WindowNames): Promise<boolean | undefined>;
isMenubarOpen(): Promise<boolean>;
isTidgiMiniWindowOpen(): Promise<boolean>;
loadURL(windowName: WindowNames, newUrl?: string): Promise<void>;
maximize(): Promise<void>;
/**
@ -55,15 +55,15 @@ export interface IWindowService {
set(windowName: WindowNames, win: BrowserWindow | undefined): void;
setWindowMeta<N extends WindowNames>(windowName: N, meta?: WindowMeta[N]): Promise<void>;
stopFindInPage(close?: boolean, windowName?: WindowNames): Promise<void>;
toggleMenubarWindow(): Promise<void>;
toggleTidgiMiniWindow(): Promise<void>;
updateWindowMeta<N extends WindowNames>(windowName: N, meta?: WindowMeta[N]): Promise<void>;
/** Open menubar window without restart - hot reload. enableIt=true means fully enable and open. */
openMenubarWindow(enableIt?: boolean, showWindow?: boolean): Promise<void>;
/** Close menubar window. disableIt=true means fully disable and cleanup tray. */
closeMenubarWindow(disableIt?: boolean): Promise<void>;
/** Open tidgi mini window without restart - hot reload. enableIt=true means fully enable and open. */
openTidgiMiniWindow(enableIt?: boolean, showWindow?: boolean): Promise<void>;
/** Close tidgi mini window. disableIt=true means fully disable and cleanup tray. */
closeTidgiMiniWindow(disableIt?: boolean): Promise<void>;
/** Update window properties without restart - hot reload */
updateWindowProperties(windowName: WindowNames, properties: { alwaysOnTop?: boolean }): Promise<void>;
/** React to preference changes related to windows (menubar etc.) */
/** React to preference changes related to windows (tidgi mini window etc.) */
reactWhenPreferencesChanged(key: string, value: unknown): Promise<void>;
}
export const WindowServiceIPCDescriptor = {
@ -78,7 +78,7 @@ export const WindowServiceIPCDescriptor = {
goForward: ProxyPropertyType.Function,
goHome: ProxyPropertyType.Function,
isFullScreen: ProxyPropertyType.Function,
isMenubarOpen: ProxyPropertyType.Function,
isTidgiMiniWindowOpen: ProxyPropertyType.Function,
loadURL: ProxyPropertyType.Function,
maximize: ProxyPropertyType.Function,
open: ProxyPropertyType.Function,
@ -87,10 +87,10 @@ export const WindowServiceIPCDescriptor = {
sendToAllWindows: ProxyPropertyType.Function,
setWindowMeta: ProxyPropertyType.Function,
stopFindInPage: ProxyPropertyType.Function,
toggleMenubarWindow: ProxyPropertyType.Function,
toggleTidgiMiniWindow: ProxyPropertyType.Function,
updateWindowMeta: ProxyPropertyType.Function,
openMenubarWindow: ProxyPropertyType.Function,
closeMenubarWindow: ProxyPropertyType.Function,
openTidgiMiniWindow: ProxyPropertyType.Function,
closeTidgiMiniWindow: ProxyPropertyType.Function,
updateWindowProperties: ProxyPropertyType.Function,
},
};

View file

@ -123,9 +123,9 @@ export async function registerMenu(): Promise<void> {
// if back is called in popup window
// navigate in the popup window instead
if (browserWindow !== undefined) {
// TODO: test if we really can get this isPopup value, and it works for help page popup and menubar window
// TODO: test if we really can get this isPopup value, and it works for help page popup and tidgi mini window
// const { isPopup = false } = await getFromRenderer<IBrowserViewMetaData>(MetaDataChannel.getViewMetaData, browserWindow);
// const windowName = isPopup ? WindowNames.menuBar : WindowNames.main
// const windowName = isPopup ? WindowNames.tidgiMiniWindow : WindowNames.main
await windowService.goForward();
}

View file

@ -171,7 +171,7 @@ export class WorkspaceView implements IWorkspaceViewService {
logger.debug('Skip because alreadyHaveView');
return;
}
// Create browserView, and if user want a menubar, we also create a new window for that
// Create browserView, and if user want a tidgi mini window, we also create a new window for that
await this.addViewForAllBrowserViews(workspace);
if (isNew && options.from === WikiCreationMethod.Create) {
const view = container.get<IViewService>(serviceIdentifier.View).getView(workspace.id, WindowNames.main);
@ -205,39 +205,39 @@ export class WorkspaceView implements IWorkspaceViewService {
public async addViewForAllBrowserViews(workspace: IWorkspace): Promise<void> {
const mainTask = container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.main);
// For menubar window, decide which workspace to show based on preferences
const menubarTask = (async () => {
const [attachToMenubar, menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('attachToMenubar'),
this.preferenceService.get('menubarSyncWorkspaceWithMainWindow'),
this.preferenceService.get('menubarFixedWorkspaceId'),
// For tidgi mini window, decide which workspace to show based on preferences
const tidgiMiniWindowTask = (async () => {
const [attachToTidgiMiniWindow, tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('attachToTidgiMiniWindow'),
this.preferenceService.get('tidgiMiniWindowSyncWorkspaceWithMainWindow'),
this.preferenceService.get('tidgiMiniWindowFixedWorkspaceId'),
]);
if (!attachToMenubar) {
if (!attachToTidgiMiniWindow) {
return;
}
// If syncing with main window (undefined means default to true, or explicitly true), use the current workspace
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
if (shouldSync) {
// Don't add view for pageType workspaces (they don't have browser views)
if (!workspace.pageType) {
await container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.menuBar);
await container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.tidgiMiniWindow);
}
return;
}
// If not syncing and a fixed workspace is set, only add view if this IS the fixed workspace
if (menubarFixedWorkspaceId && workspace.id === menubarFixedWorkspaceId) {
if (tidgiMiniWindowFixedWorkspaceId && workspace.id === tidgiMiniWindowFixedWorkspaceId) {
// Don't add view for pageType workspaces (they don't have browser views)
if (!workspace.pageType) {
await container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.menuBar);
await container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.tidgiMiniWindow);
}
}
// If not syncing and no fixed workspace is set, don't add any view (user needs to select one)
})();
await Promise.all([mainTask, menubarTask]);
await Promise.all([mainTask, tidgiMiniWindowTask]);
}
public async openWorkspaceWindowWithView(workspace: IWorkspace, configs?: { uri?: string }): Promise<void> {
@ -484,7 +484,7 @@ export class WorkspaceView implements IWorkspaceViewService {
await Promise.all(
workspaces.map(async (workspace) => {
await Promise.all(
[WindowNames.main, WindowNames.menuBar].map(async (windowName) => {
[WindowNames.main, WindowNames.tidgiMiniWindow].map(async (windowName) => {
const view = container.get<IViewService>(serviceIdentifier.View).getView(workspace.id, windowName);
if (view !== undefined) {
await container.get<IViewService>(serviceIdentifier.View).loadUrlForView(workspace, view);
@ -577,7 +577,7 @@ export class WorkspaceView implements IWorkspaceViewService {
return;
}
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
const menuBarWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.menuBar);
const tidgiMiniWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.tidgiMiniWindow);
logger.info(
`realignActiveWorkspaceView: id ${workspaceToRealign?.id ?? 'undefined'}`,
@ -586,7 +586,7 @@ export class WorkspaceView implements IWorkspaceViewService {
logger.warn('realignActiveWorkspaceView: no active workspace');
return;
}
if (mainWindow === undefined && menuBarWindow === undefined) {
if (mainWindow === undefined && tidgiMiniWindow === undefined) {
logger.warn('realignActiveWorkspaceView: no active window');
return;
}
@ -597,44 +597,46 @@ export class WorkspaceView implements IWorkspaceViewService {
tasks.push(container.get<IViewService>(serviceIdentifier.View).realignActiveView(mainWindow, workspaceToRealign.id, WindowNames.main));
logger.debug(`realignActiveWorkspaceView: realign main window for ${workspaceToRealign.id}.`);
}
if (menuBarWindow === undefined) {
logger.info(`realignActiveWorkspaceView: no menuBarBrowserViewWebContent, skip menu bar window for ${workspaceToRealign.id}.`);
if (tidgiMiniWindow === undefined) {
logger.info(`realignActiveWorkspaceView: no tidgiMiniWindowBrowserViewWebContent, skip tidgi mini window for ${workspaceToRealign.id}.`);
} else {
// For menubar window, decide which workspace to show based on preferences
const [menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('menubarSyncWorkspaceWithMainWindow'),
this.preferenceService.get('menubarFixedWorkspaceId'),
// For tidgi mini window, decide which workspace to show based on preferences
const [tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('tidgiMiniWindowSyncWorkspaceWithMainWindow'),
this.preferenceService.get('tidgiMiniWindowFixedWorkspaceId'),
]);
// Default to sync (undefined or true), otherwise use fixed workspace ID (fallback to main if not set)
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
const menubarWorkspaceId = shouldSync ? workspaceToRealign.id : (menubarFixedWorkspaceId || workspaceToRealign.id);
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
const tidgiMiniWindowWorkspaceId = shouldSync ? workspaceToRealign.id : (tidgiMiniWindowFixedWorkspaceId || workspaceToRealign.id);
// Check if the target workspace is a pageType workspace (which doesn't have a view)
let shouldShowMenubarView = true;
const menubarTargetWorkspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(menubarWorkspaceId);
let shouldShowTidgiMiniWindowView = true;
const tidgiMiniWindowTargetWorkspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(tidgiMiniWindowWorkspaceId);
if (menubarTargetWorkspace?.pageType) {
if (tidgiMiniWindowTargetWorkspace?.pageType) {
logger.debug(
`realignActiveWorkspaceView: skip menu bar window because workspace ${menubarWorkspaceId} is a page type workspace. Hiding all views.`,
`realignActiveWorkspaceView: skip tidgi mini window because workspace ${tidgiMiniWindowWorkspaceId} is a page type workspace. Hiding all views.`,
);
shouldShowMenubarView = false; // Don't show any view for pageType workspaces
// Hide all existing views in the menubar window
shouldShowTidgiMiniWindowView = false; // Don't show any view for pageType workspaces
// Hide all existing views in the tidgi mini window
const allWorkspaces = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getWorkspacesAsList();
await Promise.all(
allWorkspaces.map(async (workspace) => {
const view = container.get<IViewService>(serviceIdentifier.View).getView(workspace.id, WindowNames.menuBar);
if (view) {
await container.get<IViewService>(serviceIdentifier.View).hideView(menuBarWindow, WindowNames.menuBar, workspace.id);
const view = container.get<IViewService>(serviceIdentifier.View).getView(workspace.id, WindowNames.tidgiMiniWindow);
if (view !== undefined) {
await container.get<IViewService>(serviceIdentifier.View).hideView(tidgiMiniWindow, WindowNames.tidgiMiniWindow, workspace.id);
}
}),
);
}
if (shouldShowMenubarView) {
logger.debug(
`realignActiveWorkspaceView: realign menu bar window for ${menubarWorkspaceId} (main: ${workspaceToRealign.id}, sync: ${String(menubarSyncWorkspaceWithMainWindow)}).`,
if (shouldShowTidgiMiniWindowView) {
logger.info(
`realignActiveWorkspaceView: realign tidgi mini window for ${tidgiMiniWindowWorkspaceId} (main: ${workspaceToRealign.id}, sync: ${
String(tidgiMiniWindowSyncWorkspaceWithMainWindow)
}).`,
);
tasks.push(container.get<IViewService>(serviceIdentifier.View).realignActiveView(menuBarWindow, menubarWorkspaceId, WindowNames.menuBar));
tasks.push(container.get<IViewService>(serviceIdentifier.View).realignActiveView(tidgiMiniWindow, tidgiMiniWindowWorkspaceId, WindowNames.tidgiMiniWindow));
}
}
await Promise.all(tasks);
@ -642,7 +644,7 @@ export class WorkspaceView implements IWorkspaceViewService {
private async hideWorkspaceView(idToDeactivate: string): Promise<void> {
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
const menuBarWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.menuBar);
const tidgiMiniWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.tidgiMiniWindow);
const tasks = [];
if (mainWindow === undefined) {
logger.warn(`hideWorkspaceView: no mainBrowserWindow, skip main window browserView.`);
@ -650,26 +652,26 @@ export class WorkspaceView implements IWorkspaceViewService {
logger.info(`hideWorkspaceView: hide main window browserView.`);
tasks.push(container.get<IViewService>(serviceIdentifier.View).hideView(mainWindow, WindowNames.main, idToDeactivate));
}
if (menuBarWindow === undefined) {
logger.debug(`hideWorkspaceView: no menuBarBrowserWindow, skip menu bar window browserView.`);
if (tidgiMiniWindow === undefined) {
logger.debug(`hideWorkspaceView: no tidgiMiniWindowBrowserWindow, skip tidgi mini window browserView.`);
} else {
// For menubar window, only hide if syncing with main window OR if this is the fixed workspace being deactivated
const [menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('menubarSyncWorkspaceWithMainWindow'),
this.preferenceService.get('menubarFixedWorkspaceId'),
// For tidgi mini window, only hide if syncing with main window OR if this is the fixed workspace being deactivated
const [tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId] = await Promise.all([
this.preferenceService.get('tidgiMiniWindowSyncWorkspaceWithMainWindow'),
this.preferenceService.get('tidgiMiniWindowFixedWorkspaceId'),
]);
// Default to sync (undefined or true)
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow;
const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
// Only hide menubar view if:
// Only hide tidgi mini window view if:
// 1. Syncing with main window (should hide when main window hides)
// 2. OR the workspace being hidden is the fixed workspace (rare case, but should be handled)
if (shouldSync || idToDeactivate === menubarFixedWorkspaceId) {
logger.info(`hideWorkspaceView: hide menu bar window browserView.`);
tasks.push(container.get<IViewService>(serviceIdentifier.View).hideView(menuBarWindow, WindowNames.menuBar, idToDeactivate));
if (shouldSync || idToDeactivate === tidgiMiniWindowFixedWorkspaceId) {
logger.info(`hideWorkspaceView: hide tidgi mini window browserView.`);
tasks.push(container.get<IViewService>(serviceIdentifier.View).hideView(tidgiMiniWindow, WindowNames.tidgiMiniWindow, idToDeactivate));
} else {
logger.debug(`hideWorkspaceView: skip hiding menu bar window browserView (fixed workspace: ${menubarFixedWorkspaceId || 'none'}).`);
logger.debug(`hideWorkspaceView: skip hiding tidgi mini window browserView (fixed workspace: ${tidgiMiniWindowFixedWorkspaceId || 'none'}).`);
}
}
await Promise.all(tasks);

View file

@ -23,7 +23,7 @@ import { Search } from './sections/Search';
import { Sync } from './sections/Sync';
import { System } from './sections/System';
import { TiddlyWiki } from './sections/TiddlyWiki';
import { TidGiMenubarWindow } from './sections/TidGiMenubarWindow';
import { TidGiMiniWindow } from './sections/TidGiMiniWindow';
import { Updates } from './sections/Updates';
import { SectionSideBar } from './SectionsSideBar';
import { usePreferenceSections } from './useSections';
@ -67,7 +67,7 @@ export default function Preferences(): React.JSX.Element {
<Inner>
<TiddlyWiki sections={sections} requestRestartCountDown={requestRestartCountDown} />
<General sections={sections} requestRestartCountDown={requestRestartCountDown} />
<TidGiMenubarWindow sections={sections} />
<TidGiMiniWindow sections={sections} />
<Sync sections={sections} requestRestartCountDown={requestRestartCountDown} />
<ExternalAPI sections={sections} />
<AIAgent sections={sections} />

View file

@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
import { Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function TidGiMenubarWindow(props: Partial<ISectionProps>): React.JSX.Element {
export function TidGiMiniWindow(props: Partial<ISectionProps>): React.JSX.Element {
const { t } = useTranslation();
const preference = usePreferenceObservable();
const platform = usePromiseValue(async () => await window.service.context.get('platform'));
@ -21,31 +21,31 @@ export function TidGiMenubarWindow(props: Partial<ISectionProps>): React.JSX.Ele
return (
<>
<SectionTitle ref={props.sections?.menubar.ref}>{t('Menu.TidGiMenuBar')}</SectionTitle>
<SectionTitle ref={props.sections?.tidgiMiniWindow.ref}>{t('Menu.TidGiMiniWindow')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{/* Attach to taskbar/menubar settings */}
{/* Attach to taskbar/system tray settings */}
<ListItem
secondaryAction={
<Switch
edge='end'
color='primary'
checked={preference.attachToMenubar}
checked={preference.attachToTidgiMiniWindow}
onChange={async (event) => {
await window.service.preference.set('attachToMenubar', event.target.checked);
await window.service.preference.set('attachToTidgiMiniWindow', event.target.checked);
}}
data-testid='attach-to-menubar-switch'
data-testid='attach-to-tidgi-mini-window-switch'
/>
}
>
<ListItemText
primary={platform === 'win32' ? t('Preference.AttachToTaskbar') : t('Preference.AttachToMenuBar')}
secondary={platform === 'linux' ? undefined : t('Preference.AttachToMenuBarTip')}
primary={platform === 'win32' ? t('Preference.AttachToTaskbar') : t('Preference.AttachToTidgiMiniWindow')}
secondary={platform === 'linux' ? undefined : t('Preference.AttachToTidgiMiniWindowTip')}
/>
</ListItem>
{/* Other settings are only visible when attached to taskbar/menubar */}
{preference.attachToMenubar && (
{/* Other settings are only visible when attached to taskbar/system tray */}
{preference.attachToTidgiMiniWindow && (
<>
{/* Sidebar display settings */}
<ListItem
@ -53,57 +53,57 @@ export function TidGiMenubarWindow(props: Partial<ISectionProps>): React.JSX.Ele
<Switch
edge='end'
color='primary'
checked={preference.sidebarOnMenubar}
checked={preference.sidebarOnTidgiMiniWindow}
onChange={async (event) => {
await window.service.preference.set('sidebarOnMenubar', event.target.checked);
await window.service.preference.set('sidebarOnTidgiMiniWindow', event.target.checked);
}}
data-testid='sidebar-on-menubar-switch'
data-testid='sidebar-on-tidgi-mini-window-switch'
/>
}
>
<ListItemText
primary={platform === 'win32' ? t('Preference.AttachToTaskbarShowSidebar') : t('Preference.AttachToMenuBarShowSidebar')}
secondary={platform === 'linux' ? undefined : t('Preference.AttachToMenuBarShowSidebarTip')}
primary={platform === 'win32' ? t('Preference.AttachToTaskbarShowSidebar') : t('Preference.AttachToTidgiMiniWindowShowSidebar')}
secondary={platform === 'linux' ? undefined : t('Preference.AttachToTidgiMiniWindowShowSidebarTip')}
/>
</ListItem>
<Divider />
{/* Show title bar on menubar window */}
{/* Show title bar on tidgi mini window */}
<ListItem
secondaryAction={
<Switch
edge='end'
color='primary'
checked={preference.showMenubarWindowTitleBar}
checked={preference.showTidgiMiniWindowTitleBar}
onChange={async (event) => {
await window.service.preference.set('showMenubarWindowTitleBar', event.target.checked);
await window.service.preference.set('showTidgiMiniWindowTitleBar', event.target.checked);
}}
data-testid='menubar-titlebar-switch'
data-testid='tidgi-mini-window-titlebar-switch'
/>
}
>
<ListItemText
primary={t('Preference.ShowMenubarWindowTitleBar')}
secondary={t('Preference.ShowMenubarWindowTitleBarDetail')}
primary={t('Preference.ShowTidgiMiniWindowTitleBar')}
secondary={t('Preference.ShowTidgiMiniWindowTitleBarDetail')}
/>
</ListItem>
{/* Keep menubar window on top of other windows */}
{/* Keep tidgi mini window on top of other windows */}
<ListItem
secondaryAction={
<Switch
edge='end'
color='primary'
checked={preference.menuBarAlwaysOnTop}
checked={preference.tidgiMiniWindowAlwaysOnTop}
onChange={async (event) => {
await window.service.preference.set('menuBarAlwaysOnTop', event.target.checked);
await window.service.preference.set('tidgiMiniWindowAlwaysOnTop', event.target.checked);
}}
data-testid='menubar-always-on-top-switch'
data-testid='tidgi-mini-window-always-on-top-switch'
/>
}
>
<ListItemText primary={t('Preference.MenubarAlwaysOnTop')} secondary={t('Preference.MenubarAlwaysOnTopDetail')} />
<ListItemText primary={t('Preference.TidgiMiniWindowAlwaysOnTop')} secondary={t('Preference.TidgiMiniWindowAlwaysOnTopDetail')} />
</ListItem>
<Divider />
@ -114,32 +114,32 @@ export function TidGiMenubarWindow(props: Partial<ISectionProps>): React.JSX.Ele
<Switch
edge='end'
color='primary'
checked={preference.menubarSyncWorkspaceWithMainWindow}
checked={preference.tidgiMiniWindowSyncWorkspaceWithMainWindow}
onChange={async (event) => {
await window.service.preference.set('menubarSyncWorkspaceWithMainWindow', event.target.checked);
await window.service.preference.set('tidgiMiniWindowSyncWorkspaceWithMainWindow', event.target.checked);
}}
data-testid='menubar-sync-workspace-switch'
data-testid='tidgi-mini-window-sync-workspace-switch'
/>
}
>
<ListItemText
primary={t('Preference.MenubarSyncWorkspaceWithMainWindow')}
secondary={t('Preference.MenubarSyncWorkspaceWithMainWindowDetail')}
primary={t('Preference.TidgiMiniWindowSyncWorkspaceWithMainWindow')}
secondary={t('Preference.TidgiMiniWindowSyncWorkspaceWithMainWindowDetail')}
/>
</ListItem>
{/* Select fixed workspace for TidGi menubar window */}
{!preference.menubarSyncWorkspaceWithMainWindow && (
{/* Select fixed workspace for TidGi mini window */}
{!preference.tidgiMiniWindowSyncWorkspaceWithMainWindow && (
<Box sx={{ p: 2 }}>
<FormControl fullWidth variant='outlined' sx={{ mt: 1 }}>
<InputLabel>{t('Preference.MenubarFixedWorkspace')}</InputLabel>
<InputLabel>{t('Preference.TidgiMiniWindowFixedWorkspace')}</InputLabel>
<Select
value={preference.menubarFixedWorkspaceId || ''}
value={preference.tidgiMiniWindowFixedWorkspaceId || ''}
onChange={async (event) => {
await window.service.preference.set('menubarFixedWorkspaceId', event.target.value);
await window.service.preference.set('tidgiMiniWindowFixedWorkspaceId', event.target.value);
}}
label={t('Preference.MenubarFixedWorkspace')}
inputProps={{ 'data-testid': 'menubar-fixed-workspace-select' }}
label={t('Preference.TidgiMiniWindowFixedWorkspace')}
inputProps={{ 'data-testid': 'tidgi-mini-window-fixed-workspace-select' }}
>
<MenuItem value=''>{t('Preference.SelectWorkspace')}</MenuItem>
{workspaces?.map((workspace) => (
@ -152,23 +152,23 @@ export function TidGiMenubarWindow(props: Partial<ISectionProps>): React.JSX.Ele
</Box>
)}
{/* Set shortcut key to toggle TidGi menubar window */}
{/* Set shortcut key to toggle TidGi mini window */}
<Box sx={{ p: 2 }}>
<KeyboardShortcutRegister
label={t('Preference.MenubarShortcutKey')}
value={preference.keyboardShortcuts?.['Window.toggleMenubarWindow'] || ''}
label={t('Preference.TidgiMiniWindowShortcutKey')}
value={preference.keyboardShortcuts?.['Window.toggleTidgiMiniWindow'] || ''}
onChange={async (value) => {
if (value && value.trim() !== '') {
await window.service.native.registerKeyboardShortcut<IWindowService>('Window', 'toggleMenubarWindow', value);
await window.service.native.registerKeyboardShortcut<IWindowService>('Window', 'toggleTidgiMiniWindow', value);
} else {
await window.service.native.unregisterKeyboardShortcut<IWindowService>('Window', 'toggleMenubarWindow');
await window.service.native.unregisterKeyboardShortcut<IWindowService>('Window', 'toggleTidgiMiniWindow');
}
}}
data-testid='menubar-shortcut-input'
data-testid='tidgi-mini-window-shortcut-input'
/>
<Box sx={{ mt: 1 }}>
<Typography variant='caption' color='textSecondary'>
{t('Preference.MenubarShortcutKeyHelperText')}
{t('Preference.TidgiMiniWindowShortcutKeyHelperText')}
</Typography>
</Box>
</Box>

View file

@ -10,7 +10,7 @@ import { defaultPreferences } from '@services/preferences/defaultPreferences';
import type { IPreferences } from '@services/preferences/interface';
import { SupportedStorageServices } from '@services/types';
import type { IWorkspace } from '@services/workspaces/interface';
import { TidGiMenubarWindow } from '../TidGiMenubarWindow';
import { TidGiMiniWindow } from '../TidGiMiniWindow';
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<ThemeProvider theme={lightTheme}>
@ -87,14 +87,14 @@ const createMockPreference = (overrides: Partial<IPreferences> = {}): IPreferenc
...overrides,
});
describe('TidGiMenubarWindow Component', () => {
describe('TidGiMiniWindow Component', () => {
let preferenceSubject: BehaviorSubject<IPreferences | undefined>;
beforeEach(() => {
vi.clearAllMocks();
preferenceSubject = new BehaviorSubject<IPreferences | undefined>(
createMockPreference({ attachToMenubar: false }),
createMockPreference({ attachToTidgiMiniWindow: false }),
);
Object.defineProperty(window.observables.preference, 'preference$', {
@ -136,7 +136,7 @@ describe('TidGiMenubarWindow Component', () => {
const renderComponent = async () => {
const result = render(
<TestWrapper>
<TidGiMenubarWindow />
<TidGiMiniWindow />
</TestWrapper>,
);
@ -147,7 +147,7 @@ describe('TidGiMenubarWindow Component', () => {
// Ensure component is fully rendered
await waitFor(() => {
expect(screen.getByText('Menu.TidGiMenuBar')).toBeInTheDocument();
expect(screen.getByText('Menu.TidGiMiniWindow')).toBeInTheDocument();
});
return result;
@ -166,7 +166,7 @@ describe('TidGiMenubarWindow Component', () => {
const { unmount } = render(
<TestWrapper>
<TidGiMenubarWindow />
<TidGiMiniWindow />
</TestWrapper>,
);
@ -180,7 +180,7 @@ describe('TidGiMenubarWindow Component', () => {
it('should render after loading with preferences', async () => {
await renderComponent();
expect(screen.getByText('Menu.TidGiMenuBar')).toBeInTheDocument();
expect(screen.getByText('Menu.TidGiMiniWindow')).toBeInTheDocument();
});
it('should load platform information from backend on mount', async () => {
@ -195,7 +195,7 @@ describe('TidGiMenubarWindow Component', () => {
expect(window.service.workspace.getWorkspacesAsList).toHaveBeenCalled();
});
it('should display correct attach to menubar text for Windows', async () => {
it('should display correct attach to tidgi mini window text for Windows', async () => {
Object.defineProperty(window.service.context, 'get', {
value: vi.fn().mockResolvedValue('win32'),
writable: true,
@ -206,7 +206,7 @@ describe('TidGiMenubarWindow Component', () => {
expect(screen.getByText('Preference.AttachToTaskbar')).toBeInTheDocument();
});
it('should display correct attach to menubar text for macOS', async () => {
it('should display correct attach to tidgi mini window text for macOS', async () => {
Object.defineProperty(window.service.context, 'get', {
value: vi.fn().mockResolvedValue('darwin'),
writable: true,
@ -214,13 +214,13 @@ describe('TidGiMenubarWindow Component', () => {
await renderComponent();
expect(screen.getByText('Preference.AttachToMenuBar')).toBeInTheDocument();
expect(screen.getByText('Preference.AttachToTidgiMiniWindow')).toBeInTheDocument();
});
});
describe('Attach to menubar toggle', () => {
it('should display attach to menubar switch with correct initial state', async () => {
preferenceSubject.next(createMockPreference({ attachToMenubar: false }));
describe('Attach to tidgi mini window toggle', () => {
it('should display attach to tidgi mini window switch with correct initial state', async () => {
preferenceSubject.next(createMockPreference({ attachToTidgiMiniWindow: false }));
await renderComponent();
const switches = screen.getAllByRole('checkbox');
@ -228,9 +228,9 @@ describe('TidGiMenubarWindow Component', () => {
expect(attachSwitch).not.toBeChecked();
});
it('should call backend API when attach to menubar is toggled', async () => {
it('should call backend API when attach to tidgi mini window is toggled', async () => {
const user = userEvent.setup();
preferenceSubject.next(createMockPreference({ attachToMenubar: false }));
preferenceSubject.next(createMockPreference({ attachToTidgiMiniWindow: false }));
await renderComponent();
const switches = screen.getAllByRole('checkbox');
@ -239,13 +239,13 @@ describe('TidGiMenubarWindow Component', () => {
await user.click(attachSwitch);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('attachToMenubar', true);
expect(window.service.preference.set).toHaveBeenCalledWith('attachToTidgiMiniWindow', true);
});
});
it('should toggle attach to menubar setting', async () => {
it('should toggle attach to tidgi mini window setting', async () => {
const user = userEvent.setup();
preferenceSubject.next(createMockPreference({ attachToMenubar: false }));
preferenceSubject.next(createMockPreference({ attachToTidgiMiniWindow: false }));
await renderComponent();
const switches = screen.getAllByRole('checkbox');
@ -254,45 +254,45 @@ describe('TidGiMenubarWindow Component', () => {
await user.click(attachSwitch);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('attachToMenubar', true);
expect(window.service.preference.set).toHaveBeenCalledWith('attachToTidgiMiniWindow', true);
});
});
});
describe('Conditional settings visibility', () => {
it('should hide additional settings when attachToMenubar is false', async () => {
preferenceSubject.next(createMockPreference({ attachToMenubar: false }));
it('should hide additional settings when attachToTidgiMiniWindow is false', async () => {
preferenceSubject.next(createMockPreference({ attachToTidgiMiniWindow: false }));
await renderComponent();
expect(screen.queryByText('Preference.AttachToTaskbarShowSidebar')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.AttachToMenuBarShowSidebar')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.MenubarAlwaysOnTop')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.MenubarSyncWorkspaceWithMainWindow')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.AttachToTidgiMiniWindowShowSidebar')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.TidgiMiniWindowAlwaysOnTop')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.TidgiMiniWindowSyncWorkspaceWithMainWindow')).not.toBeInTheDocument();
});
it('should show additional settings when attachToMenubar is true', async () => {
it('should show additional settings when attachToTidgiMiniWindow is true', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
sidebarOnMenubar: false,
menuBarAlwaysOnTop: false,
menubarSyncWorkspaceWithMainWindow: true,
attachToTidgiMiniWindow: true,
sidebarOnTidgiMiniWindow: false,
tidgiMiniWindowAlwaysOnTop: false,
tidgiMiniWindowSyncWorkspaceWithMainWindow: true,
}),
);
await renderComponent();
expect(screen.getByText('Preference.AttachToTaskbarShowSidebar')).toBeInTheDocument();
expect(screen.getByText('Preference.MenubarAlwaysOnTop')).toBeInTheDocument();
expect(screen.getByText('Preference.MenubarSyncWorkspaceWithMainWindow')).toBeInTheDocument();
expect(screen.getByText('Preference.TidgiMiniWindowAlwaysOnTop')).toBeInTheDocument();
expect(screen.getByText('Preference.TidgiMiniWindowSyncWorkspaceWithMainWindow')).toBeInTheDocument();
});
});
describe('Sidebar on menubar toggle', () => {
describe('Sidebar on tidgi mini window toggle', () => {
it('should display sidebar toggle with correct initial state', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
sidebarOnMenubar: false,
attachToTidgiMiniWindow: true,
sidebarOnTidgiMiniWindow: false,
}),
);
await renderComponent();
@ -310,8 +310,8 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
sidebarOnMenubar: false,
attachToTidgiMiniWindow: true,
sidebarOnTidgiMiniWindow: false,
}),
);
await renderComponent();
@ -322,7 +322,7 @@ describe('TidGiMenubarWindow Component', () => {
await user.click(sidebarSwitch);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('sidebarOnMenubar', true);
expect(window.service.preference.set).toHaveBeenCalledWith('sidebarOnTidgiMiniWindow', true);
});
});
});
@ -331,8 +331,8 @@ describe('TidGiMenubarWindow Component', () => {
it('should display always on top toggle with correct initial state', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menuBarAlwaysOnTop: false,
attachToTidgiMiniWindow: true,
tidgiMiniWindowAlwaysOnTop: false,
}),
);
await renderComponent();
@ -340,7 +340,7 @@ describe('TidGiMenubarWindow Component', () => {
const switches = screen.getAllByRole('checkbox');
const alwaysOnTopSwitch = switches.find((s) => {
const label = s.closest('li')?.querySelector('.MuiListItemText-primary');
return label?.textContent === 'Preference.MenubarAlwaysOnTop';
return label?.textContent === 'Preference.TidgiMiniWindowAlwaysOnTop';
});
expect(alwaysOnTopSwitch).not.toBeChecked();
@ -350,19 +350,19 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menuBarAlwaysOnTop: false,
attachToTidgiMiniWindow: true,
tidgiMiniWindowAlwaysOnTop: false,
}),
);
await renderComponent();
const switches = screen.getAllByRole('checkbox');
const alwaysOnTopSwitch = switches[2];
const alwaysOnTopSwitch = switches[3];
await user.click(alwaysOnTopSwitch);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('menuBarAlwaysOnTop', true);
expect(window.service.preference.set).toHaveBeenCalledWith('tidgiMiniWindowAlwaysOnTop', true);
});
});
});
@ -371,8 +371,8 @@ describe('TidGiMenubarWindow Component', () => {
it('should display workspace sync toggle with correct initial state', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: true,
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: true,
}),
);
await renderComponent();
@ -380,7 +380,7 @@ describe('TidGiMenubarWindow Component', () => {
const switches = screen.getAllByRole('checkbox');
const syncSwitch = switches.find((s) => {
const label = s.closest('li')?.querySelector('.MuiListItemText-primary');
return label?.textContent === 'Preference.MenubarSyncWorkspaceWithMainWindow';
return label?.textContent === 'Preference.TidgiMiniWindowSyncWorkspaceWithMainWindow';
});
expect(syncSwitch).toBeChecked();
@ -390,27 +390,27 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: true,
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: true,
}),
);
await renderComponent();
const switches = screen.getAllByRole('checkbox');
const syncSwitch = switches[3];
const syncSwitch = switches[4];
await user.click(syncSwitch);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('menubarSyncWorkspaceWithMainWindow', false);
expect(window.service.preference.set).toHaveBeenCalledWith('tidgiMiniWindowSyncWorkspaceWithMainWindow', false);
});
});
it('should hide fixed workspace selector when sync is enabled', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: true,
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: true,
}),
);
await renderComponent();
@ -421,8 +421,8 @@ describe('TidGiMenubarWindow Component', () => {
it('should show fixed workspace selector when sync is disabled', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: false,
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: false,
}),
);
await renderComponent();
@ -437,8 +437,8 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: false,
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: false,
}),
);
await renderComponent();
@ -455,9 +455,9 @@ describe('TidGiMenubarWindow Component', () => {
it('should have workspace selector that can trigger API calls', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: false,
menubarFixedWorkspaceId: '',
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: false,
tidgiMiniWindowFixedWorkspaceId: '',
}),
);
await renderComponent();
@ -474,9 +474,9 @@ describe('TidGiMenubarWindow Component', () => {
it('should display currently selected workspace', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
menubarSyncWorkspaceWithMainWindow: false,
menubarFixedWorkspaceId: 'workspace-2',
attachToTidgiMiniWindow: true,
tidgiMiniWindowSyncWorkspaceWithMainWindow: false,
tidgiMiniWindowFixedWorkspaceId: 'workspace-2',
}),
);
const { container } = await renderComponent();
@ -491,25 +491,25 @@ describe('TidGiMenubarWindow Component', () => {
});
describe('Keyboard shortcut registration', () => {
it('should display keyboard shortcut button when menubar is attached', async () => {
it('should display keyboard shortcut button when tidgi mini window is attached', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
attachToTidgiMiniWindow: true,
keyboardShortcuts: {},
}),
);
await renderComponent();
const shortcutButton = screen.getByRole('button', { name: /Preference\.MenubarShortcutKey/ });
const shortcutButton = screen.getByRole('button', { name: /Preference\.TidgiMiniWindowShortcutKey/ });
expect(shortcutButton).toBeInTheDocument();
});
it('should display current keyboard shortcut value', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
attachToTidgiMiniWindow: true,
keyboardShortcuts: {
'Window.toggleMenubarWindow': 'Ctrl+Shift+M',
'Window.toggleTidgiMiniWindow': 'Ctrl+Shift+M',
},
}),
);
@ -522,19 +522,19 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
attachToTidgiMiniWindow: true,
keyboardShortcuts: {},
}),
);
await renderComponent();
const shortcutButton = screen.getByRole('button', { name: /Preference\.MenubarShortcutKey/ });
const shortcutButton = screen.getByRole('button', { name: /Preference\.TidgiMiniWindowShortcutKey/ });
const mockOnChange = vi.fn(async (value: string) => {
if (value && value.trim() !== '') {
await window.service.native.registerKeyboardShortcut('Window', 'toggleMenubarWindow', value);
await window.service.native.registerKeyboardShortcut('Window', 'toggleTidgiMiniWindow', value);
} else {
await window.service.native.unregisterKeyboardShortcut('Window', 'toggleMenubarWindow');
await window.service.native.unregisterKeyboardShortcut('Window', 'toggleTidgiMiniWindow');
}
});
@ -545,7 +545,7 @@ describe('TidGiMenubarWindow Component', () => {
await user.click(shortcutButton);
await waitFor(() => {
expect(window.service.native.registerKeyboardShortcut).toHaveBeenCalledWith('Window', 'toggleMenubarWindow', 'Ctrl+Shift+T');
expect(window.service.native.registerKeyboardShortcut).toHaveBeenCalledWith('Window', 'toggleTidgiMiniWindow', 'Ctrl+Shift+T');
});
});
@ -553,21 +553,21 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
attachToTidgiMiniWindow: true,
keyboardShortcuts: {
'Window.toggleMenubarWindow': 'Ctrl+Shift+M',
'Window.toggleTidgiMiniWindow': 'Ctrl+Shift+M',
},
}),
);
await renderComponent();
const shortcutButton = screen.getByRole('button', { name: /Preference\.MenubarShortcutKey/ });
const shortcutButton = screen.getByRole('button', { name: /Preference\.TidgiMiniWindowShortcutKey/ });
const mockOnChange = vi.fn(async (value: string) => {
if (value && value.trim() !== '') {
await window.service.native.registerKeyboardShortcut('Window', 'toggleMenubarWindow', value);
await window.service.native.registerKeyboardShortcut('Window', 'toggleTidgiMiniWindow', value);
} else {
await window.service.native.unregisterKeyboardShortcut('Window', 'toggleMenubarWindow');
await window.service.native.unregisterKeyboardShortcut('Window', 'toggleTidgiMiniWindow');
}
});
@ -578,30 +578,30 @@ describe('TidGiMenubarWindow Component', () => {
await user.click(shortcutButton);
await waitFor(() => {
expect(window.service.native.unregisterKeyboardShortcut).toHaveBeenCalledWith('Window', 'toggleMenubarWindow');
expect(window.service.native.unregisterKeyboardShortcut).toHaveBeenCalledWith('Window', 'toggleTidgiMiniWindow');
});
});
it('should display helper text for keyboard shortcut', async () => {
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
attachToTidgiMiniWindow: true,
keyboardShortcuts: {},
}),
);
await renderComponent();
expect(screen.getByText('Preference.MenubarShortcutKeyHelperText')).toBeInTheDocument();
expect(screen.getByText('Preference.TidgiMiniWindowShortcutKeyHelperText')).toBeInTheDocument();
});
});
describe('Integration: Toggle sequence', () => {
it('should show all settings when attachToMenubar is toggled on', async () => {
it('should show all settings when attachToTidgiMiniWindow is toggled on', async () => {
const user = userEvent.setup();
// Create a fresh subject for this test to avoid interference
const toggleTestSubject = new BehaviorSubject<IPreferences | undefined>(
createMockPreference({ attachToMenubar: false }),
createMockPreference({ attachToTidgiMiniWindow: false }),
);
Object.defineProperty(window.observables.preference, 'preference$', {
@ -623,32 +623,32 @@ describe('TidGiMenubarWindow Component', () => {
render(
<TestWrapper>
<TidGiMenubarWindow />
<TidGiMiniWindow />
</TestWrapper>,
);
// Wait for component to fully load
await waitFor(() => {
expect(screen.getByText('Menu.TidGiMenuBar')).toBeInTheDocument();
expect(screen.getByText('Menu.TidGiMiniWindow')).toBeInTheDocument();
});
// Verify additional settings are hidden initially
expect(screen.queryByText('Preference.MenubarAlwaysOnTop')).not.toBeInTheDocument();
expect(screen.queryByText('Preference.TidgiMiniWindowAlwaysOnTop')).not.toBeInTheDocument();
// Click the attach to menubar toggle
// Click the attach to tidgi mini window toggle
const switches = screen.getAllByRole('checkbox');
const attachSwitch = switches[0];
await user.click(attachSwitch);
// Wait for API call
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('attachToMenubar', true);
expect(window.service.preference.set).toHaveBeenCalledWith('attachToTidgiMiniWindow', true);
});
// Now verify new elements appear (they should appear automatically after the state update)
await waitFor(() => {
expect(screen.getByText('Preference.MenubarAlwaysOnTop')).toBeInTheDocument();
expect(screen.getByText('Preference.MenubarSyncWorkspaceWithMainWindow')).toBeInTheDocument();
expect(screen.getByText('Preference.TidgiMiniWindowAlwaysOnTop')).toBeInTheDocument();
expect(screen.getByText('Preference.TidgiMiniWindowSyncWorkspaceWithMainWindow')).toBeInTheDocument();
});
});
@ -656,10 +656,10 @@ describe('TidGiMenubarWindow Component', () => {
const user = userEvent.setup();
preferenceSubject.next(
createMockPreference({
attachToMenubar: true,
sidebarOnMenubar: false,
menuBarAlwaysOnTop: false,
menubarSyncWorkspaceWithMainWindow: true,
attachToTidgiMiniWindow: true,
sidebarOnTidgiMiniWindow: false,
tidgiMiniWindowAlwaysOnTop: false,
tidgiMiniWindowSyncWorkspaceWithMainWindow: true,
}),
);
await renderComponent();
@ -668,17 +668,17 @@ describe('TidGiMenubarWindow Component', () => {
await user.click(switches[1]);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('sidebarOnMenubar', true);
});
await user.click(switches[2]);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('menuBarAlwaysOnTop', true);
expect(window.service.preference.set).toHaveBeenCalledWith('sidebarOnTidgiMiniWindow', true);
});
await user.click(switches[3]);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('menubarSyncWorkspaceWithMainWindow', false);
expect(window.service.preference.set).toHaveBeenCalledWith('tidgiMiniWindowAlwaysOnTop', true);
});
await user.click(switches[4]);
await waitFor(() => {
expect(window.service.preference.set).toHaveBeenCalledWith('tidgiMiniWindowSyncWorkspaceWithMainWindow', false);
});
expect(window.service.preference.set).toHaveBeenCalledTimes(3);

View file

@ -51,8 +51,8 @@ export function usePreferenceSections<SectionTitleElement = HTMLSpanElement>():
Icon: GitHubIcon,
ref: useRef<SectionTitleElement>(null),
},
[PreferenceSections.menubar]: {
text: t('Menu.TidGiMenuBar'),
[PreferenceSections.tidgiMiniWindow]: {
text: t('Menu.TidGiMiniWindow'),
Icon: PhonelinkIcon,
ref: useRef<SectionTitleElement>(null),
},