@vacantthinker/firefox-addon-framework-easy 2026.526.1707 → 2026.527.1212

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 CHANGED
@@ -23,6 +23,12 @@ export class BaseORM { }
23
23
 
24
24
  ```
25
25
 
26
+ ### 📄 File: `src/browserDownload.js`
27
+ ```javascript
28
+ export async function browserDownloadByDownlink({ }
29
+
30
+ ```
31
+
26
32
  ### 📄 File: `src/browserNotification.js`
27
33
  ```javascript
28
34
  export async function browserNotificationCreate(
@@ -59,6 +65,10 @@ export function generateHtmlByUserSettings(
59
65
  radioItemClickCallback,
60
66
  ) { }
61
67
 
68
+ export function generateMkvScriptForSystemWindows({ }
69
+
70
+ export function generateMkvScriptForSystemFedora({ }
71
+
62
72
  ```
63
73
 
64
74
  ### 📄 File: `src/opStorage.js`
@@ -149,6 +159,8 @@ export async function serviceCopyContentToClipboard(data) { }
149
159
 
150
160
  export function serviceSaveContentToLocal(content, filename, ext = 'txt') { }
151
161
 
162
+ export async function serviceGenerateMkvToolNixScript({ }
163
+
152
164
  export function serviceRemoveIllegalWord(value) { }
153
165
 
154
166
  ```
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './src/baseORM.js'
2
+ export * from './src/browserDownload.js'
2
3
  export * from './src/browserNotification.js'
3
4
  export * from './src/browserRuntime.js'
4
5
  export * from './src/generate.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vacantthinker/firefox-addon-framework-easy",
3
- "version": "2026.0526.1707",
3
+ "version": "2026.0527.1212",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -0,0 +1,11 @@
1
+ /**
2
+ *
3
+ * @param param0
4
+ * @param param0.downlink{string}
5
+ * @param param0.filename{string}
6
+ * @returns {Promise<void>}
7
+ */
8
+ export async function browserDownloadByDownlink({ downlink, filename }) {
9
+ let url = downlink;
10
+ await browser.downloads.download({url, filename});
11
+ }
package/src/generate.js CHANGED
@@ -1,4 +1,4 @@
1
- import { stoOpGet, stoOpSet } from './opStorage.js';
1
+ import {stoOpGet, stoOpSet} from './opStorage.js';
2
2
 
3
3
  /**
4
4
  * Generates HTML elements based on a user settings schema object.
@@ -11,7 +11,10 @@ export function generateHtmlByUserSettings(
11
11
  userSettings,
12
12
  radioItemClickCallback,
13
13
  ) {
14
- return Object.keys(userSettings).map((storageKey) => {
14
+ // Keeps track of all generated fieldsets by their storageKey
15
+ const elementsMap = {};
16
+
17
+ const fieldsets = Object.keys(userSettings).map((storageKey) => {
15
18
  const storageValue = userSettings[storageKey];
16
19
  const type = storageValue.type || 'text'; // Default to text if type is not specified
17
20
 
@@ -21,6 +24,9 @@ export function generateHtmlByUserSettings(
21
24
  eleTitle.textContent = storageKey;
22
25
  eleWrap.append(eleTitle);
23
26
 
27
+ // Save a reference to the wrapper element for visibility switching
28
+ elementsMap[storageKey] = eleWrap;
29
+
24
30
  // --- CONDITION 1: CHECKBOX & RADIO ---
25
31
  if (type === 'checkbox' || type === 'radio') {
26
32
  const options = storageValue.options || [];
@@ -39,37 +45,54 @@ export function generateHtmlByUserSettings(
39
45
  const initialArray = Array.from(v || storageValue.selected || []);
40
46
  const set = new Set(initialArray);
41
47
  eleInput.checked = set.has(option);
48
+
49
+ // Initial visibility evaluation
50
+ triggerVisibility(storageKey, initialArray);
42
51
  });
43
52
 
44
53
  eleInput.addEventListener('change', async () => {
45
- const optionsCurrent = await stoOpGet(storageKey) || storageValue.selected || [];
54
+ const optionsCurrent = await stoOpGet(storageKey) ||
55
+ storageValue.selected || [];
46
56
  const set = new Set(Array.from(optionsCurrent));
47
57
 
48
58
  if (eleInput.checked) {
49
59
  set.add(option);
50
- } else {
60
+ }
61
+ else {
51
62
  set.delete(option);
52
63
  }
53
64
 
54
65
  const valueNew = Array.from(set);
55
- console.info(`k=${storageKey} option=${option} eleInput.checked=${eleInput.checked} valueNew=${valueNew}`);
66
+ console.info(
67
+ `k=${storageKey} option=${option} eleInput.checked=${eleInput.checked} valueNew=${valueNew}`);
56
68
  await stoOpSet(storageKey, valueNew);
69
+
70
+ // Dynamic visibility update
71
+ triggerVisibility(storageKey, valueNew);
57
72
  });
58
73
  }
59
74
  else if (type === 'radio') {
60
75
  stoOpGet(storageKey).then((v) => {
61
- const currentSelected = (v !== undefined && v !== null) ? v : storageValue.selected;
76
+ const currentSelected = (v !== undefined && v !== null) ?
77
+ v :
78
+ storageValue.selected;
62
79
  if (option === currentSelected) {
63
80
  eleInput.checked = true;
64
81
  }
82
+
83
+ // Initial visibility evaluation
84
+ triggerVisibility(storageKey, currentSelected);
65
85
  });
66
86
 
67
- eleLabel.onclick = function () {
87
+ eleLabel.onclick = function() {
68
88
  stoOpSet(storageKey, option).then(() => {
69
89
  console.info(`k=${storageKey} option=${option}`);
70
90
  if (typeof radioItemClickCallback === 'function') {
71
91
  radioItemClickCallback(storageKey, option);
72
92
  }
93
+
94
+ // Dynamic visibility update
95
+ triggerVisibility(storageKey, option);
73
96
  });
74
97
  };
75
98
  }
@@ -86,14 +109,22 @@ export function generateHtmlByUserSettings(
86
109
 
87
110
  stoOpGet(storageKey).then((v) => {
88
111
  // Fallback to default schema configuration if no value is stored yet
89
- let currentStatus = (v !== undefined && v !== null) ? (v === true || v === 'true') : storageValue.selected;
112
+ let currentStatus = (v !== undefined && v !== null) ?
113
+ (v === true || v === 'true') :
114
+ storageValue.selected;
90
115
  eleButton.textContent = String(currentStatus);
91
116
 
117
+ // Initial visibility evaluation
118
+ triggerVisibility(storageKey, currentStatus);
119
+
92
120
  eleButton.addEventListener('click', async () => {
93
121
  currentStatus = !currentStatus; // Toggle state
94
122
  eleButton.textContent = String(currentStatus);
95
123
  console.info(`k=${storageKey} toggled to=${currentStatus}`);
96
124
  await stoOpSet(storageKey, currentStatus);
125
+
126
+ // Dynamic visibility update
127
+ triggerVisibility(storageKey, currentStatus);
97
128
  });
98
129
  });
99
130
 
@@ -107,8 +138,13 @@ export function generateHtmlByUserSettings(
107
138
  eleInput.name = storageKey;
108
139
 
109
140
  stoOpGet(storageKey).then((v) => {
110
- const currentVal = (v !== undefined && v !== null) ? v : storageValue.selected;
141
+ const currentVal = (v !== undefined && v !== null) ?
142
+ v :
143
+ storageValue.selected;
111
144
  eleInput.value = currentVal;
145
+
146
+ // Initial visibility evaluation
147
+ triggerVisibility(storageKey, currentVal);
112
148
  });
113
149
 
114
150
  // Updates storage on every keystroke/change execution
@@ -118,6 +154,9 @@ export function generateHtmlByUserSettings(
118
154
 
119
155
  console.info(`k=${storageKey} value changed to=${finalizedValue}`);
120
156
  await stoOpSet(storageKey, finalizedValue);
157
+
158
+ // Dynamic visibility update
159
+ triggerVisibility(storageKey, finalizedValue);
121
160
  });
122
161
 
123
162
  eleWrap.append(eleInput);
@@ -125,4 +164,192 @@ export function generateHtmlByUserSettings(
125
164
 
126
165
  return eleWrap;
127
166
  });
167
+
168
+ /**
169
+ * Evaluates the visibility rules for a given source key based on its current value.
170
+ */
171
+ function triggerVisibility(sourceKey, currentValue) {
172
+ const config = userSettings[sourceKey];
173
+ if (config && config.visibilityControl) {
174
+ const {targetField, expectedValue} = config.visibilityControl;
175
+ const targetElement = elementsMap[targetField];
176
+
177
+ if (targetElement) {
178
+ // String conversion guarantees type safety (e.g., matching boolean true against string "true")
179
+ const shouldBeVisible = String(currentValue) === String(expectedValue);
180
+ targetElement.style.display = shouldBeVisible ? '' : 'none';
181
+ }
182
+ }
183
+ }
184
+
185
+ return fieldsets;
128
186
  }
187
+
188
+ export function generateMkvScriptForSystemWindows({vid, title}) {
189
+ let args = {vid, title};
190
+
191
+ return `if (true) {
192
+ const path = require('path');
193
+ const fs = require('fs');
194
+ const {execSync, exec} = require('node:child_process');
195
+ let pathDownload = path.join(__dirname);
196
+ let dot = '.';
197
+ let extMKV = 'mkv';
198
+
199
+ let {vid, title} = ${JSON.stringify(args)};
200
+ let playVideoAfterMerged = true;
201
+ let pathToMkvmerge = 'C:\\\\Program Files\\\\MKVToolNix\\\\mkvmerge.exe';
202
+
203
+ let pathMKVOutput = path.join(pathDownload, title.concat(dot, extMKV));
204
+ let pathOutput = path.join(pathDownload, vid.concat(dot, extMKV));
205
+ let pathInputAudio = path.join(pathDownload, vid.concat(dot, "mp3"));
206
+ let pathInputVideo = path.join(pathDownload, vid.concat(dot, "mp4"));
207
+ let pathJsFile = path.join(__filename);
208
+
209
+ if (
210
+ fs.existsSync(pathToMkvmerge)
211
+ ) {
212
+
213
+ console.log('');
214
+ console.log(['file check ok!', title].join(' '));
215
+
216
+ let cmd_merge = [
217
+ [pathToMkvmerge].map(v => '"' + v + '"').join(''),
218
+
219
+ '-o',
220
+ [pathOutput]
221
+ .map(value => '"' + value + '"')
222
+ .join(' '),
223
+
224
+ '--no-video',
225
+ [pathInputAudio]
226
+ .map(value => '"' + value + '"')
227
+ .join(' '),
228
+
229
+ '--no-audio',
230
+ [pathInputVideo]
231
+ .map(value => '"' + value + '"')
232
+ .join(' '),
233
+ ].join(' ');
234
+
235
+ console.log('execute script merge ...')
236
+ console.log(cmd_merge);
237
+
238
+ let exec_merge = exec(cmd_merge);
239
+ exec_merge.stdout.on('data', (data) => {
240
+ console.log(data);
241
+ });
242
+ exec_merge.stderr.on('data', (data) => {
243
+ console.log('error', data);
244
+ });
245
+ exec_merge.stdout.on('close', (data) => {
246
+ console.log(['merge finish!', title].join(' '));
247
+
248
+ if (true) {
249
+ console.log('remove inputFile');
250
+ fs.unlinkSync(pathInputVideo);
251
+ fs.unlinkSync(pathInputAudio);
252
+ }
253
+
254
+ console.log('rename output mkv video...');
255
+ fs.renameSync(pathOutput, pathMKVOutput);
256
+
257
+ if (playVideoAfterMerged) {
258
+ console.log('play mkv video...');
259
+ execSync('"' + pathMKVOutput + '"');
260
+ }
261
+
262
+ if (true) {
263
+ console.log('remove script file');
264
+ fs.unlinkSync(pathJsFile);
265
+ }
266
+ });
267
+
268
+ } else {
269
+ console.log('pathToMkvmerge error')
270
+
271
+ }
272
+ }
273
+ `;
274
+ }
275
+
276
+ export function generateMkvScriptForSystemFedora({vid, title}) {
277
+ let args = {vid, title};
278
+
279
+ // We wrap the Node.js script inside a Linux Shell script block
280
+ return `#!/usr/bin/env bash
281
+ # This header tells Fedora to treat this file as a runnable bash script
282
+
283
+ # Open a terminal window if not already running inside one
284
+ if [ -z "$VTE_VERSION" ] && [ -z "$ALACRITTY_WINDOW_ID" ] && [ -z "$KITTY_WINDOW_ID" ] && [ "$1" != "--child" ]; then
285
+ # Re-run this same script inside a visible terminal window so the user sees progress
286
+ gnome-terminal -- "$0" --child
287
+ exit 0
288
+ fi
289
+
290
+ # Run the embedded Node.js code below
291
+ node << 'EOF'
292
+ const path = require('path');
293
+ const fs = require('fs');
294
+ const { execSync } = require('node:child_process');
295
+
296
+ let pathDownload = path.join(__dirname);
297
+ let dot = '.';
298
+ let extMKV = 'mkv';
299
+
300
+ let {vid, title} = ${JSON.stringify(args)};
301
+ let playVideoAfterMerged = true;
302
+ let pathToMkvmerge = '/usr/bin/mkvmerge';
303
+
304
+ let pathMKVOutput = path.join(pathDownload, title.concat(dot, extMKV));
305
+ let pathOutput = path.join(pathDownload, vid.concat(dot, extMKV));
306
+ let pathInputAudio = path.join(pathDownload, vid.concat(dot, "mp3"));
307
+ let pathInputVideo = path.join(pathDownload, vid.concat(dot, "mp4"));
308
+
309
+ if (fs.existsSync(pathToMkvmerge)) {
310
+ console.log('\\nfile check ok! ' + title);
311
+
312
+ let cmd_merge = [
313
+ '"' + pathToMkvmerge + '"',
314
+ '-o', '"' + pathOutput + '"',
315
+ '--no-video', '"' + pathInputAudio + '"',
316
+ '--no-audio', '"' + pathInputVideo + '"',
317
+ ].join(' ');
318
+
319
+ console.log('execute script merge ...\\n' + cmd_merge);
320
+
321
+ try {
322
+ // Run merge synchronously so stdout pipes directly to the terminal
323
+ execSync(cmd_merge, { stdio: 'inherit' });
324
+ console.log('merge finish! ' + title);
325
+
326
+ console.log('remove inputFile');
327
+ if (fs.existsSync(pathInputVideo)) fs.unlinkSync(pathInputVideo);
328
+ if (fs.existsSync(pathInputAudio)) fs.unlinkSync(pathInputAudio);
329
+
330
+ console.log('rename output mkv video...');
331
+ fs.renameSync(pathOutput, pathMKVOutput);
332
+
333
+ if (playVideoAfterMerged) {
334
+ console.log('play mkv video...');
335
+ // xdg-open usually hands off to the player and exits immediately
336
+ execSync('xdg-open "' + pathMKVOutput + '"');
337
+ }
338
+ } catch (err) {
339
+ console.error('An error occurred during processing:', err.message);
340
+ }
341
+ } else {
342
+ console.log('pathToMkvmerge error. run: sudo dnf install mkvtoolnix')
343
+ }
344
+ EOF
345
+
346
+ # Prevent the terminal window from closing instantly so the user can read messages
347
+ echo ""
348
+ echo "Press Enter to exit and automatically delete this script..."
349
+ read unused_input
350
+
351
+ # Deletes the shell script file itself after completion
352
+ rm -- "$0"
353
+ `;
354
+ }
355
+
@@ -40,7 +40,7 @@ export async function servicePostJson(
40
40
  * rpcsecret:string,
41
41
  * rpcport:string
42
42
  * }}
43
- * @returns {Promise<Response>}
43
+ * @returns {Promise<Response|null>}
44
44
  */
45
45
  export async function serviceSendDataToLocalAria2(message) {
46
46
  let {downlink, filename, rpcsecret, rpcport} = message;
@@ -76,8 +76,9 @@ export async function serviceSendDataToLocalAria2(message) {
76
76
 
77
77
  return response;
78
78
  } catch (e) {
79
+ await browserNotificationCreate(e)
79
80
  console.error(e);
80
- await browserNotificationCreate(`error! ${e}`)
81
+ return null;
81
82
  }
82
83
 
83
84
  }
@@ -1,3 +1,9 @@
1
+ import {browserRuntimePlatformInfo} from './browserRuntime.js';
2
+ import {
3
+ generateMkvScriptForSystemFedora,
4
+ generateMkvScriptForSystemWindows,
5
+ } from './generate.js';
6
+
1
7
  /**
2
8
  *
3
9
  * @param data
@@ -22,8 +28,8 @@ export function serviceSaveContentToLocal(content, filename, ext = 'txt') {
22
28
 
23
29
  const extObj = {
24
30
  txt: 'text/plain',
25
- json: 'application/json'
26
- }
31
+ json: 'application/json',
32
+ };
27
33
  const type = extObj[ext];
28
34
 
29
35
  const file = new Blob([content], {type});
@@ -38,6 +44,26 @@ export function serviceSaveContentToLocal(content, filename, ext = 'txt') {
38
44
  // eleBtn.previousElementSibling
39
45
  }
40
46
 
47
+ /**
48
+ * need install nodejs mkvtoolnix
49
+ * @param message{{
50
+ * vid, title,
51
+ * }}
52
+ * @returns {Promise<void>}
53
+ */
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
+ }
65
+ }
66
+
41
67
  /**
42
68
  *
43
69
  * @param {string} value