TidGi-Desktop/features/supports/webContentsViewHelper.ts
lin onetwo 19ef74a4a6
Feat/mini window (#642)
* feat: new config for tidgi mini window

* chore: upgrade electron-forge

* fix: use 汉语 和 漢語

* feat: shortcut to open mini window

* test: TidGiMenubarWindow

* feat: allow updateWindowProperties on the fly

* fix: wrong icon path

* fix: log not showing error message and stack

* refactor: directly log error when using logger.error

* feat: shortcut to open window

* fix: menubar not closed

* test: e2e for menubar

* test: keyboard shortcut

* test: wiki web content, and refactor to files

* test: update command

* Update Testing.md

* test: menubar settings about menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId

* test: simplify menubar test and cleanup test config

* fix: view missing when execute several test all together

* refactor: use hook to cleanup menubar setting

* refactor: I clear test ai settings to before hook

* Add option to show title bar on menubar window

Introduces a new preference 'showMenubarWindowTitleBar' allowing users to toggle the title bar visibility on the menubar window. Updates related services, interfaces, and UI components to support this feature, and adds corresponding localization strings for English and Chinese.

* refactor: tidgiminiwindow

* refactor: preference keys to right order

* Refactor window dimension checks to use constants

Replaces hardcoded window dimensions with values from windowDimension and WindowNames constants for improved maintainability and consistency in window identification and checks.

* I cleanup test wiki

* Update defaultPreferences.ts

* test: mini window workspace switch

* fix: image broken by ai, and lint

* fix: can't switch to mini window

* refactor: useless todo

* Update index.ts

* refactor: reuse serialize-error

* Update index.ts

* Update testKeyboardShortcuts.ts

* refactor: dup logic

* Update ui.ts

* fix: electron-ipc-cat
2025-10-21 20:07:04 +08:00

151 lines
5 KiB
TypeScript

import type { ElectronApplication } from 'playwright';
/**
* Get text content from WebContentsView
* @param app Electron application instance
* @returns Promise<string | null> Returns text content or null
*/
export async function getTextContent(app: ElectronApplication): Promise<string | null> {
return await app.evaluate(async ({ BrowserWindow }) => {
// Get all browser windows
const windows = BrowserWindow.getAllWindows();
for (const window of windows) {
// Get all child views (WebContentsView instances) attached to this window
if (window.contentView && 'children' in window.contentView) {
const views = window.contentView.children || [];
for (const view of views) {
// Type guard to check if view is a WebContentsView
if (view && view.constructor.name === 'WebContentsView') {
try {
// Cast to WebContentsView type and execute JavaScript
const webContentsView = view as unknown as { webContents: { executeJavaScript: (script: string) => Promise<string> } };
const content = await webContentsView.webContents.executeJavaScript(`
document.body.textContent || document.body.innerText || ''
`);
if (content && content.trim()) {
return content;
}
} catch {
// Continue to next view if this one fails
continue;
}
}
}
}
}
return null;
});
}
/**
* Get DOM content from WebContentsView
* @param app Electron application instance
* @returns Promise<string | null> Returns DOM content or null
*/
export async function getDOMContent(app: ElectronApplication): Promise<string | null> {
return await app.evaluate(async ({ BrowserWindow }) => {
// Get all browser windows
const windows = BrowserWindow.getAllWindows();
for (const window of windows) {
// Get all child views (WebContentsView instances) attached to this window
if (window.contentView && 'children' in window.contentView) {
const views = window.contentView.children || [];
for (const view of views) {
// Type guard to check if view is a WebContentsView
if (view && view.constructor.name === 'WebContentsView') {
try {
// Cast to WebContentsView type and execute JavaScript
const webContentsView = view as unknown as { webContents: { executeJavaScript: (script: string) => Promise<string> } };
const content = await webContentsView.webContents.executeJavaScript(`
document.documentElement.outerHTML || ''
`);
if (content && content.trim()) {
return content;
}
} catch {
// Continue to next view if this one fails
continue;
}
}
}
}
}
return null;
});
}
/**
* Check if WebContentsView exists and is loaded
* @param app Electron application instance
* @returns Promise<boolean> Returns whether it exists and is loaded
*/
export async function isLoaded(app: ElectronApplication): Promise<boolean> {
return await app.evaluate(async ({ BrowserWindow }) => {
// Get all browser windows
const windows = BrowserWindow.getAllWindows();
for (const window of windows) {
// Get all child views (WebContentsView instances) attached to this window
if (window.contentView && 'children' in window.contentView) {
const views = window.contentView.children || [];
for (const view of views) {
// Type guard to check if view is a WebContentsView
if (view && view.constructor.name === 'WebContentsView') {
// If we found a WebContentsView, consider it loaded
return true;
}
}
}
}
return false;
});
}
/**
* Find specified text in WebContentsView
* @param app Electron application instance
* @param expectedText Text to search for
* @param contentType Content type: 'text' or 'dom'
* @returns Promise<boolean> Returns whether text was found
*/
export async function containsText(
app: ElectronApplication,
expectedText: string,
contentType: 'text' | 'dom' = 'text',
): Promise<boolean> {
const content = contentType === 'text'
? await getTextContent(app)
: await getDOMContent(app);
return content !== null && content.includes(expectedText);
}
/**
* Get WebContentsView content summary (for error messages)
* @param app Electron application instance
* @param contentType Content type: 'text' or 'dom'
* @param maxLength Maximum length, default 200
* @returns Promise<string> Returns content summary
*/
export async function getContentSummary(
app: ElectronApplication,
contentType: 'text' | 'dom' = 'text',
maxLength: number = 200,
): Promise<string> {
const content = contentType === 'text'
? await getTextContent(app)
: await getDOMContent(app);
if (!content) {
return 'null';
}
return content.length > maxLength
? content.substring(0, maxLength) + '...'
: content;
}