electron-findbar 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -47
- package/index.js +171 -51
- package/package.json +7 -1
- package/remote.js +26 -4
- package/web/app.css +46 -17
- package/web/findbar.html +17 -11
- package/web/preload.js +82 -0
- package/.vscode/launch.json +0 -17
- package/test/sample.html +0 -28
- package/test/sample.js +0 -43
- package/web/app.js +0 -53
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<p align='center'>
|
|
2
2
|
<a href="https://github.com/ECRomaneli/handbook" style='text-decoration:none'>
|
|
3
|
-
<img src="https://i.postimg.cc/
|
|
4
|
-
<img src="https://i.postimg.cc/
|
|
3
|
+
<img src="https://i.postimg.cc/sXwqJP59/findbar-v2-light.png" alt='Findbar Light Theme'>
|
|
4
|
+
<img src="https://i.postimg.cc/j26XXRVV/findbar-v2-dark.png" alt='Findbar Dark Theme'>
|
|
5
5
|
</a>
|
|
6
6
|
</p>
|
|
7
7
|
<p align='center'>
|
|
@@ -47,13 +47,35 @@ const Findbar = require('electron-findbar')
|
|
|
47
47
|
You can pass a `BrowserWindow` instance as a single parameter to use it as the parent window. The `BrowserWindow.WebContents` will be used as the findable content:
|
|
48
48
|
|
|
49
49
|
```js
|
|
50
|
-
|
|
50
|
+
// Create or retrieve the findbar associated to the browserWindow.webContents. If a new findbar is created, the browserWindow is used as parent.
|
|
51
|
+
const findbar = Findbar.from(browserWindow)
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
Alternatively, you can provide a custom `WebContents` as the second parameter. In this case, the first parameter can be any `BaseWindow`, and the second parameter will be the findable content:
|
|
54
55
|
|
|
55
56
|
```js
|
|
56
|
-
|
|
57
|
+
// Create or retrieve the findbar associated to the webContents. If a new findbar is created, the baseWindow is used as parent.
|
|
58
|
+
const findbar = Findbar.from(baseWindow, webContents)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Is also possible to create a findbar without a parent window (even though it is not recommended):
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// Create or retrieve the findbar associated to the webContents. If a new findbar is created, it will be displayed in the middle of the screen without a parent to connect to.
|
|
65
|
+
const findbar = Findbar.from(webContents)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Note:** The findbar is ALWAYS linked to the webContents not the window. The parent is only the window to connect the events and stay on top. If the `.from(webContents)` is used to retrieve an existing findbar previously created with a parent, the findbar will stay connected to the parent.
|
|
69
|
+
|
|
70
|
+
#### Retrieve if exists
|
|
71
|
+
|
|
72
|
+
If there is no intention to create a new findbar in case it does not exist, use:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// Get the existing findbar or undefined.
|
|
76
|
+
const existingFindbar = Findbar.fromIfExists(browserWindow)
|
|
77
|
+
/* OR */
|
|
78
|
+
const existingFindbar = Findbar.fromIfExists(webContents)
|
|
57
79
|
```
|
|
58
80
|
|
|
59
81
|
### Configuring the Findbar
|
|
@@ -61,7 +83,7 @@ const findbar = new Findbar(baseWindow, customWebContents)
|
|
|
61
83
|
You can customize the Findbar window options using the `setWindowOptions` method:
|
|
62
84
|
|
|
63
85
|
```js
|
|
64
|
-
findbar.setWindowOptions({
|
|
86
|
+
findbar.setWindowOptions({ resizable: true, alwaysOnTop: true, height: 100 })
|
|
65
87
|
```
|
|
66
88
|
|
|
67
89
|
To handle the Findbar window directly after it is opened, use the `setWindowHandler` method:
|
|
@@ -72,12 +94,14 @@ findbar.setWindowHandler(win => {
|
|
|
72
94
|
});
|
|
73
95
|
```
|
|
74
96
|
|
|
75
|
-
The findbar has a default position handler which moves the findbar to the top-right corner. To change the position handler, use the `
|
|
97
|
+
The findbar has a default position handler which moves the findbar to the top-right corner. To change the position handler, use the `setBoundsHandler` method. The bounds handler is called when the parent window moves or resizes and provides both the parent and findbar bounds as parameters.
|
|
76
98
|
|
|
77
99
|
```js
|
|
78
|
-
findbar.
|
|
100
|
+
findbar.setBoundsHandler((parentBounds, findbarBounds) => ({
|
|
79
101
|
x: parentBounds.x + parentBounds.width - findbarBounds.width - 20,
|
|
80
102
|
y: parentBounds.y - ((findbarBounds.height / 4) | 0)
|
|
103
|
+
/* width: OPTIONAL, current value will be used */
|
|
104
|
+
/* height: OPTIONAL, current value will be used */
|
|
81
105
|
}))
|
|
82
106
|
```
|
|
83
107
|
|
|
@@ -89,38 +113,6 @@ The Findbar is a child window of the `BaseWindow` passed during construction. To
|
|
|
89
113
|
findbar.open()
|
|
90
114
|
```
|
|
91
115
|
|
|
92
|
-
### Finding Text in the Page
|
|
93
|
-
|
|
94
|
-
Once open, the Findbar appears by default in the top-right corner of the parent window and can be used without additional coding. Alternatively, you can use the following methods to trigger `findInPage` and navigate through matches in the main process:
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
/**
|
|
98
|
-
* Retrieve the last queried value.
|
|
99
|
-
*/
|
|
100
|
-
getLastValue()
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Initiate a request to find all matches for the specified text on the page.
|
|
104
|
-
* @param {string} text - The text to search for.
|
|
105
|
-
*/
|
|
106
|
-
startFind(text)
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Select the previous match, if available.
|
|
110
|
-
*/
|
|
111
|
-
findPrevious()
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Select the next match, if available.
|
|
115
|
-
*/
|
|
116
|
-
findNext()
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Stop the find request.
|
|
120
|
-
*/
|
|
121
|
-
stopFind()
|
|
122
|
-
```
|
|
123
|
-
|
|
124
116
|
### Closing the Findbar
|
|
125
117
|
|
|
126
118
|
When the Findbar is closed, its window is destroyed to free memory resources. Use the following method to close the Findbar:
|
|
@@ -144,21 +136,148 @@ app.whenReady().then(() => {
|
|
|
144
136
|
window.loadURL('https://github.com/ECRomaneli/electron-findbar')
|
|
145
137
|
|
|
146
138
|
// Create and configure the Findbar object
|
|
147
|
-
const findbar =
|
|
139
|
+
const findbar = Findbar.from(window)
|
|
148
140
|
|
|
149
141
|
// [OPTIONAL] Customize window options
|
|
150
142
|
findbar.setWindowOptions({ movable: true, resizable: true })
|
|
151
143
|
|
|
152
144
|
// [OPTIONAL] Handle the window object when the Findbar is opened
|
|
153
|
-
findbar.setWindowHandler(win => {
|
|
154
|
-
win.webContents.openDevTools()
|
|
155
|
-
})
|
|
145
|
+
findbar.setWindowHandler(win => { win.webContents.openDevTools() })
|
|
156
146
|
|
|
157
147
|
// Open the Findbar
|
|
158
148
|
findbar.open()
|
|
159
149
|
})
|
|
160
150
|
```
|
|
161
151
|
|
|
152
|
+
### Configuring Keyboard Shortcuts
|
|
153
|
+
|
|
154
|
+
The Findbar component can be controlled using keyboard shortcuts. Below are two implementation approaches to help you integrate search functionality seamlessly into your application's user experience.
|
|
155
|
+
|
|
156
|
+
**Note:** The following examples demonstrate only the ideal (happy path) scenarios. For production use, make sure to thoroughly validate all inputs and handle edge cases appropriately.
|
|
157
|
+
|
|
158
|
+
#### Using Before Input Event
|
|
159
|
+
|
|
160
|
+
The `before-input-event` approach allows you to capture keyboard events directly in the main process before they're processed by the web contents, giving you precise control:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
window.webContents.on('before-input-event', (event, input) => {
|
|
164
|
+
// Detect Ctrl+F (Windows/Linux) or Command+F (macOS)
|
|
165
|
+
if ((input.control || input.meta) && input.key.toLowerCase() === 'f') {
|
|
166
|
+
// Prevent default browser behavior
|
|
167
|
+
event.preventDefault()
|
|
168
|
+
|
|
169
|
+
// Access and open the findbar
|
|
170
|
+
const findbar = Findbar.from(window)
|
|
171
|
+
if (findbar) {
|
|
172
|
+
findbar.open()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle Escape key to close the findbar
|
|
177
|
+
if (input.key === 'Escape') {
|
|
178
|
+
const findbar = Findbar.from(window)
|
|
179
|
+
if (findbar && findbar.isOpen()) {
|
|
180
|
+
event.preventDefault()
|
|
181
|
+
findbar.close()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Using Menu Accelerators
|
|
188
|
+
|
|
189
|
+
For a more integrated approach, you can modify your application's menu system to include findbar controls with keyboard accelerators. This method makes shortcuts available throughout your application:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
// Get reference to the parent window
|
|
193
|
+
const parent = currentBrowserWindowOrWebContents
|
|
194
|
+
|
|
195
|
+
// Get or create application menu
|
|
196
|
+
const appMenu = Menu.getApplicationMenu() ?? new Menu()
|
|
197
|
+
|
|
198
|
+
// Add Findbar controls to menu
|
|
199
|
+
appMenu.append(new MenuItem({
|
|
200
|
+
label: 'Find',
|
|
201
|
+
submenu: [
|
|
202
|
+
{
|
|
203
|
+
label: 'Find in Page',
|
|
204
|
+
click: () => Findbar.from(parent)?.open(),
|
|
205
|
+
accelerator: 'CommandOrControl+F'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
label: 'Close Find',
|
|
209
|
+
click: () => Findbar.from(parent)?.close(),
|
|
210
|
+
accelerator: 'Esc'
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}))
|
|
214
|
+
|
|
215
|
+
// Apply the updated menu
|
|
216
|
+
Menu.setApplicationMenu(appMenu)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Both approaches have their advantages - the first offers fine-grained control over exactly when shortcuts are activated, while the second provides better integration with standard application menu conventions.
|
|
220
|
+
|
|
221
|
+
### Finding Text using the main process
|
|
222
|
+
|
|
223
|
+
Once open, the Findbar appears by default in the top-right corner of the parent window and can be used without additional coding. Alternatively, you can use the following methods to trigger `findInPage` and navigate through matches in the main process:
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
/**
|
|
227
|
+
* Get the last state of the findbar.
|
|
228
|
+
* @returns {{ text: string, matchCase: boolean, movable: boolean }} Last state of the findbar.
|
|
229
|
+
*/
|
|
230
|
+
getLastState()
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Initiate a request to find all matches for the specified text on the page.
|
|
234
|
+
* @param {string} text - The text to search for.
|
|
235
|
+
* @param {boolean} [skipRendererEvent=false] - Skip update renderer event.
|
|
236
|
+
*/
|
|
237
|
+
startFind(text, skipRendererEvent)
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Whether the search should be case-sensitive.
|
|
241
|
+
* @param {boolean} status - Whether the search should be case-sensitive. Default is false.
|
|
242
|
+
* @param {boolean} [skipRendererEvent=false] - Skip update renderer event.
|
|
243
|
+
*/
|
|
244
|
+
matchCase(status, skipRendererEvent)
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Select the previous match, if available.
|
|
248
|
+
*/
|
|
249
|
+
findPrevious()
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Select the next match, if available.
|
|
253
|
+
*/
|
|
254
|
+
findNext()
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Stop the find request and clears selection.
|
|
258
|
+
*/
|
|
259
|
+
stopFind()
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Whether the findbar is opened.
|
|
263
|
+
* @returns {boolean} True if the findbar is open, otherwise false.
|
|
264
|
+
*/
|
|
265
|
+
isOpen()
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Whether the findbar is focused. If the findbar is closed, false will be returned.
|
|
269
|
+
* @returns {boolean} True if the findbar is focused, otherwise false.
|
|
270
|
+
*/
|
|
271
|
+
isFocused()
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Whether the findbar is visible to the user in the foreground of the app.
|
|
275
|
+
* If the findbar is closed, false will be returned.
|
|
276
|
+
* @returns {boolean} True if the findbar is visible, otherwise false.
|
|
277
|
+
*/
|
|
278
|
+
isVisible()
|
|
279
|
+
```
|
|
280
|
+
|
|
162
281
|
## IPC Events
|
|
163
282
|
|
|
164
283
|
As an alternative, the findbar can be controlled using IPC events in the `renderer` process of the `WebContents` provided during the findbar construction.
|
|
@@ -169,12 +288,12 @@ If the `contextIsolation` is enabled, the `electron-findbar/remote` will not be
|
|
|
169
288
|
|
|
170
289
|
```js
|
|
171
290
|
const $remote = (ipc => ({
|
|
172
|
-
|
|
291
|
+
getLastState: async () => ipc.invoke('electron-findbar/last-state'),
|
|
173
292
|
inputChange: (value) => { ipc.send('electron-findbar/input-change', value) },
|
|
293
|
+
matchCase: (value) => { ipc.send('electron-findbar/match-case', value) },
|
|
174
294
|
previous: () => { ipc.send('electron-findbar/previous') },
|
|
175
295
|
next: () => { ipc.send('electron-findbar/next') },
|
|
176
|
-
|
|
177
|
-
close: () => { ipc.send('electron-findbar/close') },
|
|
296
|
+
close: () => { ipc.send('electron-findbar/close') }
|
|
178
297
|
})) (require('electron').ipcRenderer)
|
|
179
298
|
|
|
180
299
|
$remote.open()
|
|
@@ -198,5 +317,5 @@ Created by [Emerson Capuchi Romaneli](https://github.com/ECRomaneli) (@ECRomanel
|
|
|
198
317
|
|
|
199
318
|
## License
|
|
200
319
|
|
|
201
|
-
This project is licensed under the [MIT License](https://github.com/ECRomaneli/
|
|
320
|
+
This project is licensed under the [MIT License](https://github.com/ECRomaneli/electron-findbar/blob/master/LICENSE).
|
|
202
321
|
|
package/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { BaseWindow, BrowserWindow, WebContents, BrowserWindowConstructorOptions, Rectangle } = require('electron')
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Chrome-like findbar for Electron applications.
|
|
5
|
+
*/
|
|
3
6
|
class Findbar {
|
|
4
7
|
/** @type {BaseWindow} */
|
|
5
8
|
#parent
|
|
@@ -10,14 +13,14 @@ class Findbar {
|
|
|
10
13
|
/** @type {WebContents} */
|
|
11
14
|
#findableContents
|
|
12
15
|
|
|
13
|
-
/** */
|
|
16
|
+
/** @type { { active: number, total: number } } */
|
|
14
17
|
#matches
|
|
15
18
|
|
|
16
19
|
/** @type {(findbarWindow: BrowserWindow) => void} */
|
|
17
20
|
#windowHandler
|
|
18
21
|
|
|
19
|
-
/** @type {{parentBounds: Rectangle, findbarBounds: Rectangle} =>
|
|
20
|
-
#
|
|
22
|
+
/** @type {{parentBounds: Rectangle, findbarBounds: Rectangle} => Rectangle} */
|
|
23
|
+
#boundsHandler = Findbar.#setDefaultPosition
|
|
21
24
|
|
|
22
25
|
/** @type {BrowserWindowConstructorOptions} */
|
|
23
26
|
#customOptions
|
|
@@ -25,6 +28,12 @@ class Findbar {
|
|
|
25
28
|
/** @type {string} */
|
|
26
29
|
#lastText = ''
|
|
27
30
|
|
|
31
|
+
/** @type {boolean} */
|
|
32
|
+
#matchCase = false
|
|
33
|
+
|
|
34
|
+
/** @type {boolean} */
|
|
35
|
+
#isMovable = false
|
|
36
|
+
|
|
28
37
|
/**
|
|
29
38
|
* Workaround to fix "findInPage" bug - double-click to loop
|
|
30
39
|
* @type {boolean | null}
|
|
@@ -32,37 +41,56 @@ class Findbar {
|
|
|
32
41
|
#fixMove = null
|
|
33
42
|
|
|
34
43
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @
|
|
38
|
-
*
|
|
44
|
+
* Configure the findbar and link to the web contents.
|
|
45
|
+
*
|
|
46
|
+
* @overload
|
|
47
|
+
* @param {BrowserWindow} browserWindow Parent window.
|
|
48
|
+
* @param {WebContents} [customWebContents] Custom findable web contents. If not provided, the web contents of the BrowserWindow will be used.
|
|
49
|
+
* @returns {Findbar} The findbar instance if it exists.
|
|
50
|
+
*
|
|
51
|
+
* @overload
|
|
52
|
+
* @param {BaseWindow} baseWindow Parent window.
|
|
53
|
+
* @param {WebContents} webContents Findable web contents.
|
|
54
|
+
* @returns {Findbar} The findbar instance if it exists.
|
|
55
|
+
* @throws {Error} If no webContents is provided.
|
|
56
|
+
* *
|
|
57
|
+
* @overload
|
|
58
|
+
* @param {WebContents} webContents Findable web contents. The parent window will be undefined.
|
|
59
|
+
* @returns {Findbar} The findbar instance if it exists.
|
|
60
|
+
* @throws {Error} If no webContents is provided.
|
|
39
61
|
*/
|
|
40
62
|
constructor (parent, webContents) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
if (isFindable(parent)) {
|
|
64
|
+
this.#parent = void 0
|
|
65
|
+
this.#findableContents = parent
|
|
66
|
+
} else {
|
|
67
|
+
this.#parent = parent
|
|
68
|
+
this.#findableContents = webContents ?? parent.webContents
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
if (!this.#findableContents) {
|
|
46
72
|
throw new Error('There are no searchable web contents.')
|
|
47
73
|
}
|
|
74
|
+
|
|
75
|
+
this.#findableContents._findbar = this
|
|
48
76
|
}
|
|
49
77
|
|
|
50
78
|
/**
|
|
51
79
|
* Open the findbar. If the findbar is already opened, focus the input text.
|
|
80
|
+
* @returns {void}
|
|
52
81
|
*/
|
|
53
82
|
open() {
|
|
54
83
|
if (this.#window) {
|
|
55
84
|
this.#focusWindowAndHighlightInput()
|
|
56
85
|
return
|
|
57
86
|
}
|
|
58
|
-
|
|
87
|
+
const options = Findbar.#mergeStandardOptions(this.#customOptions, this.#parent)
|
|
88
|
+
this.#isMovable = options.movable
|
|
89
|
+
this.#window = new BrowserWindow(options)
|
|
59
90
|
this.#window.webContents._findbar = this
|
|
60
91
|
|
|
61
92
|
this.#registerListeners()
|
|
62
93
|
|
|
63
|
-
const pos = this.#positionHandler(this.#parent.getBounds(), this.#window.getBounds())
|
|
64
|
-
this.#window.setPosition(pos.x, pos.y)
|
|
65
|
-
|
|
66
94
|
this.#windowHandler && this.#windowHandler(this.#window)
|
|
67
95
|
|
|
68
96
|
this.#window.loadFile(`${__dirname}/web/findbar.html`)
|
|
@@ -70,59 +98,82 @@ class Findbar {
|
|
|
70
98
|
|
|
71
99
|
/**
|
|
72
100
|
* Close the findbar.
|
|
101
|
+
* @returns {void}
|
|
73
102
|
*/
|
|
74
103
|
close() {
|
|
75
|
-
this.#window
|
|
104
|
+
if (!this.#window || this.#window.isDestroyed()) { return }
|
|
105
|
+
if (!this.#window.isVisible()) { this.#window.close(); return }
|
|
106
|
+
this.#window.on('hide', () => { this.#window.close() })
|
|
107
|
+
this.#window.hide()
|
|
76
108
|
}
|
|
77
109
|
|
|
78
110
|
/**
|
|
79
|
-
* Get last
|
|
111
|
+
* Get the last state of the findbar.
|
|
112
|
+
* @returns {{ text: string, matchCase: boolean, movable: boolean }} Last state of the findbar.
|
|
80
113
|
*/
|
|
81
|
-
|
|
82
|
-
return this.#lastText
|
|
114
|
+
getLastState() {
|
|
115
|
+
return { text: this.#lastText, matchCase: this.#matchCase, movable: this.#isMovable }
|
|
83
116
|
}
|
|
84
117
|
|
|
85
118
|
/**
|
|
86
119
|
* Starts a request to find all matches for the text in the page.
|
|
87
|
-
* @param {string} text Value to find in page.
|
|
88
|
-
* @param {boolean
|
|
120
|
+
* @param {string} text - Value to find in page.
|
|
121
|
+
* @param {boolean} [skipRendererEvent=false] - Skip update renderer event.
|
|
122
|
+
* @returns {void}
|
|
89
123
|
*/
|
|
90
|
-
startFind(text,
|
|
91
|
-
|
|
124
|
+
startFind(text, skipRendererEvent) {
|
|
125
|
+
skipRendererEvent || this.#window?.webContents.send('electron-findbar/text-change', text)
|
|
92
126
|
if (this.#lastText = text) {
|
|
93
|
-
this.isOpen() && this.#
|
|
127
|
+
this.isOpen() && this.#findInContent({ findNext: true })
|
|
94
128
|
} else {
|
|
95
129
|
this.stopFind()
|
|
96
130
|
}
|
|
97
131
|
}
|
|
98
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Whether the search should be case-sensitive. If not set, the search will be case-insensitive.
|
|
135
|
+
* @param {boolean} status - Whether the search should be case-sensitive. Default is false.
|
|
136
|
+
* @param {boolean} [skipRendererEvent=false] - Skip update renderer event.
|
|
137
|
+
* @returns {void}
|
|
138
|
+
*/
|
|
139
|
+
matchCase(status, skipRendererEvent) {
|
|
140
|
+
if (this.#matchCase === status) { return }
|
|
141
|
+
this.#matchCase = status
|
|
142
|
+
skipRendererEvent || this.#window?.webContents.send('electron-findbar/match-case-change', this.#matchCase)
|
|
143
|
+
this.#stopFindInContent()
|
|
144
|
+
this.startFind(this.#lastText, skipRendererEvent)
|
|
145
|
+
}
|
|
146
|
+
|
|
99
147
|
/**
|
|
100
148
|
* Select previous match if any.
|
|
149
|
+
* @returns {void}
|
|
101
150
|
*/
|
|
102
151
|
findPrevious() {
|
|
103
152
|
this.#matches.active === 1 && (this.#fixMove = false)
|
|
104
|
-
this.isOpen() && this.#
|
|
153
|
+
this.isOpen() && this.#findInContent({ forward: false })
|
|
105
154
|
}
|
|
106
155
|
|
|
107
156
|
/**
|
|
108
157
|
* Select next match if any.
|
|
158
|
+
* @returns {void}
|
|
109
159
|
*/
|
|
110
160
|
findNext() {
|
|
111
161
|
this.#matches.active === this.#matches.total && (this.#fixMove = true)
|
|
112
|
-
this.isOpen() && this.#
|
|
162
|
+
this.isOpen() && this.#findInContent({ forward: true })
|
|
113
163
|
}
|
|
114
164
|
|
|
115
165
|
/**
|
|
116
|
-
* Stops the find request.
|
|
166
|
+
* Stops the find request and clears selection.
|
|
167
|
+
* @returns {void}
|
|
117
168
|
*/
|
|
118
169
|
stopFind() {
|
|
119
170
|
this.isOpen() && this.#sendMatchesCount(0, 0)
|
|
120
|
-
this.#findableContents.isDestroyed() || this.#
|
|
171
|
+
this.#findableContents.isDestroyed() || this.#stopFindInContent()
|
|
121
172
|
}
|
|
122
173
|
|
|
123
174
|
/**
|
|
124
175
|
* Whether the findbar is opened.
|
|
125
|
-
* @returns {boolean} True
|
|
176
|
+
* @returns {boolean} True if the findbar is open, otherwise false.
|
|
126
177
|
*/
|
|
127
178
|
isOpen() {
|
|
128
179
|
return !!this.#window
|
|
@@ -130,15 +181,16 @@ class Findbar {
|
|
|
130
181
|
|
|
131
182
|
/**
|
|
132
183
|
* Whether the findbar is focused. If the findbar is closed, false will be returned.
|
|
133
|
-
* @returns {boolean} True
|
|
184
|
+
* @returns {boolean} True if the findbar is focused, otherwise false.
|
|
134
185
|
*/
|
|
135
186
|
isFocused() {
|
|
136
187
|
return !!this.#window?.isFocused()
|
|
137
188
|
}
|
|
138
189
|
|
|
139
190
|
/**
|
|
140
|
-
* Whether the findbar is visible to the user in the foreground of the app.
|
|
141
|
-
*
|
|
191
|
+
* Whether the findbar is visible to the user in the foreground of the app.
|
|
192
|
+
* If the findbar is closed, false will be returned.
|
|
193
|
+
* @returns {boolean} True if the findbar is visible, otherwise false.
|
|
142
194
|
*/
|
|
143
195
|
isVisible() {
|
|
144
196
|
return !!this.#window?.isVisible()
|
|
@@ -157,7 +209,9 @@ class Findbar {
|
|
|
157
209
|
* - options.fullscreenable (value: false)
|
|
158
210
|
* - options.webPreferences.nodeIntegration (value: true)
|
|
159
211
|
* - options.webPreferences.contextIsolation (value: false)
|
|
160
|
-
*
|
|
212
|
+
*
|
|
213
|
+
* @param {BrowserWindowConstructorOptions} customOptions - Custom window options.
|
|
214
|
+
* @returns {void}
|
|
161
215
|
*/
|
|
162
216
|
setWindowOptions(customOptions) {
|
|
163
217
|
this.#customOptions = customOptions
|
|
@@ -165,18 +219,32 @@ class Findbar {
|
|
|
165
219
|
|
|
166
220
|
/**
|
|
167
221
|
* Set a window handler capable of changing the findbar window settings after opening.
|
|
168
|
-
* @param {(findbarWindow: BrowserWindow) => void} windowHandler Window handler.
|
|
222
|
+
* @param {(findbarWindow: BrowserWindow) => void} windowHandler - Window handler function.
|
|
223
|
+
* @returns {void}
|
|
169
224
|
*/
|
|
170
225
|
setWindowHandler(windowHandler) {
|
|
171
226
|
this.#windowHandler = windowHandler
|
|
172
227
|
}
|
|
173
228
|
|
|
174
229
|
/**
|
|
175
|
-
* Set a bounds handler to calculate the findbar bounds when the parent resizes.
|
|
176
|
-
* @param {
|
|
230
|
+
* Set a bounds handler to calculate the findbar bounds when the parent window resizes. If width and/or height are not provided, the current value will be used.
|
|
231
|
+
* @param {(parentBounds: Rectangle, findbarBounds: Rectangle) => Rectangle} boundsHandler - Bounds handler function.
|
|
232
|
+
* @returns {void}
|
|
177
233
|
*/
|
|
178
234
|
setBoundsHandler(boundsHandler) {
|
|
179
|
-
this.#
|
|
235
|
+
this.#boundsHandler = boundsHandler
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {Electron.FindInPageOptions} options
|
|
240
|
+
*/
|
|
241
|
+
#findInContent(options) {
|
|
242
|
+
options.matchCase = this.#matchCase
|
|
243
|
+
this.#findableContents.findInPage(this.#lastText, options)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#stopFindInContent() {
|
|
247
|
+
this.#findableContents.stopFindInPage('clearSelection')
|
|
180
248
|
}
|
|
181
249
|
|
|
182
250
|
/**
|
|
@@ -185,25 +253,35 @@ class Findbar {
|
|
|
185
253
|
#registerListeners() {
|
|
186
254
|
const showCascade = () => this.#window.isVisible() || this.#window.show()
|
|
187
255
|
const hideCascade = () => this.#window.isVisible() && this.#window.hide()
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
this.#
|
|
256
|
+
const boundsHandler = () => {
|
|
257
|
+
const currentBounds = this.#window.getBounds()
|
|
258
|
+
const newBounds = this.#boundsHandler(this.#parent.getBounds(), currentBounds)
|
|
259
|
+
if (!newBounds.width) { newBounds.width = currentBounds.width }
|
|
260
|
+
if (!newBounds.height) { newBounds.height = currentBounds.height }
|
|
261
|
+
this.#window.setBounds(newBounds, false)
|
|
191
262
|
}
|
|
192
263
|
|
|
193
|
-
this.#parent.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
264
|
+
if (this.#parent && !this.#parent.isDestroyed()) {
|
|
265
|
+
boundsHandler()
|
|
266
|
+
this.#parent.prependListener('show', showCascade)
|
|
267
|
+
this.#parent.prependListener('hide', hideCascade)
|
|
268
|
+
this.#parent.prependListener('resize', boundsHandler)
|
|
269
|
+
this.#parent.prependListener('move', boundsHandler)
|
|
270
|
+
}
|
|
197
271
|
|
|
198
272
|
this.#window.once('close', () => {
|
|
199
|
-
this.#parent.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
273
|
+
if (this.#parent && !this.#parent.isDestroyed()) {
|
|
274
|
+
this.#parent.off('show', showCascade)
|
|
275
|
+
this.#parent.off('hide', hideCascade)
|
|
276
|
+
this.#parent.off('resize', boundsHandler)
|
|
277
|
+
this.#parent.off('move', boundsHandler)
|
|
278
|
+
}
|
|
203
279
|
this.#window = null
|
|
204
280
|
this.stopFind()
|
|
205
281
|
})
|
|
206
282
|
|
|
283
|
+
this.#window.prependOnceListener('ready-to-show', () => { this.#window.show() })
|
|
284
|
+
|
|
207
285
|
this.#findableContents.prependOnceListener('destroyed', () => { this.close() })
|
|
208
286
|
this.#findableContents.prependListener('found-in-page', (_e, result) => { this.#sendMatchesCount(result.activeMatchOrdinal, result.matches) })
|
|
209
287
|
}
|
|
@@ -259,6 +337,7 @@ class Findbar {
|
|
|
259
337
|
options.movable = options.movable ?? false
|
|
260
338
|
options.acceptFirstMouse = options.acceptFirstMouse ?? true
|
|
261
339
|
options.parent = parent
|
|
340
|
+
options.show = false
|
|
262
341
|
options.frame = false
|
|
263
342
|
options.roundedCorners = true
|
|
264
343
|
options.transparent = process.platform === 'linux'
|
|
@@ -266,19 +345,60 @@ class Findbar {
|
|
|
266
345
|
options.minimizable = false
|
|
267
346
|
options.skipTaskbar = true
|
|
268
347
|
options.fullscreenable = false
|
|
348
|
+
options.autoHideMenuBar = true
|
|
269
349
|
if (!options.webPreferences) { options.webPreferences = {} }
|
|
270
|
-
options.webPreferences.nodeIntegration =
|
|
271
|
-
options.webPreferences.contextIsolation =
|
|
350
|
+
options.webPreferences.nodeIntegration = false
|
|
351
|
+
options.webPreferences.contextIsolation = true
|
|
352
|
+
options.webPreferences.preload = options.webPreferences.preload ?? `${__dirname}/web/preload.js`
|
|
272
353
|
return options
|
|
273
354
|
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get the findbar instance for a given BrowserWindow or WebContents.
|
|
358
|
+
* If no findbar instance exists, it will return a new one linked to the web contents.
|
|
359
|
+
*
|
|
360
|
+
* @overload
|
|
361
|
+
* @param {BrowserWindow} browserWindow Parent window.
|
|
362
|
+
* @param {WebContents} [customWebContents] Custom findable web contents. If not provided, the web contents of the BrowserWindow will be used.
|
|
363
|
+
* @returns {Findbar} The findbar instance if it exists.
|
|
364
|
+
*
|
|
365
|
+
* @overload
|
|
366
|
+
* @param {WebContents} webContents Findable web contents. The parent window will be undefined.
|
|
367
|
+
* @returns {Findbar} The findbar instance if it exists.
|
|
368
|
+
* @throws {Error} If no webContents is provided.
|
|
369
|
+
*
|
|
370
|
+
* @overload
|
|
371
|
+
* @param {BaseWindow} baseWindow Parent window.
|
|
372
|
+
* @param {WebContents} webContents Findable web contents.
|
|
373
|
+
* @returns {Findbar} The findbar instance if it exists.
|
|
374
|
+
* @throws {Error} If no webContents is provided.
|
|
375
|
+
*/
|
|
376
|
+
static from(windowOrWebContents, customWebContents) {
|
|
377
|
+
let webContents = isFindable(windowOrWebContents) ?
|
|
378
|
+
windowOrWebContents : customWebContents ?? windowOrWebContents.webContents
|
|
379
|
+
|
|
380
|
+
return webContents._findbar || new Findbar(windowOrWebContents, customWebContents)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get the findbar instance for a given BrowserWindow or WebContents.
|
|
385
|
+
* @param {BrowserWindow | WebContents} windowOrWebContents
|
|
386
|
+
* @returns {Findbar | undefined} The findbar instance if it exists, otherwise undefined.
|
|
387
|
+
*/
|
|
388
|
+
static fromIfExists(windowOrWebContents) {
|
|
389
|
+
return (isFindable(windowOrWebContents) ? windowOrWebContents : windowOrWebContents.webContents)._findbar
|
|
390
|
+
}
|
|
274
391
|
}
|
|
275
392
|
|
|
393
|
+
const isFindable = (obj) => obj && typeof obj.findInPage === 'function' && typeof obj.stopFindInPage === 'function';
|
|
394
|
+
|
|
276
395
|
/**
|
|
277
396
|
* Define IPC events.
|
|
278
397
|
*/
|
|
279
398
|
(ipc => {
|
|
280
|
-
ipc.handle('electron-findbar/last-
|
|
399
|
+
ipc.handle('electron-findbar/last-state', e => e.sender._findbar.getLastState())
|
|
281
400
|
ipc.on('electron-findbar/input-change', (e, text, skip) => e.sender._findbar.startFind(text, skip))
|
|
401
|
+
ipc.on('electron-findbar/match-case', (e, status, skip) => e.sender._findbar.matchCase(status, skip))
|
|
282
402
|
ipc.on('electron-findbar/previous', e => e.sender._findbar.findPrevious())
|
|
283
403
|
ipc.on('electron-findbar/next', e => e.sender._findbar.findNext())
|
|
284
404
|
ipc.on('electron-findbar/open', e => e.sender._findbar.open())
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electron-findbar",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Chrome-like findbar for your Electron app.",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"remote.js",
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"web/**"
|
|
11
|
+
],
|
|
6
12
|
"scripts": {
|
|
7
13
|
"sample": "electron test/sample.js"
|
|
8
14
|
},
|
package/remote.js
CHANGED
|
@@ -3,19 +3,41 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const Remote = (ipc => ({
|
|
5
5
|
/**
|
|
6
|
-
* Get last queried text.
|
|
7
|
-
* @returns {string}
|
|
6
|
+
* Get last queried text and the "match case" status.
|
|
7
|
+
* @returns {Promise<{ text: string, matchCase: boolean, movable: boolean }>}
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
getLastState: async () => ipc.invoke('electron-findbar/last-state'),
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Change the input value and find it.
|
|
13
|
-
* @param {string} text
|
|
13
|
+
* @param {string} text - The text to search for
|
|
14
14
|
*/
|
|
15
15
|
inputChange: (text) => { ipc.send('electron-findbar/input-change', text) },
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Toggle case sensitive search
|
|
19
|
+
* @param {boolean} value - Whether to match case or not
|
|
20
|
+
*/
|
|
21
|
+
matchCase: (value) => { ipc.send('electron-findbar/match-case', value) },
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Navigate to the previous match
|
|
25
|
+
*/
|
|
16
26
|
previous: () => { ipc.send('electron-findbar/previous') },
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Navigate to the next match
|
|
30
|
+
*/
|
|
17
31
|
next: () => { ipc.send('electron-findbar/next') },
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Open the findbar
|
|
35
|
+
*/
|
|
18
36
|
open: () => { ipc.send('electron-findbar/open') },
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Close the findbar
|
|
40
|
+
*/
|
|
19
41
|
close: () => { ipc.send('electron-findbar/close') },
|
|
20
42
|
})) (require('electron').ipcRenderer)
|
|
21
43
|
|
package/web/app.css
CHANGED
|
@@ -11,28 +11,37 @@ body {
|
|
|
11
11
|
|
|
12
12
|
nav {
|
|
13
13
|
--bg-color: #fff;
|
|
14
|
-
--border: #
|
|
14
|
+
--border: #ddd;
|
|
15
15
|
--color: #626262;
|
|
16
|
-
--input-color: #
|
|
16
|
+
--input-color: #111;
|
|
17
17
|
--btn-hover-color: #ccc;
|
|
18
18
|
--btn-active-color: #bbb;
|
|
19
19
|
--font-family: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
|
20
20
|
--font-size: .75rem;
|
|
21
|
-
--spacing: .
|
|
21
|
+
--spacing: .5rem;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
@media (prefers-color-scheme: dark) {
|
|
25
25
|
nav {
|
|
26
26
|
--bg-color: #1f1f1f;
|
|
27
|
-
--border: #
|
|
27
|
+
--border: #3f3f3f;
|
|
28
28
|
--color: #a9a9aa;
|
|
29
|
-
--input-color: #
|
|
29
|
+
--input-color: #eee;
|
|
30
30
|
--btn-hover-color: #333;
|
|
31
31
|
--btn-active-color: #444;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
#match-case {
|
|
36
|
+
fill: var(--color);
|
|
37
|
+
user-select: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#match-case > svg {
|
|
41
|
+
width: 20px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#previous, #next, #close {
|
|
36
45
|
fill: none;
|
|
37
46
|
stroke: var(--color);
|
|
38
47
|
stroke-width: 2;
|
|
@@ -48,14 +57,15 @@ nav {
|
|
|
48
57
|
padding: var(--spacing);
|
|
49
58
|
background-color: var(--bg-color);
|
|
50
59
|
color: var(--color);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.linux nav {
|
|
51
63
|
border-radius: 10px;
|
|
52
64
|
border: 1px solid var(--border);
|
|
53
|
-
-webkit-app-region: drag;
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
nav
|
|
57
|
-
|
|
58
|
-
margin-right: var(--spacing);
|
|
67
|
+
nav, .btn-group {
|
|
68
|
+
gap: var(--spacing);
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
span, input {
|
|
@@ -64,6 +74,7 @@ span, input {
|
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
span {
|
|
77
|
+
color: var(--input-color);
|
|
67
78
|
user-select: none;
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -74,7 +85,6 @@ input {
|
|
|
74
85
|
font-weight: 500;
|
|
75
86
|
border: none;
|
|
76
87
|
outline: none;
|
|
77
|
-
-webkit-app-region: no-drag;
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
.divider {
|
|
@@ -87,26 +97,45 @@ input {
|
|
|
87
97
|
display: flex;
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
button {
|
|
101
|
+
background-color: transparent;
|
|
102
|
+
border: none;
|
|
91
103
|
border-radius: 50%;
|
|
92
104
|
cursor: default;
|
|
93
105
|
width: 26px;
|
|
94
106
|
height: 26px;
|
|
95
107
|
padding: 3px;
|
|
96
108
|
text-align: center;
|
|
97
|
-
transition: .
|
|
98
|
-
-webkit-app-region: no-drag;
|
|
109
|
+
transition: .2s linear all;
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
|
|
112
|
+
button:hover {
|
|
102
113
|
background-color: var(--btn-hover-color);
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
button:active {
|
|
106
117
|
background-color: var(--btn-active-color);
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
|
|
120
|
+
button:focus {
|
|
121
|
+
outline: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
button.disabled {
|
|
110
125
|
opacity: .4;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#previous.disabled, #next.disabled {
|
|
111
129
|
background-color: transparent !important;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.movable nav {
|
|
133
|
+
-webkit-app-region: drag;
|
|
134
|
+
app-region: drag;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
input, button {
|
|
138
|
+
-webkit-app-region: no-drag;
|
|
139
|
+
app-region: no-drag;
|
|
140
|
+
|
|
112
141
|
}
|
package/web/findbar.html
CHANGED
|
@@ -4,34 +4,40 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<
|
|
7
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; style-src 'self'; default-src 'self'; connect-src 'none'; frame-src 'none'; object-src 'none'; font-src 'self'; img-src 'self'">
|
|
8
|
+
<title>Find in page</title>
|
|
8
9
|
<link rel="stylesheet" href="app.css">
|
|
9
|
-
<body>
|
|
10
|
+
<body class="movable">
|
|
10
11
|
<nav>
|
|
11
|
-
<input id=
|
|
12
|
+
<input id="input" type="text" spellcheck="false">
|
|
12
13
|
<span id="matches"></span>
|
|
14
|
+
<button id="match-case" class="disabled" title="Match case">
|
|
15
|
+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" stroke="none">
|
|
16
|
+
<text x="4" y="15" font-size="14" font-family="monospace" font-weight="bold">A</text>
|
|
17
|
+
<text x="12" y="19" font-size="14" font-family="monospace">a</text>
|
|
18
|
+
</svg>
|
|
19
|
+
</button>
|
|
13
20
|
<div class="divider"></div>
|
|
14
21
|
<div class="btn-group">
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
<button id="previous" class="disabled" title="Previous">
|
|
16
24
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
17
25
|
<path d="M7 15L12 10L17 15"/>
|
|
18
26
|
</svg>
|
|
19
|
-
</
|
|
20
|
-
<
|
|
27
|
+
</button>
|
|
28
|
+
<button id="next" class="disabled" title="Next">
|
|
21
29
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
22
30
|
<path d="M7 10L12 15L17 10"/>
|
|
23
31
|
</svg>
|
|
24
|
-
</
|
|
25
|
-
<
|
|
32
|
+
</button>
|
|
33
|
+
<button id="close" title="Close">
|
|
26
34
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
27
35
|
<path d="M7 17.5L12 12.5L17 17.5"/>
|
|
28
36
|
<path d="M7 7.5L12 12.5L17 7.5"/>
|
|
29
37
|
</svg>
|
|
30
|
-
</
|
|
38
|
+
</button>
|
|
31
39
|
</div>
|
|
32
40
|
</nav>
|
|
33
|
-
|
|
34
|
-
<script src="app.js"></script>
|
|
35
41
|
</body>
|
|
36
42
|
|
|
37
43
|
</html>
|
package/web/preload.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const $remote = (ipc => ({
|
|
2
|
+
getLastState: async () => ipc.invoke('electron-findbar/last-state'),
|
|
3
|
+
inputChange: (value) => { ipc.send('electron-findbar/input-change', value, true) },
|
|
4
|
+
matchCase: (value) => { ipc.send('electron-findbar/match-case', value, true) },
|
|
5
|
+
previous: () => { ipc.send('electron-findbar/previous') },
|
|
6
|
+
next: () => { ipc.send('electron-findbar/next') },
|
|
7
|
+
close: () => { ipc.send('electron-findbar/close') },
|
|
8
|
+
onMatchesChange: (listener) => { ipc.on('electron-findbar/matches', listener) },
|
|
9
|
+
onInputFocus: (listener) => { ipc.on('electron-findbar/input-focus', listener) },
|
|
10
|
+
onTextChange: (listener) => { ipc.on('electron-findbar/text-change', listener) },
|
|
11
|
+
onMatchCaseChange: (listener) => { ipc.on('electron-findbar/match-case-change', listener) }
|
|
12
|
+
})) (require('electron').ipcRenderer)
|
|
13
|
+
|
|
14
|
+
let canRequest = true, canMove = false
|
|
15
|
+
|
|
16
|
+
function inputChange(e) {
|
|
17
|
+
canRequest = false
|
|
18
|
+
$remote.inputChange(e.target.value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function matchCaseChange(btn) {
|
|
22
|
+
const newStatus = buttonIsDisabled(btn)
|
|
23
|
+
toggleButton(btn, newStatus)
|
|
24
|
+
$remote.matchCase(newStatus)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function move(next) {
|
|
28
|
+
if (canRequest && canMove) {
|
|
29
|
+
canRequest = false
|
|
30
|
+
next ? $remote.next() : $remote.previous()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toggleButton(btn, status) {
|
|
35
|
+
btn.classList[status ? 'remove' : 'add']('disabled')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buttonIsDisabled(btn) {
|
|
39
|
+
return btn.classList.contains('disabled')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
43
|
+
const inputEl = document.getElementById('input')
|
|
44
|
+
const matchCaseBtn = document.getElementById('match-case')
|
|
45
|
+
const previousBtn = document.getElementById('previous')
|
|
46
|
+
const nextBtn = document.getElementById('next')
|
|
47
|
+
const closeBtn = document.getElementById('close')
|
|
48
|
+
const matchesEl = document.getElementById('matches')
|
|
49
|
+
const moveBtns = [previousBtn, nextBtn]
|
|
50
|
+
|
|
51
|
+
matchCaseBtn.addEventListener('click', () => matchCaseChange(matchCaseBtn))
|
|
52
|
+
previousBtn.addEventListener('click', () => move(false))
|
|
53
|
+
nextBtn.addEventListener('click', () => move(true))
|
|
54
|
+
closeBtn.addEventListener('click', () => $remote.close())
|
|
55
|
+
inputEl.addEventListener('input', inputChange)
|
|
56
|
+
|
|
57
|
+
$remote.onTextChange((_, text) => { inputEl.value = text })
|
|
58
|
+
|
|
59
|
+
$remote.onInputFocus(() => {
|
|
60
|
+
inputEl.setSelectionRange(0, inputEl.value.length)
|
|
61
|
+
inputEl.focus()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
$remote.onMatchCaseChange((_, status) => { console.log('Match case:', status);toggleButton(matchCaseBtn, status) })
|
|
65
|
+
|
|
66
|
+
$remote.onMatchesChange((_, m) => {
|
|
67
|
+
canRequest = true
|
|
68
|
+
matchesEl.innerText = inputEl.value ? m.active + '/' + m.total : ''
|
|
69
|
+
for (var moveBtn of moveBtns) { toggleButton(moveBtn, canMove = m.total > 1) }
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const lastState = await $remote.getLastState()
|
|
73
|
+
inputEl.value = lastState.text || ''
|
|
74
|
+
lastState.movable || document.body.classList.remove('movable')
|
|
75
|
+
if (process.platform === 'linux') {
|
|
76
|
+
document.body.classList.add('linux')
|
|
77
|
+
}
|
|
78
|
+
toggleButton(matchCaseBtn, lastState.matchCase)
|
|
79
|
+
$remote.inputChange(inputEl.value)
|
|
80
|
+
inputEl.setSelectionRange(0, inputEl.value.length)
|
|
81
|
+
inputEl.focus()
|
|
82
|
+
})
|
package/.vscode/launch.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.2.0",
|
|
3
|
-
"configurations": [
|
|
4
|
-
{
|
|
5
|
-
"name": "Debug Main Process",
|
|
6
|
-
"type": "node",
|
|
7
|
-
"request": "launch",
|
|
8
|
-
"cwd": "${workspaceFolder}",
|
|
9
|
-
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
|
10
|
-
"windows": {
|
|
11
|
-
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
|
12
|
-
},
|
|
13
|
-
"args" : ["test/sample.js"],
|
|
14
|
-
"outputCapture": "std"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
package/test/sample.html
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Sample</title>
|
|
5
|
-
</head>
|
|
6
|
-
<body>
|
|
7
|
-
<input type="text" oninput="$remote.inputChange(event.target.value)">
|
|
8
|
-
<button onclick="$remote.open()">Open</button>
|
|
9
|
-
<button onclick="$remote.previous()">Previous</button>
|
|
10
|
-
<button onclick="$remote.next()">Next</button>
|
|
11
|
-
<button onclick="$remote.close()">Close</button>
|
|
12
|
-
<br>
|
|
13
|
-
<span>
|
|
14
|
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec volutpat massa et suscipit tincidunt. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam dictum massa id sapien tristique, in venenatis neque sollicitudin. Fusce accumsan augue arcu, sed rhoncus libero pretium vitae. Phasellus sed imperdiet ante. Maecenas ultrices, elit vitae aliquet tincidunt, elit enim maximus libero, id lacinia ex enim consequat justo. Sed sodales tristique augue sed maximus. Integer tincidunt mi ac arcu tempus, sed accumsan lorem bibendum. Sed in dictum nibh. Pellentesque posuere dui pulvinar sodales scelerisque. Ut nisi magna, vulputate ornare bibendum pharetra, accumsan sed tortor. Proin rhoncus interdum tincidunt. Suspendisse diam eros, ultrices eu volutpat in, vestibulum quis nunc.
|
|
15
|
-
|
|
16
|
-
Sed sit amet dapibus eros. Quisque porttitor mi a nisl pretium molestie. Praesent tellus dui, vehicula a ex sed, faucibus congue mauris. Cras dictum, sapien tempus consequat luctus, dolor magna vestibulum risus, nec blandit diam nulla eget est. Curabitur vitae posuere dolor, accumsan vulputate felis. Cras eget iaculis ante. Nulla velit felis, aliquet vitae convallis nec, vehicula et nunc. Fusce dapibus vel eros non viverra. Fusce elit arcu, tempus eu enim in, rutrum mattis justo.
|
|
17
|
-
|
|
18
|
-
Cras justo tellus, imperdiet et felis sed, iaculis varius lacus. Pellentesque posuere feugiat nisl, eu vulputate tortor. Proin volutpat tortor erat, feugiat pretium justo aliquam non. Maecenas nec neque ultricies diam rhoncus ullamcorper a vitae mauris. Integer ultricies euismod leo, nec facilisis diam volutpat et. Nulla eleifend ante egestas, imperdiet elit at, malesuada velit. Donec tincidunt eleifend libero. Integer congue pharetra scelerisque. In egestas lacus erat. Quisque aliquam massa lectus, eu semper massa ornare auctor. Cras sit amet auctor sem. Curabitur vitae tellus eu risus ultrices accumsan. Curabitur egestas eu lorem et efficitur. Sed nec turpis felis.
|
|
19
|
-
|
|
20
|
-
Suspendisse vel euismod ante. Nunc sagittis quam ut gravida pulvinar. Sed at semper nisl, eu porttitor ante. Cras vitae dolor massa. Fusce tincidunt turpis at egestas pharetra. Donec vitae vestibulum ante. Cras erat dolor, finibus vitae auctor vel, varius dictum arcu. Nam porta arcu consectetur, posuere tellus at, laoreet metus. Ut faucibus tincidunt mi placerat fermentum.
|
|
21
|
-
|
|
22
|
-
Aliquam ut pellentesque tellus, quis vulputate nisl. Phasellus vitae blandit nunc, eu sodales velit. Duis enim tellus, faucibus id arcu vitae, consectetur tempus mauris. Praesent commodo commodo dolor non malesuada. Donec sed dolor eget arcu tincidunt efficitur sit amet vel nisi. Sed consectetur tincidunt molestie. Nam at magna a odio rhoncus convallis id eu nunc. Nam luctus ut leo et viverra. Proin tempor libero vitae arcu laoreet, sed pharetra sapien rutrum. Nam nunc orci, aliquet sit amet dignissim nec, aliquet quis quam. Nulla facilisi.
|
|
23
|
-
</span>
|
|
24
|
-
<script>
|
|
25
|
-
const $remote = require('../remote')
|
|
26
|
-
</script>
|
|
27
|
-
</body>
|
|
28
|
-
</html>
|
package/test/sample.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const { BrowserWindow, app, Menu, MenuItem } = require('electron')
|
|
2
|
-
const Findbar = require('../index')
|
|
3
|
-
|
|
4
|
-
app.whenReady().then(() => {
|
|
5
|
-
const window = setupWindow()
|
|
6
|
-
const findbar = setupFindbar(window)
|
|
7
|
-
setupApplicationMenu(findbar)
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
function setupWindow() {
|
|
11
|
-
const window = new BrowserWindow({
|
|
12
|
-
webPreferences: {
|
|
13
|
-
nodeIntegration: true,
|
|
14
|
-
contextIsolation: false
|
|
15
|
-
}
|
|
16
|
-
})
|
|
17
|
-
window.loadFile(`${__dirname}/sample.html`)
|
|
18
|
-
return window
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function setupFindbar(window) {
|
|
22
|
-
const findbar = new Findbar(window)
|
|
23
|
-
findbar.setWindowOptions({ movable: true, resizable: true })
|
|
24
|
-
findbar.setWindowHandler(win => { /* handle the findbar window */ })
|
|
25
|
-
return findbar
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function setupApplicationMenu(findbar) {
|
|
29
|
-
const appMenu = Menu.getApplicationMenu()
|
|
30
|
-
appMenu.append(new MenuItem({ label: 'Findbar', submenu: [
|
|
31
|
-
{ label: 'Open', click: () => findbar.open(), accelerator: 'CommandOrControl+F' },
|
|
32
|
-
{ label: 'Close', click: () => findbar.isOpen() && findbar.close(), accelerator: 'Esc' },
|
|
33
|
-
{ role: 'toggleDevTools', accelerator: 'CommandOrControl+Shift+I' },
|
|
34
|
-
{ label: 'Test input propagation', click: () => {
|
|
35
|
-
let count = 0
|
|
36
|
-
setInterval(() => {
|
|
37
|
-
findbar.startFind('count: ' + count++)
|
|
38
|
-
findbar.startFind('cannot show this', true)
|
|
39
|
-
}, 1000)
|
|
40
|
-
}}
|
|
41
|
-
]}))
|
|
42
|
-
Menu.setApplicationMenu(appMenu)
|
|
43
|
-
}
|
package/web/app.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
const $remote = (ipc => ({
|
|
2
|
-
getLastText: async () => ipc.invoke('electron-findbar/last-text'),
|
|
3
|
-
inputChange: (value) => { ipc.send('electron-findbar/input-change', value, true) },
|
|
4
|
-
previous: () => { ipc.send('electron-findbar/previous') },
|
|
5
|
-
next: () => { ipc.send('electron-findbar/next') },
|
|
6
|
-
close: () => { ipc.send('electron-findbar/close') },
|
|
7
|
-
onMatchesChange: (listener) => { ipc.on('electron-findbar/matches', listener) },
|
|
8
|
-
onInputFocus: (listener) => { ipc.on('electron-findbar/input-focus', listener) },
|
|
9
|
-
onTextChange: (listener) => { ipc.on('electron-findbar/text-change', listener) }
|
|
10
|
-
})) (require('electron').ipcRenderer)
|
|
11
|
-
|
|
12
|
-
let canRequest = true, canMove = false
|
|
13
|
-
|
|
14
|
-
function inputChange(e) {
|
|
15
|
-
canRequest = false
|
|
16
|
-
$remote.inputChange(e.target.value)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function move(next) {
|
|
20
|
-
if (canRequest && canMove) {
|
|
21
|
-
canRequest = false
|
|
22
|
-
next ? $remote.next() : $remote.previous()
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
document.addEventListener('DOMContentLoaded', async () => {
|
|
27
|
-
const inputEl = document.getElementById('input')
|
|
28
|
-
const matchesEl = document.getElementById('matches')
|
|
29
|
-
const moveBtns = [...document.getElementsByClassName('disabled')]
|
|
30
|
-
|
|
31
|
-
$remote.onMatchesChange((_, m) => {
|
|
32
|
-
canRequest = true
|
|
33
|
-
matchesEl.innerText = inputEl.value ? m.active + '/' + m.total : ''
|
|
34
|
-
|
|
35
|
-
for (var moveBtn of moveBtns) {
|
|
36
|
-
(canMove = m.total > 1) ?
|
|
37
|
-
moveBtn.classList.remove('disabled') :
|
|
38
|
-
moveBtn.classList.add('disabled')
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
$remote.onInputFocus(() => {
|
|
43
|
-
inputEl.setSelectionRange(0, inputEl.value.length)
|
|
44
|
-
inputEl.focus()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
$remote.onTextChange((_, text) => { inputEl.value = text })
|
|
48
|
-
|
|
49
|
-
inputEl.value = await $remote.getLastText()
|
|
50
|
-
$remote.inputChange(inputEl.value)
|
|
51
|
-
inputEl.setSelectionRange(0, inputEl.value.length)
|
|
52
|
-
inputEl.focus()
|
|
53
|
-
})
|