@vacantthinker/firefox-addon-framework-easy 2026.606.751 → 2026.606.1403
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 +11 -15
- package/package.json +1 -5
- package/src/BaseORM.js +1 -1
- package/src/browserDownload.js +15 -15
- package/src/browserNotification.js +2 -2
- package/src/browserRuntime.js +1 -1
- package/src/browserRuntimeOnMessageCommon.js +17 -17
- package/src/browserTab.js +28 -28
- package/src/generate.js +188 -187
- package/src/serviceFetch.js +2 -2
- package/src/serviceOpContent.js +43 -42
- package/src/serviceOpJavascript.js +36 -32
- package/src/serviceUpdateTabStyle.js +6 -6
- package/src/serviceUserSettings.js +10 -10
- package/.prettierrc.json +0 -12
package/README.md
CHANGED
|
@@ -26,15 +26,15 @@ export class BaseORM { }
|
|
|
26
26
|
### 📄 File: `src/browserDownload.js`
|
|
27
27
|
```javascript
|
|
28
28
|
export async function browserDownloadByDownlink(
|
|
29
|
-
|
|
29
|
+
{ }
|
|
30
30
|
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
### 📄 File: `src/browserNotification.js`
|
|
34
34
|
```javascript
|
|
35
35
|
export async function browserNotificationCreate(
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
content,
|
|
37
|
+
title = browserRuntimeManifestName(),
|
|
38
38
|
) { }
|
|
39
39
|
|
|
40
40
|
```
|
|
@@ -44,7 +44,7 @@ export async function browserNotificationCreate(
|
|
|
44
44
|
export function browserRuntimeReload() { }
|
|
45
45
|
|
|
46
46
|
export async function browserRuntimeSetUninstallURL(
|
|
47
|
-
|
|
47
|
+
url = '',
|
|
48
48
|
) { }
|
|
49
49
|
|
|
50
50
|
export function browserRuntimeOnUpdateAvailable(doWhat = null) { }
|
|
@@ -61,11 +61,7 @@ export function browserRuntimeManifestName() { }
|
|
|
61
61
|
|
|
62
62
|
### 📄 File: `src/browserRuntimeOnMessageCommon.js`
|
|
63
63
|
```javascript
|
|
64
|
-
export function browserRuntimeOnMessageCommon(
|
|
65
|
-
act,
|
|
66
|
-
message,
|
|
67
|
-
sendResponse
|
|
68
|
-
) { }
|
|
64
|
+
export function browserRuntimeOnMessageCommon(act, message, sendResponse) { }
|
|
69
65
|
|
|
70
66
|
```
|
|
71
67
|
|
|
@@ -92,8 +88,8 @@ export class DomainORM extends BaseORM { }
|
|
|
92
88
|
### 📄 File: `src/generate.js`
|
|
93
89
|
```javascript
|
|
94
90
|
export async function generateHtmlByUserSettings(
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
userSettings,
|
|
92
|
+
radioItemClickCallback,
|
|
97
93
|
) { }
|
|
98
94
|
|
|
99
95
|
export function generateMkvScriptForSystemWindows({ }
|
|
@@ -167,8 +163,8 @@ export async function serviceDownloadByDownlink(message) { }
|
|
|
167
163
|
### 📄 File: `src/serviceFetch.js`
|
|
168
164
|
```javascript
|
|
169
165
|
export async function servicePostJson(
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
serverUrl,
|
|
167
|
+
message,
|
|
172
168
|
) { }
|
|
173
169
|
|
|
174
170
|
export async function serviceSendDataToLocalAria2(message) { }
|
|
@@ -187,7 +183,7 @@ export function serviceGetCurrentDateYYYYMMDDHHMMSS() { }
|
|
|
187
183
|
```javascript
|
|
188
184
|
export async function serviceCopyContentToClipboard(data) { }
|
|
189
185
|
|
|
190
|
-
export function serviceSaveContentToLocal(content, filename, ext =
|
|
186
|
+
export function serviceSaveContentToLocal(content, filename, ext = 'txt') { }
|
|
191
187
|
|
|
192
188
|
export async function serviceGenerateMkvToolNixScript({ }
|
|
193
189
|
|
|
@@ -198,7 +194,7 @@ export function serviceRemoveIllegalWord(value) { }
|
|
|
198
194
|
### 📄 File: `src/serviceOpJavascript.js`
|
|
199
195
|
```javascript
|
|
200
196
|
export async function serviceTakeScreenshot(
|
|
201
|
-
|
|
197
|
+
{ }
|
|
202
198
|
|
|
203
199
|
export async function serviceElementPicker(message) { }
|
|
204
200
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vacantthinker/firefox-addon-framework-easy",
|
|
3
|
-
"version": "2026.0606.
|
|
3
|
+
"version": "2026.0606.1403",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -22,9 +22,5 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@types/firefox-webext-browser": "^143.0.0"
|
|
25
|
-
},
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"prettier": "^3.8.3",
|
|
28
|
-
"prettier-plugin-jsdoc": "^1.8.1"
|
|
29
25
|
}
|
|
30
26
|
}
|
package/src/BaseORM.js
CHANGED
|
@@ -19,7 +19,7 @@ export class BaseORM {
|
|
|
19
19
|
constructor(prefix, id, defaultValue = {}) {
|
|
20
20
|
if (new.target === BaseORM) {
|
|
21
21
|
throw new TypeError(
|
|
22
|
-
|
|
22
|
+
'Cannot construct BaseORM instances directly (Abstract Class).');
|
|
23
23
|
}
|
|
24
24
|
if (!prefix || !id) {
|
|
25
25
|
throw new Error('Both prefix and id must be specified.');
|
package/src/browserDownload.js
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
* @returns {Promise<void>}
|
|
7
7
|
*/
|
|
8
8
|
export async function browserDownloadByDownlink(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
{
|
|
10
|
+
downlink,
|
|
11
|
+
filename = null,
|
|
12
|
+
}) {
|
|
13
|
+
let url = downlink;
|
|
14
|
+
/** @type {browser.downloads._DownloadOptions} */
|
|
15
|
+
let options = {
|
|
16
|
+
url,
|
|
17
|
+
saveAs: false,
|
|
18
|
+
};
|
|
19
|
+
if (filename) {
|
|
20
|
+
Object.assign(options, {filename});
|
|
21
|
+
}
|
|
22
|
+
await browser.downloads.download(options);
|
|
23
|
+
}
|
|
@@ -7,8 +7,8 @@ import {browserRuntimeManifestName} from './browserRuntime.js';
|
|
|
7
7
|
* @returns {Promise<string>}
|
|
8
8
|
*/
|
|
9
9
|
export async function browserNotificationCreate(
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
content,
|
|
11
|
+
title = browserRuntimeManifestName(),
|
|
12
12
|
) {
|
|
13
13
|
|
|
14
14
|
const tag = 'browserNotificationCreate';
|
package/src/browserRuntime.js
CHANGED
|
@@ -4,34 +4,35 @@ import {browserTabSendMessage} from './browserTab.js';
|
|
|
4
4
|
import {browserNotificationCreate} from './browserNotification.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Offer common act <=> function
|
|
8
8
|
*
|
|
9
9
|
* @param act{
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* |'actLog'
|
|
11
|
+
* |'actMarco'
|
|
12
|
+
* |'actRequestTabIdTabUrl'
|
|
13
|
+
* |'actNotification'
|
|
14
|
+
* |'actRemoveTab'
|
|
15
|
+
* |'actDownloadFile'
|
|
16
|
+
* |'actSendMessageToTab'
|
|
17
|
+
* }
|
|
17
18
|
* @param message
|
|
18
19
|
* @param sendResponse
|
|
19
20
|
*/
|
|
20
|
-
export function browserRuntimeOnMessageCommon(
|
|
21
|
-
act,
|
|
22
|
-
message,
|
|
23
|
-
sendResponse
|
|
24
|
-
) {
|
|
21
|
+
export function browserRuntimeOnMessageCommon(act, message, sendResponse) {
|
|
25
22
|
switch (act) {
|
|
26
23
|
case 'actLog':
|
|
27
24
|
console.log('act', act, 'message', message);
|
|
28
25
|
break;
|
|
26
|
+
case 'actMarco': //Marco Polo pool game
|
|
27
|
+
sendResponse({status: 'Polo'});
|
|
28
|
+
return true;
|
|
29
|
+
break;
|
|
29
30
|
case 'actRequestTabIdTabUrl':
|
|
30
31
|
sendResponse(message);
|
|
31
|
-
break
|
|
32
|
+
break;
|
|
32
33
|
case 'actNotification':
|
|
33
|
-
browserNotificationCreate(message.content)
|
|
34
|
-
break
|
|
34
|
+
browserNotificationCreate(message.content);
|
|
35
|
+
break;
|
|
35
36
|
case 'actRemoveTab':
|
|
36
37
|
tabOpRemove(message.tabId);
|
|
37
38
|
break;
|
|
@@ -42,5 +43,4 @@ export function browserRuntimeOnMessageCommon(
|
|
|
42
43
|
browserTabSendMessage(message.tabId, message);
|
|
43
44
|
break;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
46
|
}
|
package/src/browserTab.js
CHANGED
|
@@ -10,13 +10,13 @@ export async function browserTabSendMessage(tabId, message) {
|
|
|
10
10
|
export function browserTabWaitReloadThenSendMessageToContentJs(message) {
|
|
11
11
|
let tabId = message.tabId;
|
|
12
12
|
browser.tabs.onUpdated.addListener(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
async function lis(tabId, changeInfo) {
|
|
14
|
+
if (changeInfo.status === 'complete') {
|
|
15
|
+
browser.tabs.onUpdated.removeListener(lis);
|
|
16
|
+
await browserTabSendMessage(tabId, message);
|
|
17
|
+
}
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
, {tabId, properties: ['status']});
|
|
19
|
+
, {tabId, properties: ['status']});
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -41,14 +41,14 @@ export async function browserTabCreateToDownload(message) {
|
|
|
41
41
|
|
|
42
42
|
let {tabId} = await tabOpCreateNear(properties);
|
|
43
43
|
browser.tabs.onUpdated.addListener(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
async function lis(tabId, changeInfo) {
|
|
45
|
+
if (changeInfo.status === 'complete') {
|
|
46
|
+
browser.tabs.onUpdated.removeListener(lis);
|
|
47
|
+
// todo code here
|
|
48
|
+
await tabOpRemove(tabId);
|
|
49
|
+
}
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
, {tabId, properties: ['status']});
|
|
51
|
+
, {tabId, properties: ['status']});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -73,15 +73,15 @@ export async function browserTabCreateNearSendMessageToContentJs(message) {
|
|
|
73
73
|
|
|
74
74
|
let {tabId} = await tabOpCreateNear(properties);
|
|
75
75
|
browser.tabs.onUpdated.addListener(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
async function lis(tabId, changeInfo) {
|
|
77
|
+
if (changeInfo.status === 'complete') {
|
|
78
|
+
browser.tabs.onUpdated.removeListener(lis);
|
|
79
|
+
// todo code here
|
|
80
|
+
await browserTabSendMessage(
|
|
81
|
+
tabId, Object.assign({}, message, {tabId}));
|
|
82
|
+
}
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
, {tabId, properties: ['status']});
|
|
84
|
+
, {tabId, properties: ['status']});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -91,13 +91,13 @@ export async function browserTabCreateNearSendMessageToContentJs(message) {
|
|
|
91
91
|
*/
|
|
92
92
|
export function browserTabWaitReloadThenRemoveIt({tabId}) {
|
|
93
93
|
browser.tabs.onUpdated.addListener(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
async function lis(tabId, changeInfo) {
|
|
95
|
+
if (changeInfo.status === 'complete') {
|
|
96
|
+
browser.tabs.onUpdated.removeListener(lis);
|
|
97
|
+
// todo code here
|
|
98
|
+
await tabOpRemove(tabId);
|
|
99
|
+
}
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
-
, {tabId, properties: ['status']});
|
|
101
|
+
, {tabId, properties: ['status']});
|
|
102
102
|
|
|
103
103
|
}
|
package/src/generate.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {stoOpGet, stoOpSet} from './opStorage.js';
|
|
2
2
|
|
|
3
3
|
// [Optimization 1] Introduce a debounce function to prevent triggering storage writes on every keystroke.
|
|
4
4
|
function debounce(func, wait) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
let timeout;
|
|
6
|
+
return function(...args) {
|
|
7
|
+
clearTimeout(timeout);
|
|
8
|
+
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
9
|
+
};
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -17,187 +17,188 @@ function debounce(func, wait) {
|
|
|
17
17
|
* @returns {Promise<DocumentFragment>}
|
|
18
18
|
*/
|
|
19
19
|
export async function generateHtmlByUserSettings(
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
userSettings,
|
|
21
|
+
radioItemClickCallback,
|
|
22
22
|
) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
23
|
+
const elementsMap = {};
|
|
24
|
+
|
|
25
|
+
// [Optimization 2] Use a DocumentFragment for off-screen DOM construction to improve rendering performance.
|
|
26
|
+
const fragment = document.createDocumentFragment();
|
|
27
|
+
|
|
28
|
+
const keys = Object.keys(userSettings);
|
|
29
|
+
|
|
30
|
+
// [Optimization 3] Batch fetch all stored values.
|
|
31
|
+
// Wait for all queries to complete to prevent async callbacks from interrupting the rendering pipeline.
|
|
32
|
+
const valuesArray = await Promise.all(keys.map((key) => stoOpGet(key)));
|
|
33
|
+
const storageData = {};
|
|
34
|
+
keys.forEach((key, index) => {
|
|
35
|
+
storageData[key] = valuesArray[index];
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// First pass: Pre-create all container wrappers.
|
|
39
|
+
// This ensures all target elements exist in the DOM references before executing triggerVisibility later.
|
|
40
|
+
keys.forEach((storageKey) => {
|
|
41
|
+
const eleWrap = document.createElement('fieldset');
|
|
42
|
+
const eleTitle = document.createElement('legend');
|
|
43
|
+
eleTitle.textContent = storageKey;
|
|
44
|
+
eleWrap.append(eleTitle);
|
|
45
|
+
|
|
46
|
+
elementsMap[storageKey] = eleWrap;
|
|
47
|
+
fragment.append(eleWrap);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Second pass: Synchronously populate form content and attach event listeners.
|
|
51
|
+
keys.forEach((storageKey) => {
|
|
52
|
+
const storageValue = userSettings[storageKey];
|
|
53
|
+
const type = storageValue.type || 'text';
|
|
54
|
+
const eleWrap = elementsMap[storageKey];
|
|
55
|
+
|
|
56
|
+
// Retrieve pre-loaded value; fallback to the schema's default value if undefined.
|
|
57
|
+
const storedValue = storageData[storageKey];
|
|
58
|
+
const initialValue =
|
|
59
|
+
storedValue !== undefined && storedValue !== null
|
|
60
|
+
? storedValue
|
|
61
|
+
: storageValue.selected;
|
|
62
|
+
|
|
63
|
+
// --- CONDITION 1: CHECKBOX & RADIO ---
|
|
64
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
65
|
+
/** @type {string[]} */
|
|
66
|
+
const options = storageValue.options || [];
|
|
67
|
+
|
|
68
|
+
options.forEach((option) => {
|
|
69
|
+
const eleLabel = document.createElement('label');
|
|
70
|
+
eleLabel.textContent = option;
|
|
71
|
+
const eleInput = document.createElement('input');
|
|
72
|
+
eleInput.name = storageKey;
|
|
73
|
+
eleInput.type = type;
|
|
74
|
+
eleInput.value = option;
|
|
75
|
+
|
|
76
|
+
if (type === 'checkbox') {
|
|
77
|
+
const initialArray = Array.from(initialValue || []);
|
|
78
|
+
const set = new Set(initialArray);
|
|
79
|
+
eleInput.checked = set.has(option); // Apply state synchronously
|
|
80
|
+
|
|
81
|
+
eleInput.addEventListener('change', async () => {
|
|
82
|
+
// Derive state in real-time from the DOM (faster than reading from storage again)
|
|
83
|
+
const allCheckboxes = eleWrap.querySelectorAll(
|
|
84
|
+
'input[type="checkbox"]',
|
|
85
|
+
);
|
|
86
|
+
const valueNew = Array.from(allCheckboxes).
|
|
87
|
+
filter((cb) => cb.checked).
|
|
88
|
+
map((cb) => cb.value);
|
|
89
|
+
|
|
90
|
+
await stoOpSet(storageKey, valueNew);
|
|
91
|
+
triggerVisibility(storageKey, valueNew);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else if (type === 'radio') {
|
|
95
|
+
if (option === initialValue) {
|
|
96
|
+
eleInput.checked = true; // Apply state synchronously
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// It is recommended to bind the 'change' event to the input rather than 'onclick' to the label
|
|
100
|
+
eleInput.addEventListener('change', () => {
|
|
101
|
+
stoOpSet(storageKey, option).then(() => {
|
|
102
|
+
if (typeof radioItemClickCallback === 'function') {
|
|
103
|
+
radioItemClickCallback(storageKey, option);
|
|
104
|
+
}
|
|
105
|
+
triggerVisibility(storageKey, option);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fix: Native HTML typically places the input element before the text node inside a label.
|
|
111
|
+
eleLabel.prepend(eleInput);
|
|
112
|
+
eleWrap.append(eleLabel);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- CONDITION 2: TOGGLE BUTTON ---
|
|
117
|
+
else if (type === 'button') {
|
|
118
|
+
const eleButton = document.createElement('button');
|
|
119
|
+
eleButton.type = 'button';
|
|
120
|
+
|
|
121
|
+
let currentStatus = initialValue === true || initialValue === 'true';
|
|
122
|
+
eleButton.textContent = String(currentStatus); // Apply state synchronously
|
|
123
|
+
|
|
124
|
+
eleButton.addEventListener('click', async () => {
|
|
125
|
+
currentStatus = !currentStatus;
|
|
126
|
+
eleButton.textContent = String(currentStatus);
|
|
127
|
+
await stoOpSet(storageKey, currentStatus);
|
|
128
|
+
triggerVisibility(storageKey, currentStatus);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
eleWrap.append(eleButton);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- CONDITION 3: NUMBER & TEXT INPUTS ---
|
|
135
|
+
else if (type === 'number' || type === 'text') {
|
|
136
|
+
const eleInput = document.createElement('input');
|
|
137
|
+
eleInput.type = type;
|
|
138
|
+
eleInput.name = storageKey;
|
|
139
|
+
eleInput.value = initialValue !== undefined ? initialValue : ''; // Apply state synchronously
|
|
140
|
+
|
|
141
|
+
// [Optimization 4] Wrap the write operation with debounce, delaying storage writes by 500ms.
|
|
142
|
+
// This eliminates UI freezing regardless of typing speed.
|
|
143
|
+
const debouncedSave = debounce(async (val) => {
|
|
144
|
+
const finalizedValue = type === 'number' ? Number(val) : val;
|
|
145
|
+
await stoOpSet(storageKey, finalizedValue);
|
|
146
|
+
}, 500);
|
|
147
|
+
|
|
148
|
+
eleInput.addEventListener('input', () => {
|
|
149
|
+
const rawValue = eleInput.value;
|
|
150
|
+
debouncedSave(rawValue);
|
|
151
|
+
triggerVisibility(storageKey, rawValue); // Visual UI updates do not require a delay
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
eleWrap.append(eleInput);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- CONDITION 4: SPAN / READ-ONLY TEXT ---
|
|
158
|
+
else if (type === 'span') {
|
|
159
|
+
const eleSpan = document.createElement('span');
|
|
160
|
+
eleSpan.textContent = String(initialValue); // Apply state synchronously
|
|
161
|
+
eleWrap.append(eleSpan);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// [Optimization 5] Third pass: Globally trigger a single visibility check.
|
|
166
|
+
// At this point, all DOM elements exist and all initialValues are fully derived,
|
|
167
|
+
// preventing undefined errors or invalid target hiding.
|
|
168
|
+
keys.forEach((storageKey) => {
|
|
169
|
+
const storedValue = storageData[storageKey];
|
|
170
|
+
const initialValue =
|
|
171
|
+
storedValue !== undefined && storedValue !== null
|
|
172
|
+
? storedValue
|
|
173
|
+
: userSettings[storageKey].selected;
|
|
174
|
+
|
|
175
|
+
triggerVisibility(storageKey, initialValue);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Evaluates the visibility rules for a given source key based on its current
|
|
180
|
+
* value.
|
|
181
|
+
*/
|
|
182
|
+
function triggerVisibility(sourceKey, currentValue) {
|
|
183
|
+
const config = userSettings[sourceKey];
|
|
184
|
+
if (config && config.visibilityControl) {
|
|
185
|
+
const {targetField, expectedValue} = config.visibilityControl;
|
|
186
|
+
const targetElement = elementsMap[targetField];
|
|
187
|
+
|
|
188
|
+
if (targetElement) {
|
|
189
|
+
const shouldBeVisible = String(currentValue) === String(expectedValue);
|
|
190
|
+
targetElement.style.display = shouldBeVisible ? '' : 'none';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return fragment;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
export function generateMkvScriptForSystemWindows({
|
|
198
|
-
|
|
198
|
+
export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
199
|
+
let args = {vid, title};
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
return `if (true) {
|
|
201
202
|
const path = require('path');
|
|
202
203
|
const fs = require('fs');
|
|
203
204
|
const {execSync, exec} = require('node:child_process');
|
|
@@ -282,11 +283,11 @@ export function generateMkvScriptForSystemWindows({ vid, title }) {
|
|
|
282
283
|
`;
|
|
283
284
|
}
|
|
284
285
|
|
|
285
|
-
export function generateMkvScriptForSystemFedora({
|
|
286
|
-
|
|
286
|
+
export function generateMkvScriptForSystemFedora({vid, title}) {
|
|
287
|
+
let args = {vid, title};
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
289
|
+
// We wrap the Node.js script inside a Linux Shell script block
|
|
290
|
+
return `#!/usr/bin/env bash
|
|
290
291
|
# This header tells Fedora to treat this file as a runnable bash script
|
|
291
292
|
|
|
292
293
|
# Open a terminal window if not already running inside one
|
package/src/serviceFetch.js
CHANGED
package/src/serviceOpContent.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {browserRuntimePlatformInfo} from './browserRuntime.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from
|
|
3
|
+
generateMkvScriptForSystemFedora,
|
|
4
|
+
generateMkvScriptForSystemWindows,
|
|
5
|
+
} from './generate.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param data
|
|
9
9
|
* @returns {Promise<void>}
|
|
10
10
|
*/
|
|
11
11
|
export async function serviceCopyContentToClipboard(data) {
|
|
12
|
-
|
|
12
|
+
return await window.navigator.clipboard.writeText(data);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -19,29 +19,29 @@ export async function serviceCopyContentToClipboard(data) {
|
|
|
19
19
|
* @param {string} filename Eg: abc
|
|
20
20
|
* @param {string} ext Txt json
|
|
21
21
|
*/
|
|
22
|
-
export function serviceSaveContentToLocal(content, filename, ext =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
export function serviceSaveContentToLocal(content, filename, ext = 'txt') {
|
|
23
|
+
const eleBtn = document.createElement('button');
|
|
24
|
+
eleBtn.addEventListener(
|
|
25
|
+
'click',
|
|
26
|
+
function() {
|
|
27
|
+
const eleA = document.createElement('a');
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const extObj = {
|
|
30
|
+
txt: 'text/plain',
|
|
31
|
+
json: 'application/json',
|
|
32
|
+
};
|
|
33
|
+
const type = extObj[ext];
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
const file = new Blob([content], {type});
|
|
36
|
+
eleA.href = URL.createObjectURL(file);
|
|
37
|
+
eleA.download = [filename, ext].join('.');
|
|
38
|
+
eleA.click();
|
|
39
|
+
URL.revokeObjectURL(eleA.href);
|
|
40
|
+
},
|
|
41
|
+
false,
|
|
42
|
+
);
|
|
43
|
+
eleBtn.click();
|
|
44
|
+
// eleBtn.previousElementSibling
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -51,16 +51,17 @@ export function serviceSaveContentToLocal(content, filename, ext = "txt") {
|
|
|
51
51
|
* }}
|
|
52
52
|
* @returns {Promise<void>}
|
|
53
53
|
*/
|
|
54
|
-
export async function serviceGenerateMkvToolNixScript({
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
export async function serviceGenerateMkvToolNixScript({vid, title}) {
|
|
55
|
+
let message = {vid, title};
|
|
56
|
+
const platformInfo = await browserRuntimePlatformInfo();
|
|
57
|
+
if (platformInfo.os === 'win') {
|
|
58
|
+
let content = generateMkvScriptForSystemWindows(message);
|
|
59
|
+
serviceSaveContentToLocal(content, title, 'js');
|
|
60
|
+
}
|
|
61
|
+
else if (platformInfo.os === 'linux') {
|
|
62
|
+
let content = generateMkvScriptForSystemFedora(message);
|
|
63
|
+
serviceSaveContentToLocal(content, title, 'sh');
|
|
64
|
+
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/**
|
|
@@ -68,15 +69,15 @@ export async function serviceGenerateMkvToolNixScript({ vid, title }) {
|
|
|
68
69
|
* @returns {string} -
|
|
69
70
|
*/
|
|
70
71
|
export function serviceRemoveIllegalWord(value) {
|
|
71
|
-
|
|
72
|
+
if (!value) return '';
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
let name = value.trim().split(/\r?\n/).shift();
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
name = name.replace(/[\p{P}\p{S}\p{C}]/gu, ' ');
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
name = name.replace(/[~"#%&*:<>?/\\{|}]/g, ' ');
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
name = name.replace(/[\s\u3000]+/g, ' ').trim();
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
return name.replace(/^[-.]+|[-.]+$/g, '');
|
|
82
83
|
}
|
|
@@ -20,24 +20,24 @@ import {browserNotificationCreate} from './browserNotification.js';
|
|
|
20
20
|
* @returns {Promise<void>}
|
|
21
21
|
*/
|
|
22
22
|
export async function serviceTakeScreenshot(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
{
|
|
24
|
+
tabId,
|
|
25
|
+
filename,
|
|
26
|
+
rect,
|
|
27
|
+
}) {
|
|
28
28
|
|
|
29
29
|
const tag = 'actTakeScreenshot()';
|
|
30
30
|
let dataURI = await browser.tabs.captureTab(tabId, {
|
|
31
31
|
rect: rect,
|
|
32
32
|
});
|
|
33
33
|
let assign = Object.assign(
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
{},
|
|
35
|
+
{dataURI, filename},
|
|
36
36
|
);
|
|
37
37
|
await browser.scripting.executeScript({
|
|
38
38
|
target: {tabId},
|
|
39
39
|
args: [assign],
|
|
40
|
-
func: function
|
|
40
|
+
func: function(message) {
|
|
41
41
|
if (message) {
|
|
42
42
|
let {dataURI, filename} = message;
|
|
43
43
|
|
|
@@ -70,10 +70,10 @@ export async function serviceElementPicker(message) {
|
|
|
70
70
|
await browser.scripting.executeScript({
|
|
71
71
|
target: {tabId},
|
|
72
72
|
args: [message],
|
|
73
|
-
func: async function
|
|
73
|
+
func: async function(message) {
|
|
74
74
|
if (!message) return;
|
|
75
75
|
console.log('picker.js initialized', message);
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
// 1. Create a dedicated "Always On Top" highlighter overlay
|
|
78
78
|
const overlayId = 'extension-element-highlighter-overlay';
|
|
79
79
|
let overlay = document.getElementById(overlayId);
|
|
@@ -109,7 +109,8 @@ export async function serviceElementPicker(message) {
|
|
|
109
109
|
selector += '#' + el.id;
|
|
110
110
|
path.unshift(selector);
|
|
111
111
|
break;
|
|
112
|
-
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
113
114
|
let sib = el, nth = 1;
|
|
114
115
|
while (sib = sib.previousElementSibling) {
|
|
115
116
|
if (sib.nodeName.toLowerCase() === selector) nth++;
|
|
@@ -136,9 +137,9 @@ export async function serviceElementPicker(message) {
|
|
|
136
137
|
overlay.style.setProperty('top', `${clientRect.top}px`, 'important');
|
|
137
138
|
overlay.style.setProperty('left', `${clientRect.left}px`, 'important');
|
|
138
139
|
overlay.style.setProperty('width', `${clientRect.width}px`,
|
|
139
|
-
|
|
140
|
+
'important');
|
|
140
141
|
overlay.style.setProperty('height', `${clientRect.height}px`,
|
|
141
|
-
|
|
142
|
+
'important');
|
|
142
143
|
|
|
143
144
|
// Change mouse cursor to indicate picking mode
|
|
144
145
|
document.body.style.setProperty('cursor', 'crosshair', 'important');
|
|
@@ -164,13 +165,13 @@ export async function serviceElementPicker(message) {
|
|
|
164
165
|
|
|
165
166
|
// Assuming 'target' is your clicked element (e.g., from e.target)
|
|
166
167
|
let messageTakeScreenshot = Object.assign(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
{}, // Start with a fresh, empty object
|
|
169
|
+
message, // Put the original message first so it doesn't overwrite your new data
|
|
170
|
+
{rect},
|
|
171
|
+
{
|
|
172
|
+
// The guaranteed unique CSS path (e.g., "div#wrap > ul > li:nth-of-type(2)")
|
|
173
|
+
uniqueSelector: getUniqueSelector(target),
|
|
174
|
+
},
|
|
174
175
|
);
|
|
175
176
|
|
|
176
177
|
await browser.runtime.sendMessage(messageTakeScreenshot);
|
|
@@ -224,9 +225,9 @@ export async function serviceGetFullPageRectData(message) {
|
|
|
224
225
|
x, y, width, height,
|
|
225
226
|
};
|
|
226
227
|
browser.runtime.sendMessage(Object.assign(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
{},
|
|
229
|
+
message,
|
|
230
|
+
{rect},
|
|
230
231
|
));
|
|
231
232
|
// todo end if (message)
|
|
232
233
|
},
|
|
@@ -274,7 +275,7 @@ export async function serviceFindAllMagnetLink(message) {
|
|
|
274
275
|
// --- Type 2: Find inside raw text (for <div>, <span>, <td>, etc.) ---
|
|
275
276
|
// We target elements that don't have children to avoid grabbing huge parent container blocks
|
|
276
277
|
const allElements = document.querySelectorAll(
|
|
277
|
-
|
|
278
|
+
'div, span, td, p, a, button');
|
|
278
279
|
allElements.forEach(el => {
|
|
279
280
|
if (el.children.length === 0) { // Deepest element
|
|
280
281
|
const text = el.textContent.trim();
|
|
@@ -292,11 +293,11 @@ export async function serviceFindAllMagnetLink(message) {
|
|
|
292
293
|
}
|
|
293
294
|
|
|
294
295
|
await browser.runtime.sendMessage(Object.assign(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
{},
|
|
297
|
+
message,
|
|
298
|
+
{
|
|
299
|
+
data: findAllMagnetLinks(),
|
|
300
|
+
},
|
|
300
301
|
));
|
|
301
302
|
|
|
302
303
|
// todo end if(message)
|
|
@@ -329,13 +330,16 @@ export async function serviceDealWithMagnetLink(message) {
|
|
|
329
330
|
|
|
330
331
|
if (handleOption === 'clipboard') {
|
|
331
332
|
await serviceCopyContentToClipboard(content);
|
|
332
|
-
}
|
|
333
|
+
}
|
|
334
|
+
else if (handleOption === 'txt') {
|
|
333
335
|
serviceSaveContentToLocal(content, filename);
|
|
334
|
-
}
|
|
336
|
+
}
|
|
337
|
+
else if (handleOption === 'clipboardAndTxt') {
|
|
335
338
|
await serviceCopyContentToClipboard(content);
|
|
336
339
|
serviceSaveContentToLocal(content, filename);
|
|
337
340
|
}
|
|
338
|
-
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
339
343
|
// todo notification => magnet link not found!
|
|
340
344
|
await browserNotificationCreate('magnet link not found!');
|
|
341
345
|
}
|
|
@@ -31,9 +31,9 @@ export async function serviceUpdataALLTextNodeColor(message) {
|
|
|
31
31
|
*/
|
|
32
32
|
function nativeTreeWalkerFindALLElementHasNodeText() {
|
|
33
33
|
const walker = document.createTreeWalker(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
document.body,
|
|
35
|
+
NodeFilter.SHOW_TEXT,
|
|
36
|
+
null,
|
|
37
37
|
);
|
|
38
38
|
|
|
39
39
|
let node;
|
|
@@ -96,9 +96,9 @@ export async function serviceUpdataALLNodeBackgroundColor(message) {
|
|
|
96
96
|
*/
|
|
97
97
|
function nativeTreeWalker() {
|
|
98
98
|
const walker = document.createTreeWalker(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
document.body,
|
|
100
|
+
NodeFilter.SHOW_ELEMENT,
|
|
101
|
+
null,
|
|
102
102
|
);
|
|
103
103
|
|
|
104
104
|
let node;
|
|
@@ -6,16 +6,16 @@ import {stoOpGet, stoOpSet} from './opStorage.js';
|
|
|
6
6
|
* @returns {Promise<void>}
|
|
7
7
|
*/
|
|
8
8
|
export async function serviceInitUserSettings(userSettings) {
|
|
9
|
-
const initPromises = Object.entries(userSettings)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
const initPromises = Object.entries(userSettings).
|
|
10
|
+
map(async ([key, setting]) => {
|
|
11
|
+
const oldValue = await stoOpGet(key);
|
|
12
|
+
|
|
13
|
+
// FIX: Check strictly for null or undefined.
|
|
14
|
+
// This allows `false` and `0` to be recognized as valid saved values.
|
|
15
|
+
if (oldValue === null || oldValue === undefined) {
|
|
16
|
+
await stoOpSet(key, setting.selected);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
19
|
|
|
20
20
|
await Promise.all(initPromises);
|
|
21
21
|
}
|
package/.prettierrc.json
DELETED