electron-findbar 0.1.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.
@@ -0,0 +1,17 @@
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" : ["sample.js"],
14
+ "outputCapture": "std"
15
+ }
16
+ ]
17
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Emerson Capuchi Romaneli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ ### Documentation Improvements and Report
2
+
3
+ ---
4
+
5
+ <p align='center'>
6
+ <a href="https://github.com/ECRomaneli/handbook" style='text-decoration:none'>
7
+ <img src="https://i.postimg.cc/0QR0s0Z1/findbar-light.png" alt='Findbar Light Theme'>
8
+ <img src="https://i.postimg.cc/LXtB6g0Y/findbar-dark.png" alt='Findbar Dark Theme'>
9
+ </a>
10
+ </p>
11
+ <p align='center'>
12
+ Chrome-like findbar for your Electron application
13
+ </p>
14
+ <p align='center'>
15
+ <a href="https://github.com/ECRomaneli/electron-findbar/tags" style='text-decoration:none'>
16
+ <img src="https://img.shields.io/github/v/tag/ecromaneli/electron-findbar?label=version&sort=semver&style=for-the-badge" alt="Version">
17
+ </a>
18
+ <a href="https://github.com/ECRomaneli/electron-findbar/commits/master" style='text-decoration:none'>
19
+ <img src="https://img.shields.io/github/last-commit/ecromaneli/electron-findbar?style=for-the-badge" alt="Last Commit">
20
+ </a>
21
+ <a href="https://github.com/ECRomaneli/electron-findbar/blob/master/LICENSE" style='text-decoration:none'>
22
+ <img src="https://img.shields.io/github/license/ecromaneli/electron-findbar?style=for-the-badge" alt="License">
23
+ </a>
24
+ <a href="https://github.com/ECRomaneli/electron-findbar/issues" style='text-decoration:none'>
25
+ <img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=for-the-badge" alt="Contributions Welcome">
26
+ </a>
27
+ </p>
28
+
29
+ ## Installation
30
+
31
+ Install the `electron-findbar` package via npm:
32
+
33
+ ```sh
34
+ npm install electron-findbar
35
+ ```
36
+
37
+ ## Overview
38
+
39
+ The `electron-findbar` is a `BrowserWindow` component designed to emulate the Chrome findbar layout, leveraging the `webContents.findInPage` method to navigate through matches. Inter-process communication (IPC) is used for interaction between the `main` and `renderer` processes.
40
+
41
+ ### Memory Usage
42
+
43
+ To optimize memory usage, the Findbar window is created only when the findbar is open. The implementation is lightweight, including only essential code.
44
+
45
+ ## Usage
46
+
47
+ All public methods are documented with JSDoc and can be referenced during import.
48
+
49
+ ### Importing the Findbar
50
+
51
+ To import the Findbar class:
52
+
53
+ ```js
54
+ const { Findbar } = require('electron-findbar');
55
+ ```
56
+
57
+ ### Creating the Findbar Instance
58
+
59
+ 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 searchable content:
60
+
61
+ ```js
62
+ const findbar = new Findbar(browserWindow);
63
+ ```
64
+
65
+ 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 searchable content:
66
+
67
+ ```js
68
+ const findbar = new Findbar(baseWindow, customWebContents);
69
+ ```
70
+
71
+ ### Configuring the Findbar
72
+
73
+ You can customize the Findbar window options using the `setWindowOptions` method:
74
+
75
+ ```js
76
+ findbar.setWindowOptions({ movable: true, resizable: true, alwaysOnTop: true });
77
+ ```
78
+
79
+ To handle the Findbar window directly after it is opened, use the `setWindowHandler` method:
80
+
81
+ ```js
82
+ findbar.setWindowHandler(win => {
83
+ win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
84
+ });
85
+ ```
86
+
87
+ ### Opening the Findbar
88
+
89
+ The Findbar is a child window of the `BaseWindow` passed during construction. It will follow the relative movement of the parent window and must be visible for the Findbar to display:
90
+
91
+ ```js
92
+ findbar.open();
93
+ ```
94
+
95
+ ### Finding Text in the Page
96
+
97
+ 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:
98
+
99
+ ```js
100
+ /**
101
+ * Retrieve the last queried value.
102
+ */
103
+ getLastValue();
104
+
105
+ /**
106
+ * Initiate a request to find all matches for the specified text on the page.
107
+ * @param {string} text - The text to search for.
108
+ */
109
+ startFind(text);
110
+
111
+ /**
112
+ * Select the previous match, if available.
113
+ */
114
+ findPrevious();
115
+
116
+ /**
117
+ * Select the next match, if available.
118
+ */
119
+ findNext();
120
+
121
+ /**
122
+ * Stop the find request.
123
+ */
124
+ stopFind();
125
+ ```
126
+
127
+ ### Closing the Findbar
128
+
129
+ When the Findbar is closed, its window is destroyed to free memory resources. Use the following method to close the Findbar:
130
+
131
+ ```js
132
+ findbar.close();
133
+ ```
134
+
135
+ A new internal window will be created the next time the `open` method is called. There is no need to instantiate another Findbar for the same parent window.
136
+
137
+ ### Quick Example
138
+
139
+ Here is a quick example demonstrating how to use the `electron-findbar`:
140
+
141
+ ```js
142
+ const { app, BrowserWindow } = require('electron');
143
+ const { Findbar } = require('electron-findbar');
144
+
145
+ app.whenReady().then(() => {
146
+ const window = new BrowserWindow();
147
+ window.loadURL('https://github.com/ECRomaneli/electron-findbar');
148
+
149
+ // Create and configure the Findbar object
150
+ const findbar = new Findbar(window);
151
+
152
+ // [OPTIONAL] Customize window options
153
+ findbar.setWindowOptions({ movable: true, resizable: true });
154
+
155
+ // [OPTIONAL] Handle the window object when the Findbar is opened
156
+ findbar.setWindowHandler(win => {
157
+ win.webContents.openDevTools();
158
+ });
159
+
160
+ // Open the Findbar
161
+ findbar.open();
162
+ });
163
+ ```
164
+
165
+ ## Notes
166
+
167
+ There are some intentional differences from the Chrome findbar, such as the horizontal margins of the divider and the input text, which has been replaced by a search input to include a clear button (the "x" on the right side).
168
+
169
+ ## Author
170
+
171
+ Created by [Emerson Capuchi Romaneli](https://github.com/ECRomaneli) (@ECRomaneli).
172
+
173
+ ## License
174
+
175
+ This project is licensed under the [MIT License](https://github.com/ECRomaneli/handbook/blob/master/LICENSE).
176
+
package/index.js ADDED
@@ -0,0 +1,241 @@
1
+ const { BaseWindow, BrowserWindow, WebContents, BrowserWindowConstructorOptions, Rectangle } = require('electron')
2
+ const path = require('node:path')
3
+
4
+ class Findbar {
5
+ /** @type {BaseWindow} */
6
+ #parent
7
+
8
+ /** @type {BrowserWindow} */
9
+ #window
10
+
11
+ /** @type {WebContents} */
12
+ #searchableContents
13
+
14
+ /** */
15
+ #matches
16
+
17
+ /** @type {(findbarWindow: BrowserWindow) => void} */
18
+ #windowHandler
19
+
20
+ /** @type {BrowserWindowConstructorOptions} */
21
+ #customOptions
22
+
23
+ /** @type {string} */
24
+ #lastValue = ''
25
+
26
+ /**
27
+ * Prepare the findbar.
28
+ * @param {BaseWindow} parent Parent window.
29
+ * @param {WebContents | void} webContents Searchable web contents. If not set and the parent is a BrowserWindow,
30
+ * the web contents of the parent will be used. Otherwise, an error will be triggered.
31
+ */
32
+ constructor (parent, webContents) {
33
+ this.#parent = parent
34
+ this.#searchableContents = webContents ?? parent.webContents
35
+
36
+ if (!this.#searchableContents) {
37
+ throw new Error('There are no searchable web contents.')
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Open the findbar.
43
+ */
44
+ open() {
45
+ if (this.#window) {
46
+ this.#focusInput()
47
+ return
48
+ }
49
+ this.#window = new BrowserWindow(Findbar.#mergeStandardOptions(this.#customOptions, this.#parent))
50
+ this.#window.webContents.findbar = this
51
+
52
+ this.#registerListeners()
53
+ this.#setDefaultPosition(this.#parent.getBounds())
54
+
55
+ this.#windowHandler && this.#windowHandler(this.#window)
56
+
57
+ this.#window.loadFile(path.join('web', 'findbar.html'))
58
+ }
59
+
60
+ /**
61
+ * Close the findbar.
62
+ */
63
+ close() {
64
+ this.#window?.close()
65
+ }
66
+
67
+ /**
68
+ * Get last queried value.
69
+ */
70
+ getLastValue() {
71
+ return this.#lastValue
72
+ }
73
+
74
+ /**
75
+ * Starts a request to find all matches for the text in the page.
76
+ * @param {string} text Value to find in page.
77
+ */
78
+ startFind(text) {
79
+ if (this.#lastValue = text) {
80
+ this.#searchableContents.findInPage(this.#lastValue, { findNext: true })
81
+ } else {
82
+ this.stopFind()
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Select previous match if any.
88
+ */
89
+ findPrevious() {
90
+ this.#searchableContents.findInPage(this.#lastValue, { forward: false })
91
+ }
92
+
93
+ /**
94
+ * Select next match if any.
95
+ */
96
+ findNext() {
97
+ this.#searchableContents.findInPage(this.#lastValue, { forward: true })
98
+ }
99
+
100
+ /**
101
+ * Stops the find request.
102
+ */
103
+ stopFind() {
104
+ this.isOpen() && this.#sendMatchesCount(0, 0)
105
+ this.#searchableContents.isDestroyed() || this.#searchableContents.stopFindInPage("clearSelection")
106
+ }
107
+
108
+ /**
109
+ * Return if the findbar is open or not.
110
+ * @returns {boolean} True, if the findbar is open. Otherwise, false.
111
+ */
112
+ isOpen() {
113
+ return !!this.#window
114
+ }
115
+
116
+ /**
117
+ * Provides a customized set of options to findbar window before open. Note
118
+ * that the options below are necessary for the correct functioning and cannot
119
+ * be overridden:
120
+ * - options.parent (value: parentWindow)
121
+ * - options.frame (value: false)
122
+ * - options.transparent (value: true)
123
+ * - options.maximizable (value: false)
124
+ * - options.minimizable (value: false)
125
+ * - options.skipTaskbar (value: true)
126
+ * - options.fullscreenable (value: false)
127
+ * - options.webPreferences.nodeIntegration (value: true)
128
+ * - options.webPreferences.contextIsolation (value: false)
129
+ * @param {BrowserWindowConstructorOptions} customOptions Custom window options.
130
+ */
131
+ setWindowOptions(customOptions) {
132
+ this.#customOptions = customOptions
133
+ }
134
+
135
+ /**
136
+ * Set a window handler capable of changing the findbar window settings after opening.
137
+ * @param {(findbarWindow: BrowserWindow) => void} windowHandler Window handler.
138
+ */
139
+ setWindowHandler(windowHandler) {
140
+ this.#windowHandler = windowHandler
141
+ }
142
+
143
+ /**
144
+ * Merge custom, defaults, and fixed options.
145
+ * @param {Electron.BrowserWindowConstructorOptions} options Custom options.
146
+ * @param {BaseWindow | void} parent Parent window, if any.
147
+ * @returns {Electron.BrowserWindowConstructorOptions} Merged options.
148
+ */
149
+ static #mergeStandardOptions(options, parent) {
150
+ if (!options) { options = {} }
151
+ options.width = options.width ?? 372
152
+ options.height = options.height ?? 52
153
+ options.resizable = options.resizable ?? false
154
+ options.movable = options.movable ?? false
155
+ options.parent = parent
156
+ options.frame = false
157
+ options.transparent = true
158
+ options.maximizable = false
159
+ options.minimizable = false
160
+ options.skipTaskbar = true
161
+ options.fullscreenable = false
162
+ if (!options.webPreferences) { options.webPreferences = {} }
163
+ options.webPreferences.nodeIntegration = true
164
+ options.webPreferences.contextIsolation = false
165
+ return options
166
+ }
167
+
168
+ /**
169
+ * Register all event listeners.
170
+ */
171
+ #registerListeners() {
172
+ const showCascade = () => this.#window.isVisible() || this.#window.show()
173
+ const hideCascade = () => this.#window.isVisible() && this.#window.hide()
174
+
175
+ this.#parent.prependListener('show', showCascade)
176
+ this.#parent.prependListener('hide', hideCascade)
177
+
178
+ this.#window.once('closed', () => {
179
+ this.#parent.off('show', showCascade)
180
+ this.#parent.off('hide', hideCascade)
181
+ this.#window = null
182
+ this.stopFind()
183
+ })
184
+
185
+ this.#searchableContents.prependOnceListener('destroyed', () => { this.close() })
186
+ this.#searchableContents.prependListener('found-in-page', (_e, result) => {
187
+ this.#matches = result
188
+ this.#sendMatchesCount(result.activeMatchOrdinal, result.matches)
189
+ })
190
+ }
191
+
192
+ /**
193
+ * Set default findbar position.
194
+ * @param {Rectangle} parentBounds
195
+ */
196
+ #setDefaultPosition(parentBounds) {
197
+ const s = this.#window.getSize()
198
+ this.#window.setBounds({
199
+ x: parentBounds.x + parentBounds.width - s[0] - 20,
200
+ y: parentBounds.y - ((s[1] / 4) | 0)
201
+ })
202
+ }
203
+
204
+ /**
205
+ * Send to renderer the active match and the total.
206
+ * @param {number} active Active match.
207
+ * @param {number} total Total matches.
208
+ */
209
+ #sendMatchesCount(active, total) {
210
+ this.#window.webContents.send('electron-findbar/matches', { active, total })
211
+ }
212
+
213
+ /**
214
+ * Select input text.
215
+ */
216
+ #focusInput() {
217
+ this.#window.webContents.send('electron-findbar/input-focus')
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Define IPC events.
223
+ */
224
+ (({ ipcMain }) => {
225
+ ipcMain.handle('electron-findbar/initial-input', e => {
226
+ const findbar = e.sender.findbar
227
+ findbar.startFind(findbar.getLastValue())
228
+ return findbar.getLastValue()
229
+ })
230
+
231
+ ipcMain.on('electron-findbar/input-change', (e, value) => e.sender.findbar.startFind(value))
232
+ ipcMain.on('electron-findbar/previous', e => e.sender.findbar.findPrevious())
233
+ ipcMain.on('electron-findbar/next', e => e.sender.findbar.findNext())
234
+ ipcMain.on('electron-findbar/close', e => {
235
+ const findbar = e.sender.findbar
236
+ findbar.stopFind()
237
+ findbar.close()
238
+ })
239
+ }) (require('electron'))
240
+
241
+ module.exports = { Findbar }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "electron-findbar",
3
+ "version": "0.1.0",
4
+ "description": "Chrome-like findbar for your Electron app.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "sample": "electron sample.js",
8
+ "test": "node test.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/ECRomaneli/electron-findbar.git"
13
+ },
14
+ "keywords": [
15
+ "findbar",
16
+ "electron-findbar",
17
+ "electron",
18
+ "chrome-findbar",
19
+ "search"
20
+ ],
21
+ "author": "ECRomaneli",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/ECRomaneli/electron-findbar/issues"
25
+ },
26
+ "homepage": "https://github.com/ECRomaneli/electron-findbar#readme",
27
+ "engines": {
28
+ "node": ">=12.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "electron": ">=12.0.0"
32
+ }
33
+ }
package/sample.js ADDED
@@ -0,0 +1,23 @@
1
+ const { BrowserWindow, app, Menu } = require('electron')
2
+ const { Findbar } = require('./index')
3
+
4
+ app.whenReady().then(() => {
5
+ const window = new BrowserWindow()
6
+ window.loadURL('https://github.com/ECRomaneli/electron-findbar#readme')
7
+
8
+ const findbar = new Findbar(window)
9
+ findbar.setWindowOptions({ movable: !true, resizable: true })
10
+ findbar.setWindowHandler(win => {
11
+ win.webContents.openDevTools()
12
+ })
13
+ findbar.open()
14
+
15
+ const contextMenu = Menu.buildFromTemplate([
16
+ { role: 'separator' },
17
+ { label: 'Open findbar', click: () => findbar.open(), accelerator: 'CommandOrControl+F' },
18
+ { label: 'Close findbar', click: () => findbar.isOpen() && findbar.close(), accelerator: 'Esc', registerAccelerator: true, acceleratorWorksWhenHidden: true }
19
+ ])
20
+
21
+ Menu.setApplicationMenu(contextMenu)
22
+
23
+ })
package/test.js ADDED
File without changes
package/web/app.css ADDED
@@ -0,0 +1,106 @@
1
+ *, *:before, *:after {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ padding: 0;
8
+ font-weight: normal;
9
+ height: 100vh;
10
+ }
11
+
12
+ nav {
13
+ --bg-color: #fff;
14
+ --border: #eee;
15
+ --color: #626262;
16
+ --input-color: #1f1f1f;
17
+ --btn-hover-color: #ccc;
18
+ --btn-active-color: #bbb;
19
+ }
20
+
21
+ @media (prefers-color-scheme: dark) {
22
+ nav {
23
+ --bg-color: #1f1f1f;
24
+ --border: #2f2f2f;
25
+ --color: #a9a9aa;
26
+ --input-color: #e3e3e3;
27
+ --btn-hover-color: #333;
28
+ --btn-active-color: #444;
29
+ }
30
+ }
31
+
32
+ svg {
33
+ fill: none;
34
+ stroke: var(--color);
35
+ stroke-width: 2;
36
+ stroke-linecap: round;
37
+ stroke-linejoin: round;
38
+ }
39
+
40
+ nav {
41
+ display: flex;
42
+ align-items: center;
43
+ width: 100%;
44
+ height: 100%;
45
+ padding: .75rem;
46
+ 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";
47
+ background-color: var(--bg-color);
48
+ color: var(--color);
49
+ font-size: .75rem;
50
+ border-radius: 10px;
51
+ border: 1px solid var(--border);
52
+ -webkit-app-region: drag;
53
+ }
54
+
55
+ nav > span,
56
+ nav > .divider {
57
+ margin-right: .75rem;
58
+ user-select: none;
59
+ }
60
+
61
+ input {
62
+ width: 100%;
63
+ background-color: transparent;
64
+ color: var(--input-color);
65
+ border: none;
66
+ outline: none;
67
+ -webkit-app-region: no-drag;
68
+ }
69
+
70
+ .divider {
71
+ width: 2px;
72
+ height: 100%;
73
+ background-color: var(--btn-hover-color);
74
+ }
75
+
76
+ .btn-group {
77
+ display: flex;
78
+ }
79
+
80
+ .btn-group > :not(:last-child) {
81
+ margin-right: .5rem;
82
+ }
83
+
84
+ .btn-group > div {
85
+ border-radius: 50%;
86
+ cursor: default;
87
+ width: 26px;
88
+ height: 26px;
89
+ padding: 3px;
90
+ text-align: center;
91
+ transition: .1s linear all;
92
+ -webkit-app-region: no-drag;
93
+ }
94
+
95
+ .btn-group > div:hover {
96
+ background-color: var(--btn-hover-color);
97
+ }
98
+
99
+ .btn-group > div:active {
100
+ background-color: var(--btn-active-color);
101
+ }
102
+
103
+ .btn-group > .disabled {
104
+ opacity: .4;
105
+ background-color: transparent !important;
106
+ }
package/web/app.js ADDED
@@ -0,0 +1,51 @@
1
+ const { ipcRenderer } = require('electron')
2
+
3
+ const $remote = {
4
+ getInitialInput: async () => ipcRenderer.invoke('electron-findbar/initial-input'),
5
+ inputChange: (value) => { ipcRenderer.send('electron-findbar/input-change', value) },
6
+ previous: () => { ipcRenderer.send('electron-findbar/previous') },
7
+ next: () => { ipcRenderer.send('electron-findbar/next') },
8
+ close: () => { ipcRenderer.send('electron-findbar/close') },
9
+ onMatchesChange: (listener) => { ipcRenderer.on('electron-findbar/matches', listener) },
10
+ onInputFocus: (listener) => { ipcRenderer.on('electron-findbar/input-focus', listener) }
11
+ }
12
+
13
+ let canRequest = true, canMove = false
14
+
15
+ function inputChange(e) {
16
+ canRequest = false
17
+ $remote.inputChange(e.target.value)
18
+ }
19
+
20
+ function move(next) {
21
+ if (canRequest && canMove) {
22
+ canRequest = false
23
+ next ? $remote.next() : $remote.previous()
24
+ }
25
+ }
26
+
27
+ document.addEventListener('DOMContentLoaded', async () => {
28
+ const inputEl = document.getElementById('input')
29
+ const matchesEl = document.getElementById('matches')
30
+ const moveBtns = [...document.getElementsByClassName('disabled')]
31
+
32
+ $remote.onMatchesChange((_, m) => {
33
+ canRequest = true
34
+ matchesEl.innerText = inputEl.value ? m.active + '/' + m.total : ''
35
+
36
+ for (var moveBtn of moveBtns) {
37
+ (canMove = m.total > 1) ?
38
+ moveBtn.classList.remove('disabled') :
39
+ moveBtn.classList.add('disabled')
40
+ }
41
+ })
42
+
43
+ $remote.onInputFocus(async () => {
44
+ inputEl.setSelectionRange(0, inputEl.value.length)
45
+ inputEl.focus()
46
+ })
47
+
48
+ inputEl.value = await $remote.getInitialInput()
49
+ inputEl.setSelectionRange(0, inputEl.value.length)
50
+ inputEl.focus()
51
+ })
@@ -0,0 +1,37 @@
1
+ <!doctype html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>Findbar</title>
8
+ <link rel="stylesheet" href="app.css">
9
+ <body>
10
+ <nav>
11
+ <input id='input' oninput="inputChange(event)" type="search" spellcheck="false">
12
+ <span id="matches"></span>
13
+ <div class="divider"></div>
14
+ <div class="btn-group">
15
+ <div onclick="move(false)" class="disabled">
16
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
17
+ <path d="M7 15L12 10L17 15"/>
18
+ </svg>
19
+ </div>
20
+ <div onclick="move(true)" class="disabled">
21
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
22
+ <path d="M7 10L12 15L17 10"/>
23
+ </svg>
24
+ </div>
25
+ <div onclick="$remote.close()">
26
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
27
+ <path d="M7 17.5L12 12.5L17 17.5"/>
28
+ <path d="M7 7.5L12 12.5L17 7.5"/>
29
+ </svg>
30
+ </div>
31
+ </div>
32
+ </nav>
33
+
34
+ <script src="app.js"></script>
35
+ </body>
36
+
37
+ </html>