mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-05 18:20:39 -08:00
* 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
163 lines
7.3 KiB
TypeScript
163 lines
7.3 KiB
TypeScript
import { BrowserWindowConstructorOptions, shell, WebContentsView } from 'electron';
|
|
import windowStateKeeper from 'electron-window-state';
|
|
|
|
import { SETTINGS_FOLDER } from '@/constants/appPaths';
|
|
import { MetaDataChannel } from '@/constants/channels';
|
|
import { extractDomain, isInternalUrl } from '@/helpers/url';
|
|
import { container } from '@services/container';
|
|
import { logger } from '@services/libs/log';
|
|
import type { IMenuService } from '@services/menu/interface';
|
|
import serviceIdentifier from '@services/serviceIdentifier';
|
|
import { getPreloadPath } from '@services/windows/viteEntry';
|
|
import type { IBrowserViewMetaData } from '@services/windows/WindowProperties';
|
|
import { windowDimension, WindowNames } from '@services/windows/WindowProperties';
|
|
import type { IWorkspace, IWorkspaceService } from '@services/workspaces/interface';
|
|
import { isWikiWorkspace } from '@services/workspaces/interface';
|
|
import type { INewWindowAction } from './interface';
|
|
import type { IViewMeta } from './setupViewEventHandlers';
|
|
import { handleOpenFileExternalLink } from './setupViewFileProtocol';
|
|
|
|
export interface INewWindowContext {
|
|
meta: IViewMeta;
|
|
sharedWebPreferences: BrowserWindowConstructorOptions['webPreferences'];
|
|
view: WebContentsView;
|
|
workspace: IWorkspace;
|
|
}
|
|
|
|
export function handleNewWindow(
|
|
details: Electron.HandlerDetails,
|
|
newWindowContext: INewWindowContext,
|
|
parentWebContents: Electron.WebContents,
|
|
): INewWindowAction {
|
|
const { url: nextUrl, disposition, frameName } = details;
|
|
/**
|
|
* Guess from tiddlywiki's `core/modules/startup/windows.js`, it will open with details {
|
|
url: 'about:blank#blocked',
|
|
frameName: 'external-XXXSomeTiddlerTitle',
|
|
features: 'scrollbars,width=700,height=600',
|
|
disposition: 'new-window',
|
|
referrer: { url: '', policy: 'strict-origin-when-cross-origin' },
|
|
postBody: undefined
|
|
}
|
|
*/
|
|
const mightFromTiddlywikiOpenNewWindow = frameName.startsWith('external-');
|
|
logger.debug('Getting url that will open externally', { ...details, fromTW: mightFromTiddlywikiOpenNewWindow });
|
|
// don't show useless blank page
|
|
if (nextUrl.startsWith('about:blank') && !mightFromTiddlywikiOpenNewWindow) {
|
|
logger.debug('ignore about:blank');
|
|
return { action: 'deny' };
|
|
}
|
|
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
|
|
|
|
const nextDomain = extractDomain(nextUrl);
|
|
const handleOpenFileExternalLinkAction = handleOpenFileExternalLink(nextUrl, newWindowContext);
|
|
if (handleOpenFileExternalLinkAction !== undefined) return handleOpenFileExternalLinkAction;
|
|
// open external url in browser
|
|
if (nextDomain !== undefined && (disposition === 'foreground-tab' || disposition === 'background-tab')) {
|
|
logger.debug('openExternal', { nextUrl, nextDomain, disposition, function: 'handleNewWindow' });
|
|
void shell.openExternal(nextUrl).catch((error_: unknown) => {
|
|
const error = error_ as Error;
|
|
logger.error(
|
|
`handleNewWindow() openExternal error ${error.message}`,
|
|
{ error },
|
|
);
|
|
});
|
|
return {
|
|
action: 'deny',
|
|
};
|
|
}
|
|
logger.debug('Allowing creating new window', { newWindowContext, function: 'handleNewWindow' });
|
|
const { view: parentWindowView, workspace, sharedWebPreferences, meta } = newWindowContext;
|
|
const currentUrl = parentWindowView.webContents.getURL();
|
|
/** Conditions are listed by order of priority
|
|
if global.forceNewWindow = true
|
|
or regular new-window event
|
|
or if in Google Drive app, open Google Docs files internally https://github.com/atomery/webcatalog/issues/800
|
|
the next external link request will be opened in new window */
|
|
const clickOpenNewWindow = meta.forceNewWindow || disposition === 'new-window' || disposition === 'default';
|
|
/** App tries to open external link using JS
|
|
nextURL === 'about:blank' but then window will redirect to the external URL
|
|
https://github.com/quanglam2807/webcatalog/issues/467#issuecomment-569857721 */
|
|
const isExternalLinkUsingJS = nextDomain === null && (disposition === 'foreground-tab' || disposition === 'background-tab');
|
|
if (clickOpenNewWindow || isExternalLinkUsingJS) {
|
|
// https://gist.github.com/Gvozd/2cec0c8c510a707854e439fb15c561b0
|
|
// if 'new-window' is triggered with Cmd+Click
|
|
// options is undefined
|
|
// https://github.com/atomery/webcatalog/issues/842
|
|
const browserViewMetaData: IBrowserViewMetaData = {
|
|
isPopup: true,
|
|
...(JSON.parse(
|
|
decodeURIComponent(sharedWebPreferences?.additionalArguments?.[1]?.replace(MetaDataChannel.browserViewMetaData, '') ?? '{}'),
|
|
) as IBrowserViewMetaData),
|
|
};
|
|
logger.debug('open new window request', {
|
|
browserViewMetaData,
|
|
disposition,
|
|
nextUrl,
|
|
nextDomain,
|
|
function: 'handleNewWindow',
|
|
});
|
|
meta.forceNewWindow = false;
|
|
const webPreferences = {
|
|
additionalArguments: [
|
|
`${MetaDataChannel.browserViewMetaData}${WindowNames.view}`,
|
|
`${MetaDataChannel.browserViewMetaData}${encodeURIComponent(JSON.stringify(browserViewMetaData))}`,
|
|
'--unsafely-disable-devtools-self-xss-warnings',
|
|
],
|
|
preload: getPreloadPath(),
|
|
};
|
|
const windowWithBrowserViewState = windowStateKeeper({
|
|
file: 'window-state-open-in-new-window.json',
|
|
path: SETTINGS_FOLDER,
|
|
defaultWidth: windowDimension[WindowNames.main].width,
|
|
defaultHeight: windowDimension[WindowNames.main].height,
|
|
});
|
|
let newOptions: BrowserWindowConstructorOptions = {
|
|
x: windowWithBrowserViewState.x,
|
|
y: windowWithBrowserViewState.y,
|
|
width: windowWithBrowserViewState.width,
|
|
height: windowWithBrowserViewState.height,
|
|
webPreferences,
|
|
autoHideMenuBar: true,
|
|
};
|
|
|
|
if (isExternalLinkUsingJS) {
|
|
newOptions = { ...newOptions, show: false };
|
|
}
|
|
parentWebContents.once('did-create-window', (childWindow) => {
|
|
childWindow.setMenuBarVisibility(false);
|
|
childWindow.webContents.setWindowOpenHandler((details: Electron.HandlerDetails) => handleNewWindow(details, newWindowContext, childWindow.webContents));
|
|
childWindow.webContents.once('will-navigate', async (_event, url) => {
|
|
// if the window is used for the current app, then use default behavior
|
|
const currentWorkspace = await workspaceService.get(workspace.id);
|
|
const appUrl = currentWorkspace && isWikiWorkspace(currentWorkspace) ? currentWorkspace.homeUrl : undefined;
|
|
if (appUrl === undefined) {
|
|
throw new Error(`Workspace ${workspace.id} not existed, or don't have homeUrl setting`);
|
|
}
|
|
if (isInternalUrl(url, [appUrl, currentUrl])) {
|
|
childWindow.show();
|
|
} else {
|
|
// if not, open in browser
|
|
_event.preventDefault();
|
|
void shell.openExternal(url);
|
|
childWindow.close();
|
|
}
|
|
});
|
|
windowWithBrowserViewState.manage(childWindow);
|
|
const menuService = container.get<IMenuService>(serviceIdentifier.MenuService);
|
|
childWindow.webContents.once('dom-ready', async () => {
|
|
await menuService.initContextMenuForWindowWebContents(childWindow.webContents).then((unregisterContextMenu) => {
|
|
childWindow.webContents.on('destroyed', () => {
|
|
unregisterContextMenu();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
return {
|
|
action: 'allow',
|
|
overrideBrowserWindowOptions: newOptions,
|
|
};
|
|
}
|
|
|
|
return { action: 'allow' };
|
|
}
|