feat: 新增包文件
This commit is contained in:
249
packages/electron-chrome-context-menu/index.ts
Normal file
249
packages/electron-chrome-context-menu/index.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import { app, BrowserWindow, clipboard, Menu, MenuItem } from 'electron'
|
||||
|
||||
const LABELS = {
|
||||
openInNewTab: (type: 'link' | Electron.ContextMenuParams['mediaType']) =>
|
||||
`Open ${type} in new tab`,
|
||||
openInNewWindow: (type: 'link' | Electron.ContextMenuParams['mediaType']) =>
|
||||
`Open ${type} in new window`,
|
||||
copyAddress: (type: 'link' | Electron.ContextMenuParams['mediaType']) => `Copy ${type} address`,
|
||||
undo: 'Undo',
|
||||
redo: 'Redo',
|
||||
cut: 'Cut',
|
||||
copy: 'Copy',
|
||||
delete: 'Delete',
|
||||
paste: 'Paste',
|
||||
selectAll: 'Select All',
|
||||
back: 'Back',
|
||||
forward: 'Forward',
|
||||
reload: 'Reload',
|
||||
inspect: 'Inspect',
|
||||
addToDictionary: 'Add to dictionary',
|
||||
exitFullScreen: 'Exit full screen',
|
||||
emoji: 'Emoji',
|
||||
}
|
||||
|
||||
const getBrowserWindowFromWebContents = (webContents: Electron.WebContents) => {
|
||||
return BrowserWindow.getAllWindows().find((win) => {
|
||||
if (win.webContents === webContents) return true
|
||||
|
||||
let browserViews: Electron.BrowserView[]
|
||||
|
||||
if ('getBrowserViews' in win) {
|
||||
browserViews = win.getBrowserViews()
|
||||
} else if ('getBrowserView' in win) {
|
||||
// @ts-ignore
|
||||
browserViews = [win.getBrowserView()]
|
||||
} else {
|
||||
browserViews = []
|
||||
}
|
||||
|
||||
return browserViews.some((view) => view.webContents === webContents)
|
||||
})
|
||||
}
|
||||
|
||||
type ChromeContextMenuLabels = typeof LABELS
|
||||
|
||||
interface ChromeContextMenuOptions {
|
||||
/** Context menu parameters emitted from the WebContents 'context-menu' event. */
|
||||
params: Electron.ContextMenuParams
|
||||
|
||||
/** WebContents which emitted the 'context-menu' event. */
|
||||
webContents: Electron.WebContents
|
||||
|
||||
/** Handler for opening links. */
|
||||
openLink: (
|
||||
url: string,
|
||||
disposition: 'default' | 'foreground-tab' | 'background-tab' | 'new-window',
|
||||
params: Electron.ContextMenuParams,
|
||||
) => void
|
||||
|
||||
/** Chrome extension menu items. */
|
||||
extensionMenuItems?: MenuItem[]
|
||||
|
||||
/** Labels used to create menu items. Replace this if localization is needed. */
|
||||
labels?: ChromeContextMenuLabels
|
||||
|
||||
/**
|
||||
* @deprecated Use 'labels' instead.
|
||||
*/
|
||||
strings?: ChromeContextMenuLabels
|
||||
}
|
||||
|
||||
export const buildChromeContextMenu = (opts: ChromeContextMenuOptions): Menu => {
|
||||
const { params, webContents, openLink, extensionMenuItems } = opts
|
||||
|
||||
const labels = opts.labels || opts.strings || LABELS
|
||||
|
||||
const menu = new Menu()
|
||||
const append = (opts: Electron.MenuItemConstructorOptions) => menu.append(new MenuItem(opts))
|
||||
const appendSeparator = () => menu.append(new MenuItem({ type: 'separator' }))
|
||||
|
||||
if (params.linkURL) {
|
||||
append({
|
||||
label: labels.openInNewTab('link'),
|
||||
click: () => {
|
||||
openLink(params.linkURL, 'default', params)
|
||||
},
|
||||
})
|
||||
append({
|
||||
label: labels.openInNewWindow('link'),
|
||||
click: () => {
|
||||
openLink(params.linkURL, 'new-window', params)
|
||||
},
|
||||
})
|
||||
appendSeparator()
|
||||
append({
|
||||
label: labels.copyAddress('link'),
|
||||
click: () => {
|
||||
clipboard.writeText(params.linkURL)
|
||||
},
|
||||
})
|
||||
appendSeparator()
|
||||
} else if (params.mediaType !== 'none') {
|
||||
// TODO: Loop, Show controls
|
||||
append({
|
||||
label: labels.openInNewTab(params.mediaType),
|
||||
click: () => {
|
||||
openLink(params.srcURL, 'default', params)
|
||||
},
|
||||
})
|
||||
append({
|
||||
label: labels.copyAddress(params.mediaType),
|
||||
click: () => {
|
||||
clipboard.writeText(params.srcURL)
|
||||
},
|
||||
})
|
||||
appendSeparator()
|
||||
}
|
||||
|
||||
if (params.isEditable) {
|
||||
if (params.misspelledWord) {
|
||||
for (const suggestion of params.dictionarySuggestions) {
|
||||
append({
|
||||
label: suggestion,
|
||||
click: () => webContents.replaceMisspelling(suggestion),
|
||||
})
|
||||
}
|
||||
|
||||
if (params.dictionarySuggestions.length > 0) appendSeparator()
|
||||
|
||||
append({
|
||||
label: labels.addToDictionary,
|
||||
click: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord),
|
||||
})
|
||||
} else {
|
||||
if (
|
||||
app.isEmojiPanelSupported() &&
|
||||
!['input-number', 'input-telephone'].includes(params.formControlType)
|
||||
) {
|
||||
append({
|
||||
label: labels.emoji,
|
||||
click: () => app.showEmojiPanel(),
|
||||
})
|
||||
appendSeparator()
|
||||
}
|
||||
|
||||
append({
|
||||
label: labels.redo,
|
||||
enabled: params.editFlags.canRedo,
|
||||
click: () => webContents.redo(),
|
||||
})
|
||||
append({
|
||||
label: labels.undo,
|
||||
enabled: params.editFlags.canUndo,
|
||||
click: () => webContents.undo(),
|
||||
})
|
||||
}
|
||||
|
||||
appendSeparator()
|
||||
|
||||
append({
|
||||
label: labels.cut,
|
||||
enabled: params.editFlags.canCut,
|
||||
click: () => webContents.cut(),
|
||||
})
|
||||
append({
|
||||
label: labels.copy,
|
||||
enabled: params.editFlags.canCopy,
|
||||
click: () => webContents.copy(),
|
||||
})
|
||||
append({
|
||||
label: labels.paste,
|
||||
enabled: params.editFlags.canPaste,
|
||||
click: () => webContents.paste(),
|
||||
})
|
||||
append({
|
||||
label: labels.delete,
|
||||
enabled: params.editFlags.canDelete,
|
||||
click: () => webContents.delete(),
|
||||
})
|
||||
appendSeparator()
|
||||
if (params.editFlags.canSelectAll) {
|
||||
append({
|
||||
label: labels.selectAll,
|
||||
click: () => webContents.selectAll(),
|
||||
})
|
||||
appendSeparator()
|
||||
}
|
||||
} else if (params.selectionText) {
|
||||
append({
|
||||
label: labels.copy,
|
||||
click: () => {
|
||||
clipboard.writeText(params.selectionText)
|
||||
},
|
||||
})
|
||||
appendSeparator()
|
||||
}
|
||||
|
||||
if (menu.items.length === 0) {
|
||||
const browserWindow = getBrowserWindowFromWebContents(webContents)
|
||||
|
||||
// TODO: Electron needs a way to detect whether we're in HTML5 full screen.
|
||||
// Also need to properly exit full screen in Blink rather than just exiting
|
||||
// the Electron BrowserWindow.
|
||||
if (browserWindow?.fullScreen) {
|
||||
append({
|
||||
label: labels.exitFullScreen,
|
||||
click: () => browserWindow.setFullScreen(false),
|
||||
})
|
||||
|
||||
appendSeparator()
|
||||
}
|
||||
|
||||
append({
|
||||
label: labels.back,
|
||||
enabled: webContents.navigationHistory.canGoBack(),
|
||||
click: () => webContents.navigationHistory.goBack(),
|
||||
})
|
||||
append({
|
||||
label: labels.forward,
|
||||
enabled: webContents.navigationHistory.canGoForward(),
|
||||
click: () => webContents.navigationHistory.goForward(),
|
||||
})
|
||||
append({
|
||||
label: labels.reload,
|
||||
click: () => webContents.reload(),
|
||||
})
|
||||
appendSeparator()
|
||||
}
|
||||
|
||||
if (extensionMenuItems) {
|
||||
extensionMenuItems.forEach((item) => menu.append(item))
|
||||
if (extensionMenuItems.length > 0) appendSeparator()
|
||||
}
|
||||
|
||||
append({
|
||||
label: labels.inspect,
|
||||
click: () => {
|
||||
webContents.inspectElement(params.x, params.y)
|
||||
|
||||
if (!webContents.isDevToolsFocused()) {
|
||||
webContents.devToolsWebContents?.focus()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
export default buildChromeContextMenu
|
||||
Reference in New Issue
Block a user