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

View file

@ -5,7 +5,7 @@ import path from 'path';
import type { ISettingFile } from '../../src/services/database/interface'; import type { ISettingFile } from '../../src/services/database/interface';
import { settingsPath } from '../supports/paths'; 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; let existing = {} as ISettingFile;
if (await fs.pathExists(settingsPath)) { if (await fs.pathExists(settingsPath)) {
existing = await fs.readJson(settingsPath) as ISettingFile; existing = await fs.readJson(settingsPath) as ISettingFile;
@ -26,35 +26,35 @@ Given('I configure menubar with shortcut', async function() {
const updatedPreferences = { const updatedPreferences = {
...existing.preferences, ...existing.preferences,
attachToMenubar: true, attachToTidgiMiniWindow: true,
keyboardShortcuts: { keyboardShortcuts: {
...(existing.preferences?.keyboardShortcuts || {}), ...(existing.preferences?.keyboardShortcuts || {}),
'Window.toggleMenubarWindow': shortcut, 'Window.toggleTidgiMiniWindow': shortcut,
}, },
}; };
const finalSettings = { ...existing, preferences: updatedPreferences } as ISettingFile; const finalSettings = { ...existing, preferences: updatedPreferences } as ISettingFile;
await fs.writeJson(settingsPath, finalSettings, { spaces: 2 }); await fs.writeJson(settingsPath, finalSettings, { spaces: 2 });
}); });
// Cleanup function to be called after menubar tests (after app closes) // Cleanup function to be called after tidgi mini window tests (after app closes)
function clearMenubarSettings() { function clearTidgiMiniWindowSettings() {
if (!fs.existsSync(settingsPath)) return; if (!fs.existsSync(settingsPath)) return;
const parsed = fs.readJsonSync(settingsPath) as ISettingFile; 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 || {}, [ const cleanedPreferences = omit(parsed.preferences || {}, [
'attachToMenubar', 'attachToTidgiMiniWindow',
'menubarSyncWorkspaceWithMainWindow', 'tidgiMiniWindowSyncWorkspaceWithMainWindow',
'menubarFixedWorkspaceId', 'tidgiMiniWindowFixedWorkspaceId',
'menuBarAlwaysOnTop', 'tidgiMiniWindowAlwaysOnTop',
'sidebarOnMenubar', 'sidebarOnTidgiMiniWindow',
'showMenubarWindowTitleBar', 'showTidgiMiniWindowTitleBar',
]); ]);
// Also clean up the menubar shortcut from keyboardShortcuts // Also clean up the tidgi mini window shortcut from keyboardShortcuts
if (cleanedPreferences.keyboardShortcuts) { if (cleanedPreferences.keyboardShortcuts) {
cleanedPreferences.keyboardShortcuts = omit(cleanedPreferences.keyboardShortcuts, ['Window.toggleMenubarWindow']); cleanedPreferences.keyboardShortcuts = omit(cleanedPreferences.keyboardShortcuts, ['Window.toggleTidgiMiniWindow']);
} }
const cleaned = { ...parsed, preferences: cleanedPreferences }; const cleaned = { ...parsed, preferences: cleanedPreferences };
fs.writeJsonSync(settingsPath, cleaned, { spaces: 2 }); 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> { async function findWindowByType(app: ElectronApplication, windowType: string): Promise<Page | undefined> {
const pages = app.windows(); const pages = app.windows();
if (windowType.toLowerCase() === 'menubar') { if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for menubar window // Special handling for tidgi mini window
// Menubar window uses menubar library and may not have a standard route // TidGi mini window may not have a standard route
// Look for window by its title or other properties // Look for window by its title or other properties
const allWindows = pages.filter(page => !page.isClosed()); const allWindows = pages.filter(page => !page.isClosed());
// Try to find by URL pattern first // Try to find by URL pattern first
let menubarWindow = allWindows.find(page => { let tidgiMiniWindow = allWindows.find(page => {
const url = page.url() || ''; 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 not found by URL, try by window properties
if (!menubarWindow && allWindows.length > 1) { if (!tidgiMiniWindow && allWindows.length > 1) {
// Menubar is typically a smaller window // TidGi mini window is typically a smaller window
// Get the second window (first is main) // Get the second window (first is main)
menubarWindow = allWindows[1]; tidgiMiniWindow = allWindows[1];
} }
return menubarWindow; return tidgiMiniWindow;
} else { } else {
// For regular windows (preferences, about, addWorkspace, etc.) // For regular windows (preferences, about, addWorkspace, etc.)
return pages.find(page => { 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'); throw new Error('Application is not launched');
} }
if (windowType.toLowerCase() === 'menubar') { if (windowType.toLowerCase() === 'tidgiminiwindow') {
// For menubar, check via Electron API since it's a special tray window // For tidgi mini window, check via Electron API since it's a special tray window
// Menubar is created by the menubar library and may take time to initialize // TidGi mini window may take time to initialize
const maxWaitAttempts = 10; // Wait up to 10 seconds const maxWaitAttempts = 10; // Wait up to 10 seconds
const waitInterval = 1000; // Check every second const waitInterval = 1000; // Check every second
for (let attempt = 0; attempt < maxWaitAttempts; attempt++) { for (let attempt = 0; attempt < maxWaitAttempts; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => { const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows(); const allWindows = BrowserWindow.getAllWindows();
// Look for menubar window specifically by its dimensions (500x600) // Look for tidgi mini window specifically by its dimensions (500x600)
const menubarWindow = allWindows.find(win => { const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds(); const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600; return bounds.width === 500 && bounds.height === 600;
}); });
return { return {
hasMenubar: menubarWindow !== undefined, hasTidgiMiniWindow: tidgiMiniWindow !== undefined,
}; };
}); });
if (windowInfo.hasMenubar) { if (windowInfo.hasTidgiMiniWindow) {
return; return;
} }
@ -123,27 +123,27 @@ When('I confirm the {string} window visible', async function(this: ApplicationWo
throw new Error('Application is not launched'); throw new Error('Application is not launched');
} }
if (windowType.toLowerCase() === 'menubar') { if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for menubar window visibility // Special handling for tidgi mini window visibility
// Menubar window may take longer to show after shortcut key // TidGi mini window may take longer to show after shortcut key
const menubarMaxAttempts = 10; // Wait up to 10 seconds const tidgiMiniWindowMaxAttempts = 10; // Wait up to 10 seconds
const menubarWaitInterval = 1000; // Check every second 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 windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows(); const allWindows = BrowserWindow.getAllWindows();
const menubarWindow = allWindows.find(win => { const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds(); const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600; return bounds.width === 500 && bounds.height === 600;
}); });
if (!menubarWindow) { if (!tidgiMiniWindow) {
return { found: false, visible: false }; return { found: false, visible: false };
} }
return { return {
found: true, found: true,
visible: menubarWindow.isVisible(), visible: tidgiMiniWindow.isVisible(),
}; };
}); });
@ -151,9 +151,9 @@ When('I confirm the {string} window visible', async function(this: ApplicationWo
return; 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( 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'); throw new Error('Application is not launched');
} }
if (windowType.toLowerCase() === 'menubar') { if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for menubar window visibility check // Special handling for tidgi mini window visibility check
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => { const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows(); const allWindows = BrowserWindow.getAllWindows();
const menubarWindow = allWindows.find(win => { const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds(); const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600; return bounds.width === 500 && bounds.height === 600;
}); });
if (!menubarWindow) { if (!tidgiMiniWindow) {
return { found: false, visible: false }; return { found: false, visible: false };
} }
return { return {
found: true, 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'); throw new Error('Application is not launched');
} }
if (windowType.toLowerCase() === 'menubar') { if (windowType.toLowerCase() === 'tidgiminiwindow') {
// Special handling for menubar window // Special handling for tidgi mini window
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => { const windowInfo = await this.app.evaluate(async ({ BrowserWindow }) => {
const allWindows = BrowserWindow.getAllWindows(); const allWindows = BrowserWindow.getAllWindows();
// Look for menubar window specifically by its dimensions (500x600) // Look for tidgi mini window specifically by its dimensions (500x600)
const menubarWindow = allWindows.find(win => { const tidgiMiniWindow = allWindows.find(win => {
const bounds = win.getBounds(); const bounds = win.getBounds();
return bounds.width === 500 && bounds.height === 600; return bounds.width === 500 && bounds.height === 600;
}); });
return { return {
hasMenubar: menubarWindow !== undefined, hasTidgiMiniWindow: tidgiMiniWindow !== undefined,
}; };
}); });
if (!windowInfo.hasMenubar) { if (!windowInfo.hasTidgiMiniWindow) {
return; 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 // Get the window dimensions to identify it
const windowDimensions = windowType.toLowerCase() === 'menubar' const windowDimensions = windowType.toLowerCase() === 'tidgiminiwindow'
? { width: 500, height: 600 } ? { width: 500, height: 600 }
: { width: 1178, height: 686 }; // main window default : { 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 // Get the window dimensions to identify it
const windowDimensions = windowType.toLowerCase() === 'menubar' const windowDimensions = windowType.toLowerCase() === 'tidgiminiwindow'
? { width: 500, height: 600 } ? { width: 500, height: 600 }
: { width: 1178, height: 686 }; // main window default : { 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 worker files, native modules path, and ALL .node binaries (including better-sqlite3)
unpack: '{**/.webpack/main/*.worker.*,**/.webpack/main/native_modules/path.txt,**/{.**,**}/**/*.node}', 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 // @ts-expect-error - mac config is valid
mac: { mac: {
category: 'productivity', category: 'productivity',

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "Open CommandPalette", "OpenCommandPalette": "Open CommandPalette",
"OpenLinkInBrowser": "Open Link in Browser", "OpenLinkInBrowser": "Open Link in Browser",
"OpenTidGi": "Open TidGi", "OpenTidGi": "Open TidGi",
"OpenTidGiMenuBar": "Open TidGi MenuBar", "OpenTidGiMiniWindow": "Open TidGi Mini Window",
"OpenWorkspaceInNewWindow": "Open Workspace in New Window", "OpenWorkspaceInNewWindow": "Open Workspace in New Window",
"Paste": "Paste", "Paste": "Paste",
"Preferences": "Preferences...", "Preferences": "Preferences...",
@ -309,7 +309,7 @@
"SelectNextWorkspace": "Select Next Workspace", "SelectNextWorkspace": "Select Next Workspace",
"SelectPreviousWorkspace": "Select Previous Workspace", "SelectPreviousWorkspace": "Select Previous Workspace",
"TidGi": "TidGi", "TidGi": "TidGi",
"TidGiMenuBar": "TidGi MenuBar", "TidGiMiniWindow": "TidGi Mini Window",
"View": "View", "View": "View",
"Wiki": "Wiki", "Wiki": "Wiki",
"Window": "Window", "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", "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.", "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", "AskDownloadLocation": "Ask where to save each file before downloading",
"AttachToMenuBar": "Attach to menu bar", "AttachToTidgiMiniWindow": "Attach to TidGi mini window",
"AttachToMenuBarShowSidebar": "Attach To Menu Bar Show Sidebar", "AttachToTidgiMiniWindowShowSidebar": "Attach To TidGi Mini Window 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.", "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.",
"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.", "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", "AttachToTaskbar": "Attach to taskbar",
"AttachToTaskbarShowSidebar": "Attach To Taskbar Show Sidebar", "AttachToTaskbarShowSidebar": "Attach To Taskbar Show Sidebar",
"ChooseLanguage": "Choose Language 选择语言", "ChooseLanguage": "Choose Language 选择语言",
@ -362,16 +362,16 @@
"ItIsWorking": "It is working!", "ItIsWorking": "It is working!",
"Languages": "Lang/语言", "Languages": "Lang/语言",
"LightTheme": "Light Theme", "LightTheme": "Light Theme",
"MenubarAlwaysOnTop": "Menubar Always on top", "TidgiMiniWindowAlwaysOnTop": "TidGi Mini Window Always on top",
"MenubarAlwaysOnTopDetail": "Keep TidGi's Menubar always on top of other windows, and will not be covered by other windows", "TidgiMiniWindowAlwaysOnTopDetail": "Keep TidGi's Mini Window always on top of other windows, and will not be covered by other windows",
"MenubarFixedWorkspace": "Select workspace for fixed menubar window", "TidgiMiniWindowFixedWorkspace": "Select workspace for fixed TidGi Mini Window",
"MenubarShortcutKey": "Set shortcut key to toggle TidGi menubar window", "TidgiMiniWindowShortcutKey": "Set shortcut key to toggle TidGi Mini Window",
"MenubarShortcutKeyHelperText": "Set a shortcut key to quickly open or close TidGi menubar window", "TidgiMiniWindowShortcutKeyHelperText": "Set a shortcut key to quickly open or close TidGi Mini Window",
"MenubarShortcutKeyPlaceholder": "e.g.: Ctrl+Shift+D", "TidgiMiniWindowShortcutKeyPlaceholder": "e.g.: Ctrl+Shift+D",
"MenubarSyncWorkspaceWithMainWindow": "Menubar window syncs with main window workspace", "TidgiMiniWindowSyncWorkspaceWithMainWindow": "TidGi Mini Window syncs with main window workspace",
"MenubarSyncWorkspaceWithMainWindowDetail": "When checked, menubar window will display the same workspace content as main window", "TidgiMiniWindowSyncWorkspaceWithMainWindowDetail": "When checked, TidGi Mini Window will display the same workspace content as main window",
"ShowMenubarWindowTitleBar": "Show title bar on menubar window", "ShowTidgiMiniWindowTitleBar": "Show title bar on TidGi Mini Window",
"ShowMenubarWindowTitleBarDetail": "Show draggable title bar on menubar window", "ShowTidgiMiniWindowTitleBarDetail": "Show draggable title bar on TidGi Mini Window",
"Miscellaneous": "Miscellaneous", "Miscellaneous": "Miscellaneous",
"MoreWorkspaceSyncSettings": "More Workspace Sync Settings", "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.", "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>", "TestNotificationDescription": "<0>If notifications dont show up, make sure you enable notifications in<1>macOS Preferences → Notifications → TidGi</1>.</0>",
"Theme": "Theme", "Theme": "Theme",
"TiddlyWiki": "TiddlyWiki", "TiddlyWiki": "TiddlyWiki",
"ToggleMenuBar": "Toggle Menu Bar", "ToggleTidgiMiniWindow": "Toggle TidGi Mini Window",
"Token": "Git credentials", "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.", "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", "Translatium": "Translatium",

View file

@ -92,7 +92,7 @@
"OpenCommandPalette": "Ouvrir la palette de commandes", "OpenCommandPalette": "Ouvrir la palette de commandes",
"OpenLinkInBrowser": "Ouvrir le lien dans le navigateur", "OpenLinkInBrowser": "Ouvrir le lien dans le navigateur",
"OpenTidGi": "Ouvrir TidGi", "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", "OpenWorkspaceInNewWindow": "Ouvrir l'espace de travail dans une nouvelle fenêtre",
"Paste": "Coller", "Paste": "Coller",
"Preferences": "Préférences...", "Preferences": "Préférences...",
@ -309,7 +309,7 @@
"SelectNextWorkspace": "Sélectionner l'espace de travail suivant", "SelectNextWorkspace": "Sélectionner l'espace de travail suivant",
"SelectPreviousWorkspace": "Sélectionner l'espace de travail précédent", "SelectPreviousWorkspace": "Sélectionner l'espace de travail précédent",
"TidGi": "TidGi", "TidGi": "TidGi",
"TidGiMenuBar": "Barre de menu TidGi", "TidGiMiniWindow": "Mini-fenêtre TidGi",
"View": "Vue", "View": "Vue",
"Wiki": "Wiki", "Wiki": "Wiki",
"Window": "Fenêtre", "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", "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.", "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", "AskDownloadLocation": "Demander où enregistrer chaque fichier avant de télécharger",
"AttachToMenuBar": "Attacher à la barre de menu", "AttachToTidgiMiniWindow": "Attacher à la mini-fenêtre TidGi",
"AttachToMenuBarShowSidebar": "Attacher à la barre de menu Afficher la barre latérale", "AttachToTidgiMiniWindowShowSidebar": "Attacher à la mini-fenêtre TidGi 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.", "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.",
"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.", "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", "AttachToTaskbar": "Attacher à la barre des tâches",
"AttachToTaskbarShowSidebar": "Attacher à la barre des tâches Afficher la barre latérale", "AttachToTaskbarShowSidebar": "Attacher à la barre des tâches Afficher la barre latérale",
"ChooseLanguage": "Choisir la langue 选择语言", "ChooseLanguage": "Choisir la langue 选择语言",
@ -349,8 +349,8 @@
"General": "UI & Interact", "General": "UI & Interact",
"HibernateAllUnusedWorkspaces": "Mettre en veille les espaces de travail inutilisés au lancement de l'application", "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.", "HibernateAllUnusedWorkspacesDescription": "Mettre en veille tous les espaces de travail au lancement, sauf le dernier espace de travail actif.",
"HideMenuBar": "Masquer la barre de menu", "HideTidgiMiniWindow": "Masquer la mini-fenêtre TidGi",
"HideMenuBarDetail": "Masquer la barre de menu sauf si Alt+M est pressé.", "HideTidgiMiniWindowDetail": "Masquer la mini-fenêtre TidGi sauf si Alt+M est pressé.",
"HideSideBar": "Masquer la barre latérale", "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", "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", "HideTitleBar": "Masquer la barre de titre",
@ -360,8 +360,8 @@
"ItIsWorking": "Ça fonctionne !", "ItIsWorking": "Ça fonctionne !",
"Languages": "Lang/语言", "Languages": "Lang/语言",
"LightTheme": "Thème clair", "LightTheme": "Thème clair",
"MenubarAlwaysOnTop": "Barre de menu toujours au-dessus", "TidgiMiniWindowAlwaysOnTop": "TidGi Mini Window 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", "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", "Miscellaneous": "Divers",
"MoreWorkspaceSyncSettings": "Plus de paramètres de synchronisation de l'espace de travail", "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.", "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>", "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", "Theme": "Thème",
"TiddlyWiki": "TiddlyWiki", "TiddlyWiki": "TiddlyWiki",
"ToggleMenuBar": "Basculer la barre de menu", "ToggleTidgiMiniWindow": "Basculer la mini-fenêtre TidGi",
"Token": "Informations d'identification Git", "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.", "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", "Translatium": "Translatium",

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@
"test": "pnpm run test:unit && pnpm run test:prepare-e2e && pnpm run test:e2e", "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": "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: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", "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": "pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make",
"make:analyze": "cross-env ANALYZE=true pnpm run 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 buildResourcePath = path.resolve(sourcePath, 'build-resources');
export const developmentImageFolderPath = path.resolve(sourcePath, 'images'); export const developmentImageFolderPath = path.resolve(sourcePath, 'images');
// Menubar icon // TidGi Mini Window icon
const menuBarIconFileName = isMac ? 'menubarTemplate@2x.png' : 'menubar@2x.png'; const tidgiMiniWindowIconFileName = isMac ? 'tidgiMiniWindowTemplate@2x.png' : 'tidgiMiniWindow@2x.png';
export const MENUBAR_ICON_PATH = isPackaged export const TIDGI_MINI_WINDOW_ICON_PATH = isPackaged
? path.resolve(process.resourcesPath, menuBarIconFileName) // Packaged: resources/<icon> ? path.resolve(process.resourcesPath, tidgiMiniWindowIconFileName) // Packaged: resources/<icon>
: path.resolve(buildResourcePath, menuBarIconFileName); // Dev/Unit test: <project-root>/build-resources/<icon> : path.resolve(buildResourcePath, tidgiMiniWindowIconFileName); // Dev/Unit test: <project-root>/build-resources/<icon>
// System paths // System paths
export const CHROME_ERROR_PATH = 'chrome-error://chromewebdata/'; export const CHROME_ERROR_PATH = 'chrome-error://chromewebdata/';

View file

@ -118,7 +118,7 @@ const commonInit = async (): Promise<void> => {
externalAPIService.initialize(), 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 // handle workspace name + tiddler name in uri https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app
deepLinkService.initializeDeepLink('tidgi'); deepLinkService.initializeDeepLink('tidgi');
@ -139,9 +139,9 @@ const commonInit = async (): Promise<void> => {
await workspaceService.initializeDefaultPageWorkspaces(); await workspaceService.initializeDefaultPageWorkspaces();
// perform wiki startup and git sync for each workspace // perform wiki startup and git sync for each workspace
await workspaceViewService.initializeAllWorkspaceView(); await workspaceViewService.initializeAllWorkspaceView();
const attachToMenubar = await preferenceService.get('attachToMenubar'); const attachToTidgiMiniWindow = await preferenceService.get('attachToTidgiMiniWindow');
if (attachToMenubar) { if (attachToTidgiMiniWindow) {
await windowService.openMenubarWindow(true, false); await windowService.openTidgiMiniWindow(true, false);
} }
ipcMain.emit('request-update-pause-notifications-info'); 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 // Mock window.observables to provide realistic API behavior
const preferencesSubject = new BehaviorSubject({ const preferencesSubject = new BehaviorSubject({
sidebar: true, sidebar: true,
sidebarOnMenubar: true, sidebarOnTidgiMiniWindow: true,
showSideBarText: true, showSideBarText: true,
showSideBarIcon: true, showSideBarIcon: true,
}); });

View file

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

View file

@ -19,14 +19,14 @@ export function useInitialPage() {
let targetWorkspace = workspacesList.find(workspace => workspace.active); let targetWorkspace = workspacesList.find(workspace => workspace.active);
// For menubar window, determine which workspace to show based on preferences // For tidgi mini window, determine which workspace to show based on preferences
if (windowName === WindowNames.menuBar && preferences) { if (windowName === WindowNames.tidgiMiniWindow && preferences) {
const { menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId } = preferences; const { tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId } = preferences;
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow; const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
if (!shouldSync && menubarFixedWorkspaceId) { if (!shouldSync && tidgiMiniWindowFixedWorkspaceId) {
// Use fixed workspace if not syncing // If not syncing with main window, use fixed workspace
const fixedWorkspace = workspacesList.find(ws => ws.id === menubarFixedWorkspaceId); const fixedWorkspace = workspacesList.find(ws => ws.id === tidgiMiniWindowFixedWorkspaceId);
if (fixedWorkspace) { if (fixedWorkspace) {
targetWorkspace = fixedWorkspace; targetWorkspace = fixedWorkspace;
} }
@ -51,19 +51,19 @@ export function useInitialPage() {
} }
}, [location, workspacesList, preferences, windowName, setLocation]); }, [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(() => { useEffect(() => {
if (windowName !== WindowNames.menuBar || !workspacesList || !preferences) { if (windowName !== WindowNames.tidgiMiniWindow || !workspacesList || !preferences) {
return; return;
} }
const { menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId } = preferences; const { tidgiMiniWindowSyncWorkspaceWithMainWindow, tidgiMiniWindowFixedWorkspaceId } = preferences;
const shouldSync = menubarSyncWorkspaceWithMainWindow === undefined || menubarSyncWorkspaceWithMainWindow; const shouldSync = tidgiMiniWindowSyncWorkspaceWithMainWindow === undefined || tidgiMiniWindowSyncWorkspaceWithMainWindow;
// Determine target workspace // Determine target workspace
let targetWorkspace = workspacesList.find(workspace => workspace.active); let targetWorkspace = workspacesList.find(workspace => workspace.active);
if (!shouldSync && menubarFixedWorkspaceId) { if (!shouldSync && tidgiMiniWindowFixedWorkspaceId) {
const fixedWorkspace = workspacesList.find(ws => ws.id === menubarFixedWorkspaceId); const fixedWorkspace = workspacesList.find(ws => ws.id === tidgiMiniWindowFixedWorkspaceId);
if (fixedWorkspace) { if (fixedWorkspace) {
targetWorkspace = 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). * @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 => { 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; const clickedButtonIndex = ipcRenderer.sendSync(NativeChannel.showElectronMessageBoxSync, options, WindowNames.main) as unknown;
if (typeof clickedButtonIndex === 'number') { if (typeof clickedButtonIndex === 'number') {
return clickedButtonIndex; return clickedButtonIndex;

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ export interface IPreferences {
allowPrerelease: boolean; allowPrerelease: boolean;
alwaysOnTop: boolean; alwaysOnTop: boolean;
askForDownloadPath: boolean; askForDownloadPath: boolean;
attachToMenubar: boolean; attachToTidgiMiniWindow: boolean;
/** /**
* *
*/ */
@ -26,7 +26,7 @@ export interface IPreferences {
hideMenuBar: boolean; hideMenuBar: boolean;
ignoreCertificateErrors: boolean; ignoreCertificateErrors: boolean;
language: string; language: string;
menuBarAlwaysOnTop: boolean; tidgiMiniWindowAlwaysOnTop: boolean;
pauseNotifications: string | undefined; pauseNotifications: string | undefined;
pauseNotificationsBySchedule: boolean; pauseNotificationsBySchedule: boolean;
pauseNotificationsByScheduleFrom: string; pauseNotificationsByScheduleFrom: string;
@ -42,24 +42,24 @@ export interface IPreferences {
*/ */
sidebar: boolean; sidebar: boolean;
/** /**
* Should show sidebar on menubar window? * Should show sidebar on tidgi mini window?
*/ */
sidebarOnMenubar: boolean; sidebarOnTidgiMiniWindow: boolean;
spellcheck: boolean; spellcheck: boolean;
spellcheckLanguages: HunspellLanguages[]; spellcheckLanguages: HunspellLanguages[];
swipeToNavigate: boolean; swipeToNavigate: boolean;
/** /**
* Whether menubar window should show the same workspace as main window * 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 * Keyboard shortcuts configuration stored as serviceIdentifier.methodName -> shortcut
*/ */
@ -82,7 +82,7 @@ export enum PreferenceSections {
friendLinks = 'friendLinks', friendLinks = 'friendLinks',
general = 'general', general = 'general',
languages = 'languages', languages = 'languages',
menubar = 'menubar', tidgiMiniWindow = 'tidgiMiniWindow',
misc = 'misc', misc = 'misc',
network = 'network', network = 'network',
notifications = 'notifications', notifications = 'notifications',

View file

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

View file

@ -30,11 +30,11 @@ export interface IViewService {
createViewAddToWindow(workspace: IWorkspace, browserWindow: BrowserWindow, sharedWebPreferences: WebPreferences, windowName: WindowNames): Promise<WebContentsView>; createViewAddToWindow(workspace: IWorkspace, browserWindow: BrowserWindow, sharedWebPreferences: WebPreferences, windowName: WindowNames): Promise<WebContentsView>;
forEachView: (functionToRun: (view: WebContentsView, workspaceID: string, windowName: WindowNames) => void) => void; 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>; 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>>; getActiveBrowserViews: () => Promise<Array<WebContentsView | undefined>>;
getLoadedViewEnsure(workspaceID: string, windowName: WindowNames): Promise<WebContentsView>; 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. * 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 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 * @returns
*/ */
setActiveView: (workspaceID: string, windowName: WindowNames) => Promise<void>; 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. * We only have a single instance of main window, that is the app window.
*/ */
main = 'main', main = 'main',
menuBar = 'menuBar', tidgiMiniWindow = 'tidgiMiniWindow',
notifications = 'notifications', notifications = 'notifications',
preferences = 'preferences', preferences = 'preferences',
/** /**
@ -46,7 +46,7 @@ export const windowDimension: Record<WindowNames, { height?: number; width?: num
width: 1200, width: 1200,
height: 768, height: 768,
}, },
[WindowNames.menuBar]: { [WindowNames.tidgiMiniWindow]: {
width: 500, width: 500,
height: 600, height: 600,
}, },
@ -100,7 +100,7 @@ export interface WindowMeta {
[WindowNames.auth]: undefined; [WindowNames.auth]: undefined;
[WindowNames.editWorkspace]: { workspaceID?: string }; [WindowNames.editWorkspace]: { workspaceID?: string };
[WindowNames.main]: { forceClose?: boolean }; [WindowNames.main]: { forceClose?: boolean };
[WindowNames.menuBar]: undefined; [WindowNames.tidgiMiniWindow]: undefined;
[WindowNames.notifications]: undefined; [WindowNames.notifications]: undefined;
[WindowNames.preferences]: IPreferenceWindowMeta; [WindowNames.preferences]: IPreferenceWindowMeta;
[WindowNames.spellcheck]: undefined; [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 { isMac } from '@/helpers/system';
import { container } from '@services/container'; import { container } from '@services/container';
import { i18n } from '@services/libs/i18n'; import { i18n } from '@services/libs/i18n';
@ -15,14 +15,17 @@ import type { IWindowService } from './interface';
import { getMainWindowEntry } from './viteEntry'; import { getMainWindowEntry } from './viteEntry';
import { WindowNames } from './WindowProperties'; 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 menuService = container.get<IMenuService>(serviceIdentifier.MenuService);
const windowService = container.get<IWindowService>(serviceIdentifier.Window); const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const viewService = container.get<IViewService>(serviceIdentifier.View); const viewService = container.get<IViewService>(serviceIdentifier.View);
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference); const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
// Get menubar-specific titleBar preference // Get tidgi mini window-specific titleBar preference
const showMenubarWindowTitleBar = await preferenceService.get('showMenubarWindowTitleBar'); const showTidgiMiniWindowTitleBar = await preferenceService.get('showTidgiMiniWindowTitleBar');
// setImage after Tray instance is created to avoid // setImage after Tray instance is created to avoid
// "Segmentation fault (core dumped)" bug on Linux // "Segmentation fault (core dumped)" bug on Linux
@ -30,71 +33,71 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
// https://github.com/atomery/translatium/issues/164 // https://github.com/atomery/translatium/issues/164
const tray = new Tray(nativeImage.createEmpty()); const tray = new Tray(nativeImage.createEmpty());
// icon template is not supported on Windows & Linux // 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 // Create tidgi mini window-specific window configuration
// Override titleBar settings from windowConfig with menubar-specific preference // Override titleBar settings from windowConfig with tidgi mini window-specific preference
const menubarWindowConfig: BrowserWindowConstructorOptions = { const tidgiMiniWindowConfig: BrowserWindowConstructorOptions = {
...windowConfig, ...windowConfig,
show: false, show: false,
minHeight: 100, minHeight: 100,
minWidth: 250, minWidth: 250,
// Use menubar-specific titleBar setting instead of inheriting from main window // Use tidgi mini window-specific titleBar setting instead of inheriting from main window
titleBarStyle: showMenubarWindowTitleBar ? 'default' : 'hidden', titleBarStyle: showTidgiMiniWindowTitleBar ? 'default' : 'hidden',
frame: showMenubarWindowTitleBar, frame: showTidgiMiniWindowTitleBar,
// Always hide the menu bar (File, Edit, View menu), even when showing title bar // Always hide the menu bar (File, Edit, View menu), even when showing title bar
autoHideMenuBar: true, autoHideMenuBar: true,
}; };
logger.info('Creating menubar with titleBar configuration', { logger.info('Creating tidgi mini window with titleBar configuration', {
function: 'handleAttachToMenuBar', function: 'handleAttachToTidgiMiniWindow',
showMenubarWindowTitleBar, showTidgiMiniWindowTitleBar,
titleBarStyle: menubarWindowConfig.titleBarStyle, titleBarStyle: tidgiMiniWindowConfig.titleBarStyle,
frame: menubarWindowConfig.frame, frame: tidgiMiniWindowConfig.frame,
}); });
const menuBar = menubar({ const tidgiMiniWindow = menubar({
index: getMainWindowEntry(), index: getMainWindowEntry(),
tray, tray,
activateWithApp: false, activateWithApp: false,
showDockIcon: true, showDockIcon: true,
preloadWindow: true, preloadWindow: true,
tooltip: i18n.t('Menu.TidGiMenuBar'), tooltip: i18n.t('Menu.TidGiMiniWindow'),
browserWindow: menubarWindowConfig, browserWindow: tidgiMiniWindowConfig,
}); });
menuBar.on('after-create-window', () => { tidgiMiniWindow.on('after-create-window', () => {
if (menuBar.window !== undefined) { if (tidgiMiniWindow.window !== undefined) {
menuBar.window.on('focus', async () => { tidgiMiniWindow.window.on('focus', async () => {
logger.debug('restore window position', { function: 'handleAttachToMenuBar' }); logger.debug('restore window position', { function: 'handleAttachToTidgiMiniWindow' });
if (windowWithBrowserViewState === undefined) { if (windowWithBrowserViewState === undefined) {
logger.debug('windowWithBrowserViewState is undefined for menuBar', { function: 'handleAttachToMenuBar' }); logger.debug('windowWithBrowserViewState is undefined for tidgiMiniWindow', { function: 'handleAttachToTidgiMiniWindow' });
} else { } else {
if (menuBar.window === undefined) { if (tidgiMiniWindow.window === undefined) {
logger.debug('menuBar.window is undefined', { function: 'handleAttachToMenuBar' }); logger.debug('tidgiMiniWindow.window is undefined', { function: 'handleAttachToTidgiMiniWindow' });
} else { } else {
const haveXYValue = [windowWithBrowserViewState.x, windowWithBrowserViewState.y].every((value) => Number.isFinite(value)); const haveXYValue = [windowWithBrowserViewState.x, windowWithBrowserViewState.y].every((value) => Number.isFinite(value));
const haveWHValue = [windowWithBrowserViewState.width, windowWithBrowserViewState.height].every((value) => Number.isFinite(value)); const haveWHValue = [windowWithBrowserViewState.width, windowWithBrowserViewState.height].every((value) => Number.isFinite(value));
if (haveXYValue) { if (haveXYValue) {
menuBar.window.setPosition(windowWithBrowserViewState.x, windowWithBrowserViewState.y, false); tidgiMiniWindow.window.setPosition(windowWithBrowserViewState.x, windowWithBrowserViewState.y, false);
} }
if (haveWHValue) { if (haveWHValue) {
menuBar.window.setSize(windowWithBrowserViewState.width, windowWithBrowserViewState.height, false); tidgiMiniWindow.window.setSize(windowWithBrowserViewState.width, windowWithBrowserViewState.height, false);
} }
} }
} }
const view = await viewService.getActiveBrowserView(); const view = await viewService.getActiveBrowserView();
view?.webContents.focus(); view?.webContents.focus();
}); });
menuBar.window.removeAllListeners('close'); tidgiMiniWindow.window.removeAllListeners('close');
menuBar.window.on('close', (event) => { tidgiMiniWindow.window.on('close', (event) => {
event.preventDefault(); event.preventDefault();
menuBar.hideWindow(); tidgiMiniWindow.hideWindow();
}); });
} }
}); });
menuBar.on('hide', async () => { tidgiMiniWindow.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. // 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) { if (isMac) {
const mainWindow = windowService.get(WindowNames.main); const mainWindow = windowService.get(WindowNames.main);
if (mainWindow?.isVisible() === true) { if (mainWindow?.isVisible() === true) {
@ -103,29 +106,29 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
} }
}); });
// https://github.com/maxogden/menubar/issues/120 // https://github.com/maxogden/menubar/issues/120
menuBar.on('after-hide', () => { tidgiMiniWindow.on('after-hide', () => {
if (isMac) { if (isMac) {
menuBar.app.hide(); tidgiMiniWindow.app.hide();
} }
}); });
// manually save window state https://github.com/mawie81/electron-window-state/issues/64 // manually save window state https://github.com/mawie81/electron-window-state/issues/64
const debouncedSaveWindowState = debounce( const debouncedSaveWindowState = debounce(
() => { () => {
if (menuBar.window !== undefined) { if (tidgiMiniWindow.window !== undefined) {
windowWithBrowserViewState?.saveState(menuBar.window); windowWithBrowserViewState?.saveState(tidgiMiniWindow.window);
} }
}, },
500, 500,
); );
// menubar is hide, not close, so not managed by windowStateKeeper, need to save manually // tidgi mini window is hide, not close, so not managed by windowStateKeeper, need to save manually
menuBar.window?.on('resize', debouncedSaveWindowState); tidgiMiniWindow.window?.on('resize', debouncedSaveWindowState);
menuBar.window?.on('move', debouncedSaveWindowState); tidgiMiniWindow.window?.on('move', debouncedSaveWindowState);
return await new Promise<Menubar>((resolve) => { return await new Promise<Menubar>((resolve) => {
menuBar.on('ready', async () => { tidgiMiniWindow.on('ready', async () => {
// right on tray icon // right on tray icon
menuBar.tray.on('right-click', () => { tidgiMiniWindow.tray.on('right-click', () => {
const contextMenu = Menu.buildFromTemplate([ const contextMenu = Menu.buildFromTemplate([
{ {
label: i18n.t('ContextMenu.OpenTidGi'), 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 () => { click: async () => {
await menuBar.showWindow(); await tidgiMiniWindow.showWindow();
}, },
}, },
{ {
@ -165,23 +168,23 @@ export async function handleAttachToMenuBar(windowConfig: BrowserWindowConstruct
{ {
label: i18n.t('ContextMenu.Quit'), label: i18n.t('ContextMenu.Quit'),
click: () => { click: () => {
menuBar.app.quit(); tidgiMiniWindow.app.quit();
}, },
}, },
]); ]);
menuBar.tray.popUpContextMenu(contextMenu); tidgiMiniWindow.tray.popUpContextMenu(contextMenu);
}); });
// right click on window content // right click on window content
if (menuBar.window?.webContents !== undefined) { if (tidgiMiniWindow.window?.webContents !== undefined) {
const unregisterContextMenu = await menuService.initContextMenuForWindowWebContents(menuBar.window.webContents); const unregisterContextMenu = await menuService.initContextMenuForWindowWebContents(tidgiMiniWindow.window.webContents);
menuBar.on('after-close', () => { tidgiMiniWindow.on('after-close', () => {
unregisterContextMenu(); unregisterContextMenu();
}); });
} }
resolve(menuBar); resolve(tidgiMiniWindow);
}); });
}); });
} }

View file

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

View file

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

View file

@ -123,9 +123,9 @@ export async function registerMenu(): Promise<void> {
// if back is called in popup window // if back is called in popup window
// navigate in the popup window instead // navigate in the popup window instead
if (browserWindow !== undefined) { 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 { 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(); await windowService.goForward();
} }

View file

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

View file

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

View file

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

View file

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

View file

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