import { DEFAULT_DOWNLOADS_PATH } from '@/constants/appPaths'; import { MetaDataChannel, WikiChannel } from '@/constants/channels'; import { isHtmlWiki, wikiHtmlExtensions } from '@/constants/fileNames'; import { container } from '@services/container'; import getFromRenderer from '@services/libs/getFromRenderer'; import { i18n } from '@services/libs/i18n'; import { isBrowserWindow } from '@services/libs/isBrowserWindow'; import { logger } from '@services/libs/log'; import type { IMenuService } from '@services/menu/interface'; import type { INativeService } from '@services/native/interface'; import serviceIdentifier from '@services/serviceIdentifier'; import type { IViewService } from '@services/view/interface'; import type { IWikiService } from '@services/wiki/interface'; import type { IWindowService } from '@services/windows/interface'; import type { IBrowserViewMetaData } from '@services/windows/WindowProperties'; import { WindowNames } from '@services/windows/WindowProperties'; import type { IWorkspaceService } from '@services/workspaces/interface'; import { isWikiWorkspace } from '@services/workspaces/interface'; import { clipboard, dialog } from 'electron'; import { CancelError as DownloadCancelError, download } from 'electron-dl'; import { minify } from 'html-minifier-terser'; import path from 'path'; export async function registerMenu(): Promise { const menuService = container.get(serviceIdentifier.MenuService); const windowService = container.get(serviceIdentifier.Window); const viewService = container.get(serviceIdentifier.View); const workspaceService = container.get(serviceIdentifier.Workspace); const nativeService = container.get(serviceIdentifier.NativeService); const wikiService = container.get(serviceIdentifier.Wiki); const hasActiveWorkspaces = async (): Promise => (await workspaceService.getActiveWorkspace()) !== undefined; const hasActiveWikiWorkspace = async (): Promise => { const activeWorkspace = await workspaceService.getActiveWorkspace(); return activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace); }; await menuService.insertMenu('Workspaces', [ { label: () => i18n.t('Menu.DeveloperToolsActiveWorkspace'), accelerator: 'CmdOrCtrl+Option+I', click: async () => (await viewService.getActiveBrowserView())?.webContents.openDevTools({ mode: 'detach' }), enabled: hasActiveWorkspaces, }, ]); // Insert Wiki menu items (each item checks for active wiki workspace) await menuService.insertMenu('Wiki', [ { label: () => i18n.t('Menu.PrintPage'), visible: hasActiveWikiWorkspace, click: async () => { try { const browserView = await viewService.getActiveBrowserView(); const win = windowService.get(WindowNames.main); logger.info( `print page, browserView printToPDF method is ${browserView?.webContents.printToPDF === undefined ? 'undefined' : 'define'}, win is ${ win === undefined ? 'undefined' : 'define' }`, ); if (browserView === undefined || win === undefined) { return; } const pdfBuffer = await browserView.webContents.printToPDF({ generateTaggedPDF: true, }); // turn buffer to data uri const dataUri = `data:application/pdf;base64,${pdfBuffer.toString('base64')}`; await download(win, dataUri, { filename: 'wiki.pdf', overwrite: false }); logger.info(`print page done`); } catch (error) { if (error instanceof DownloadCancelError) { logger.debug('cancelled', { function: 'registerMenu.printPage' }); } else { logger.error('print page error', { function: 'registerMenu.printPage', error }); } } }, enabled: hasActiveWorkspaces, }, { label: () => i18n.t('Menu.ExportActiveTiddler'), accelerator: 'CmdOrCtrl+Alt+Shift+P', visible: hasActiveWikiWorkspace, click: async () => { const activeWorkspace = await workspaceService.getActiveWorkspace(); if (activeWorkspace === undefined) { logger.error('Can not print active tiddler, activeWorkspace is undefined'); return; } const title = await wikiService.wikiOperationInBrowser(WikiChannel.getTiddlerText, activeWorkspace.id, ['$:/temp/focussedTiddler']); if (title === undefined) { const mainWindow = windowService.get(WindowNames.main); if (mainWindow === undefined) return; void dialog.showMessageBox(mainWindow, { title: i18n.t('Dialog.FocusedTiddlerNotFoundTitle'), message: i18n.t('Dialog.FocusedTiddlerNotFoundTitleDetail'), buttons: ['OK'], cancelId: 0, defaultId: 0, }); return; } const htmlContent = await wikiService.wikiOperationInBrowser(WikiChannel.renderTiddlerOuterHTML, activeWorkspace.id, [title]); // download html content const win = windowService.get(WindowNames.main); if (win === undefined || htmlContent === undefined) return; const minified = await minify(htmlContent, { minifyCSS: { level: 2, }, collapseWhitespace: true, collapseInlineTagWhitespace: true, continueOnParseError: true, removeComments: true, }); await download(win, `data:text/html,${encodeURIComponent(minified)}`, { filename: `${title}.html`, saveAs: true }); }, enabled: hasActiveWorkspaces, }, { label: () => i18n.t('Menu.ExportWholeWikiHTML'), visible: hasActiveWikiWorkspace, click: async () => { const activeWorkspace = await workspaceService.getActiveWorkspace(); if (activeWorkspace === undefined) { logger.error('Can not export whole wiki, activeWorkspace is undefined'); return; } if (!isWikiWorkspace(activeWorkspace)) { logger.error('Can not export whole wiki, activeWorkspace is not a wiki workspace'); return; } const pathOfNewHTML = await nativeService.pickDirectory(DEFAULT_DOWNLOADS_PATH, { allowOpenFile: true, filters: [{ name: 'HTML', extensions: wikiHtmlExtensions }], }); if (pathOfNewHTML.length > 0) { const fileName = isHtmlWiki(pathOfNewHTML[0]) ? pathOfNewHTML[0] : path.join(pathOfNewHTML[0], `${activeWorkspace.name}.html`); await wikiService.packetHTMLFromWikiFolder(activeWorkspace.wikiFolderLocation, fileName); } else { logger.error("Can not export whole wiki, pickDirectory's pathOfNewHTML is empty"); } }, enabled: hasActiveWorkspaces, }, { type: 'separator', visible: hasActiveWikiWorkspace }, { label: () => i18n.t('ContextMenu.CopyLink'), accelerator: 'CmdOrCtrl+L', visible: hasActiveWikiWorkspace, click: async (_menuItem, browserWindow) => { // if back is called in popup window // copy the popup window URL instead if (isBrowserWindow(browserWindow)) { const { isPopup } = await getFromRenderer(MetaDataChannel.getViewMetaData, browserWindow); if (isPopup === true) { const url = browserWindow.webContents.getURL(); clipboard.writeText(url); return; } } const view = await viewService.getActiveBrowserView(); const url = view?.webContents.getURL(); if (typeof url === 'string') { clipboard.writeText(url); } }, }, ]); }