feat: 新增包文件

This commit is contained in:
duanshuwen
2025-11-15 22:41:50 +08:00
parent 7b65193e5c
commit 7ada85f175
104 changed files with 11273 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
dist
*.map

View File

@@ -0,0 +1 @@
tsconfig.json

View File

@@ -0,0 +1,56 @@
# electron-chrome-context-menu
> Chrome context menu for Electron browsers
Building a modern web browser requires including many features users have grown accustomed to. Context menus are a small, but noticeable feature when done improperly.
This module aims to provide a context menu with close to feature parity to that of Google Chrome.
## Install
> npm install electron-chrome-context-menu
## Usage
```ts
// ES imports
import buildChromeContextMenu from 'electron-chrome-context-menu'
// CommonJS
const buildChromeContextMenu = require('electron-chrome-context-menu').default
const { app } = require('electron')
app.on('web-contents-created', (event, webContents) => {
webContents.on('context-menu', (e, params) => {
const menu = buildChromeContextMenu({
params,
webContents,
openLink: (url, disposition) => {
webContents.loadURL(url)
}
})
menu.popup()
})
})
```
> For a complete example, see the [`electron-browser-shell`](https://github.com/samuelmaddock/electron-browser-shell) project.
## API
### `buildChromeContextMenu(options)`
* `options` Object
* `params` Electron.ContextMenuParams - Context menu parameters emitted from the WebContents 'context-menu' event.
* `webContents` Electron.WebContents - WebContents which emitted the 'context-menu' event.
* `openLink(url, disposition, params)` - Handler for opening links.
* `url` String
* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, and `new-window`.
* `params` Electron.ContextMenuParams
* `extensionMenuItems` Electron.MenuItem[] (optional) - Collection of menu items for active web extensions.
* `labels` Object (optional) - Labels used to create menu items. Replace this if localization is needed.
## License
MIT

View 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

View File

@@ -0,0 +1,25 @@
{
"name": "electron-chrome-context-menu",
"version": "1.0.0",
"description": "Chrome context menu for Electron browsers",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"prepublishOnly": "yarn build"
},
"keywords": [
"electron",
"chrome",
"context",
"menu"
],
"repository": "",
"author": "Damon <damon.shuwen@gmail.com>",
"license": "MIT",
"peerDependencies": {
"electron": ">=38.2.2"
},
"devDependencies": {
"typescript": "^4.5.4"
}
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"moduleResolution": "node",
"outDir": "dist",
"declaration": true
},
"include": ["*"],
"exclude": ["node_modules"]
}