@vacantthinker/firefox-addon-framework-easy 2026.605.1024 → 2026.605.1200

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,12 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "none",
4
+ "printWidth": 80,
5
+ "tabWidth": 2,
6
+ "useTabs": true,
7
+ "plugins": [
8
+ "prettier-plugin-jsdoc"
9
+ ],
10
+ "jsdocVerticalAlignment": true,
11
+ "jsdocLineWrappingStyle": "balance"
12
+ }
package/README.md CHANGED
@@ -92,8 +92,8 @@ export class DomainORM extends BaseORM { }
92
92
  ### 📄 File: `src/generate.js`
93
93
  ```javascript
94
94
  export function generateHtmlByUserSettings(
95
- userSettings,
96
- radioItemClickCallback,
95
+ userSettings,
96
+ radioItemClickCallback
97
97
  ) { }
98
98
 
99
99
  export function generateMkvScriptForSystemWindows({ }
@@ -187,7 +187,7 @@ export function serviceGetCurrentDateYYYYMMDDHHMMSS() { }
187
187
  ```javascript
188
188
  export async function serviceCopyContentToClipboard(data) { }
189
189
 
190
- export function serviceSaveContentToLocal(content, filename, ext = 'txt') { }
190
+ export function serviceSaveContentToLocal(content, filename, ext = "txt") { }
191
191
 
192
192
  export async function serviceGenerateMkvToolNixScript({ }
193
193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vacantthinker/firefox-addon-framework-easy",
3
- "version": "2026.0605.1024",
3
+ "version": "2026.0605.1200",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -24,21 +24,7 @@
24
24
  "@types/firefox-webext-browser": "^143.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "prettier": "3.8.3",
28
- "prettier-plugin-jsdoc": "1.8.1"
29
- },
30
- "prettier": {
31
- "semi": true,
32
- "singleQuote": true,
33
- "tabWidth": 2,
34
- "useTabs": false,
35
- "printWidth": 80,
36
- "plugins": [
37
- "prettier-plugin-jsdoc"
38
- ],
39
- "jsdocVerticalizeParams": true,
40
- "jsdocLineWrappingStyle": "balance",
41
- "jsdocPreferToKeepShortOnOneLine": false,
42
- "jsdocKeepUnpublished": false
27
+ "prettier": "^3.8.3",
28
+ "prettier-plugin-jsdoc": "^1.8.1"
43
29
  }
44
30
  }
package/src/generate.js CHANGED
@@ -1,215 +1,211 @@
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.
5
5
  *
6
- * @param {Object} userSettings
7
- * @param {Function} radioItemClickCallback
6
+ * @param {Object} userSettings
7
+ * @param {Function} radioItemClickCallback
8
8
  * @returns {HTMLFieldSetElement[]}
9
9
  */
10
10
  export function generateHtmlByUserSettings(
11
- userSettings,
12
- radioItemClickCallback,
11
+ userSettings,
12
+ radioItemClickCallback
13
13
  ) {
14
- // Keeps track of all generated fieldsets by their storageKey
15
- const elementsMap = {};
16
-
17
- const fieldsets = Object.keys(userSettings).map((storageKey) => {
18
- const storageValue = userSettings[storageKey];
19
- const type = storageValue.type || 'text'; // Default to text if type is not specified
20
-
21
- // Common container wrapper for every configuration item
22
- const eleWrap = document.createElement('fieldset');
23
- const eleTitle = document.createElement('legend');
24
- eleTitle.textContent = storageKey;
25
- eleWrap.append(eleTitle);
26
-
27
- // Save a reference to the wrapper element for visibility switching
28
- elementsMap[storageKey] = eleWrap;
29
-
30
- // --- CONDITION 1: CHECKBOX & RADIO ---
31
- if (type === 'checkbox' || type === 'radio') {
32
- /**
33
- *
34
- * @type {string[]}
35
- */
36
- const options = storageValue.options || [];
37
-
38
- options.map((option) => {
39
- const eleLabel = document.createElement('label');
40
- eleLabel.textContent = option;
41
-
42
- const eleInput = document.createElement('input');
43
- eleInput.name = storageKey;
44
- eleInput.type = type;
45
- eleInput.value = option;
46
-
47
- if (type === 'checkbox') {
48
- stoOpGet(storageKey).then((v) => {
49
- const initialArray = Array.from(v || storageValue.selected || []);
50
- const set = new Set(initialArray);
51
- eleInput.checked = set.has(option);
52
-
53
- // Initial visibility evaluation
54
- triggerVisibility(storageKey, initialArray);
55
- });
56
-
57
- eleInput.addEventListener('change', async () => {
58
- const optionsCurrent = await stoOpGet(storageKey) ||
59
- storageValue.selected || [];
60
- const set = new Set(Array.from(optionsCurrent));
61
-
62
- if (eleInput.checked) {
63
- set.add(option);
64
- }
65
- else {
66
- set.delete(option);
67
- }
68
-
69
- const valueNew = Array.from(set);
70
- await stoOpSet(storageKey, valueNew);
71
-
72
- // Dynamic visibility update
73
- triggerVisibility(storageKey, valueNew);
74
- });
75
- }
76
- else if (type === 'radio') {
77
- stoOpGet(storageKey).then((v) => {
78
- const currentSelected = (v !== undefined && v !== null) ?
79
- v :
80
- storageValue.selected;
81
- if (option === currentSelected) {
82
- eleInput.checked = true;
83
- }
84
-
85
- // Initial visibility evaluation
86
- triggerVisibility(storageKey, currentSelected);
87
- });
88
-
89
- eleLabel.onclick = function () {
90
- stoOpSet(storageKey, option).then(() => {
91
- if (typeof radioItemClickCallback === 'function') {
92
- radioItemClickCallback(storageKey, option);
93
- }
94
-
95
- // Dynamic visibility update
96
- triggerVisibility(storageKey, option);
97
- });
98
- };
99
- }
100
-
101
- eleLabel.append(eleInput);
102
- return eleLabel;
103
- }).forEach((ele) => eleWrap.append(ele));
104
- }
105
-
106
- // --- CONDITION 2: TOGGLE BUTTON ---
107
- else if (type === 'button') {
108
- const eleButton = document.createElement('button');
109
- eleButton.type = 'button'; // Prevent accidental form submissions
110
-
111
- stoOpGet(storageKey).then((v) => {
112
- // Fallback to default schema configuration if no value is stored yet
113
- let currentStatus = (v !== undefined && v !== null) ?
114
- (v === true || v === 'true') :
115
- storageValue.selected;
116
- eleButton.textContent = String(currentStatus);
117
-
118
- // Initial visibility evaluation
119
- triggerVisibility(storageKey, currentStatus);
120
-
121
- eleButton.addEventListener('click', async () => {
122
- currentStatus = !currentStatus; // Toggle state
123
- eleButton.textContent = String(currentStatus);
124
- await stoOpSet(storageKey, currentStatus);
125
-
126
- // Dynamic visibility update
127
- triggerVisibility(storageKey, currentStatus);
128
- });
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
-
140
- stoOpGet(storageKey).then((v) => {
141
- const currentVal = (v !== undefined && v !== null) ?
142
- v :
143
- storageValue.selected;
144
- eleInput.value = currentVal;
145
-
146
- // Initial visibility evaluation
147
- triggerVisibility(storageKey, currentVal);
148
- });
149
-
150
- // Updates storage on every keystroke/change execution
151
- eleInput.addEventListener('input', async () => {
152
- const rawValue = eleInput.value;
153
- const finalizedValue = type === 'number' ? Number(rawValue) : rawValue;
154
-
155
- await stoOpSet(storageKey, finalizedValue);
156
-
157
- // Dynamic visibility update
158
- triggerVisibility(storageKey, finalizedValue);
159
- });
160
-
161
- eleWrap.append(eleInput);
162
- }
163
-
164
- // --- CONDITION 4: SPAN / READ-ONLY TEXT ---
165
- else if (type === 'span') {
166
- const eleSpan = document.createElement('span');
167
- // Optional: Add a class for styling read-only text differently
168
- // eleSpan.className = 'read-only-text';
169
-
170
- stoOpGet(storageKey).then((v) => {
171
- // Fallback to default schema configuration if no value is stored yet
172
- const currentVal = (v !== undefined && v !== null) ?
173
- v :
174
- storageValue.selected;
175
-
176
- // Render as plain text
177
- eleSpan.textContent = String(currentVal);
178
-
179
- // Initial visibility evaluation
180
- triggerVisibility(storageKey, currentVal);
181
- });
182
-
183
- eleWrap.append(eleSpan);
184
- }
185
-
186
- return eleWrap;
187
- });
188
-
189
- /**
190
- * Evaluates the visibility rules for a given source key based on its current value.
191
- */
192
- function triggerVisibility(sourceKey, currentValue) {
193
- const config = userSettings[sourceKey];
194
- if (config && config.visibilityControl) {
195
- const {targetField, expectedValue} = config.visibilityControl;
196
- const targetElement = elementsMap[targetField];
197
-
198
- if (targetElement) {
199
- // String conversion guarantees type safety (e.g., matching boolean true against string "true")
200
- const shouldBeVisible = String(currentValue) === String(expectedValue);
201
- targetElement.style.display = shouldBeVisible ? '' : 'none';
202
- }
203
- }
204
- }
205
-
206
- return fieldsets;
14
+ // Keeps track of all generated fieldsets by their storageKey
15
+ const elementsMap = {};
16
+
17
+ const fieldsets = Object.keys(userSettings).map((storageKey) => {
18
+ const storageValue = userSettings[storageKey];
19
+ const type = storageValue.type || "text"; // Default to text if type is not specified
20
+
21
+ // Common container wrapper for every configuration item
22
+ const eleWrap = document.createElement("fieldset");
23
+ const eleTitle = document.createElement("legend");
24
+ eleTitle.textContent = storageKey;
25
+ eleWrap.append(eleTitle);
26
+
27
+ // Save a reference to the wrapper element for visibility switching
28
+ elementsMap[storageKey] = eleWrap;
29
+
30
+ // --- CONDITION 1: CHECKBOX & RADIO ---
31
+ if (type === "checkbox" || type === "radio") {
32
+ /** @type {string[]} */
33
+ const options = storageValue.options || [];
34
+
35
+ options
36
+ .map((option) => {
37
+ const eleLabel = document.createElement("label");
38
+ eleLabel.textContent = option;
39
+
40
+ const eleInput = document.createElement("input");
41
+ eleInput.name = storageKey;
42
+ eleInput.type = type;
43
+ eleInput.value = option;
44
+
45
+ if (type === "checkbox") {
46
+ stoOpGet(storageKey).then((v) => {
47
+ const initialArray = Array.from(v || storageValue.selected || []);
48
+ const set = new Set(initialArray);
49
+ eleInput.checked = set.has(option);
50
+
51
+ // Initial visibility evaluation
52
+ triggerVisibility(storageKey, initialArray);
53
+ });
54
+
55
+ eleInput.addEventListener("change", async () => {
56
+ const optionsCurrent =
57
+ (await stoOpGet(storageKey)) || storageValue.selected || [];
58
+ const set = new Set(Array.from(optionsCurrent));
59
+
60
+ if (eleInput.checked) {
61
+ set.add(option);
62
+ } else {
63
+ set.delete(option);
64
+ }
65
+
66
+ const valueNew = Array.from(set);
67
+ await stoOpSet(storageKey, valueNew);
68
+
69
+ // Dynamic visibility update
70
+ triggerVisibility(storageKey, valueNew);
71
+ });
72
+ } else if (type === "radio") {
73
+ stoOpGet(storageKey).then((v) => {
74
+ const currentSelected =
75
+ v !== undefined && v !== null ? v : storageValue.selected;
76
+ if (option === currentSelected) {
77
+ eleInput.checked = true;
78
+ }
79
+
80
+ // Initial visibility evaluation
81
+ triggerVisibility(storageKey, currentSelected);
82
+ });
83
+
84
+ eleLabel.onclick = function () {
85
+ stoOpSet(storageKey, option).then(() => {
86
+ if (typeof radioItemClickCallback === "function") {
87
+ radioItemClickCallback(storageKey, option);
88
+ }
89
+
90
+ // Dynamic visibility update
91
+ triggerVisibility(storageKey, option);
92
+ });
93
+ };
94
+ }
95
+
96
+ eleLabel.append(eleInput);
97
+ return eleLabel;
98
+ })
99
+ .forEach((ele) => eleWrap.append(ele));
100
+ }
101
+
102
+ // --- CONDITION 2: TOGGLE BUTTON ---
103
+ else if (type === "button") {
104
+ const eleButton = document.createElement("button");
105
+ eleButton.type = "button"; // Prevent accidental form submissions
106
+
107
+ stoOpGet(storageKey).then((v) => {
108
+ // Fallback to default schema configuration if no value is stored yet
109
+ let currentStatus =
110
+ v !== undefined && v !== null
111
+ ? v === true || v === "true"
112
+ : storageValue.selected;
113
+ eleButton.textContent = String(currentStatus);
114
+
115
+ // Initial visibility evaluation
116
+ triggerVisibility(storageKey, currentStatus);
117
+
118
+ eleButton.addEventListener("click", async () => {
119
+ currentStatus = !currentStatus; // Toggle state
120
+ eleButton.textContent = String(currentStatus);
121
+ await stoOpSet(storageKey, currentStatus);
122
+
123
+ // Dynamic visibility update
124
+ triggerVisibility(storageKey, currentStatus);
125
+ });
126
+ });
127
+
128
+ eleWrap.append(eleButton);
129
+ }
130
+
131
+ // --- CONDITION 3: NUMBER & TEXT INPUTS ---
132
+ else if (type === "number" || type === "text") {
133
+ const eleInput = document.createElement("input");
134
+ eleInput.type = type;
135
+ eleInput.name = storageKey;
136
+
137
+ stoOpGet(storageKey).then((v) => {
138
+ const currentVal =
139
+ v !== undefined && v !== null ? v : storageValue.selected;
140
+ eleInput.value = currentVal;
141
+
142
+ // Initial visibility evaluation
143
+ triggerVisibility(storageKey, currentVal);
144
+ });
145
+
146
+ // Updates storage on every keystroke/change execution
147
+ eleInput.addEventListener("input", async () => {
148
+ const rawValue = eleInput.value;
149
+ const finalizedValue = type === "number" ? Number(rawValue) : rawValue;
150
+
151
+ await stoOpSet(storageKey, finalizedValue);
152
+
153
+ // Dynamic visibility update
154
+ triggerVisibility(storageKey, finalizedValue);
155
+ });
156
+
157
+ eleWrap.append(eleInput);
158
+ }
159
+
160
+ // --- CONDITION 4: SPAN / READ-ONLY TEXT ---
161
+ else if (type === "span") {
162
+ const eleSpan = document.createElement("span");
163
+ // Optional: Add a class for styling read-only text differently
164
+ // eleSpan.className = 'read-only-text';
165
+
166
+ stoOpGet(storageKey).then((v) => {
167
+ // Fallback to default schema configuration if no value is stored yet
168
+ const currentVal =
169
+ v !== undefined && v !== null ? v : storageValue.selected;
170
+
171
+ // Render as plain text
172
+ eleSpan.textContent = String(currentVal);
173
+
174
+ // Initial visibility evaluation
175
+ triggerVisibility(storageKey, currentVal);
176
+ });
177
+
178
+ eleWrap.append(eleSpan);
179
+ }
180
+
181
+ return eleWrap;
182
+ });
183
+
184
+ /**
185
+ * Evaluates the visibility rules for a given source key based on its current
186
+ * value.
187
+ */
188
+ function triggerVisibility(sourceKey, currentValue) {
189
+ const config = userSettings[sourceKey];
190
+ if (config && config.visibilityControl) {
191
+ const { targetField, expectedValue } = config.visibilityControl;
192
+ const targetElement = elementsMap[targetField];
193
+
194
+ if (targetElement) {
195
+ // String conversion guarantees type safety (e.g., matching boolean true against string "true")
196
+ const shouldBeVisible = String(currentValue) === String(expectedValue);
197
+ targetElement.style.display = shouldBeVisible ? "" : "none";
198
+ }
199
+ }
200
+ }
201
+
202
+ return fieldsets;
207
203
  }
208
204
 
209
- export function generateMkvScriptForSystemWindows({vid, title}) {
210
- let args = {vid, title};
205
+ export function generateMkvScriptForSystemWindows({ vid, title }) {
206
+ let args = { vid, title };
211
207
 
212
- return `if (true) {
208
+ return `if (true) {
213
209
  const path = require('path');
214
210
  const fs = require('fs');
215
211
  const {execSync, exec} = require('node:child_process');
@@ -294,11 +290,11 @@ export function generateMkvScriptForSystemWindows({vid, title}) {
294
290
  `;
295
291
  }
296
292
 
297
- export function generateMkvScriptForSystemFedora({vid, title}) {
298
- let args = {vid, title};
293
+ export function generateMkvScriptForSystemFedora({ vid, title }) {
294
+ let args = { vid, title };
299
295
 
300
- // We wrap the Node.js script inside a Linux Shell script block
301
- return `#!/usr/bin/env bash
296
+ // We wrap the Node.js script inside a Linux Shell script block
297
+ return `#!/usr/bin/env bash
302
298
  # This header tells Fedora to treat this file as a runnable bash script
303
299
 
304
300
  # Open a terminal window if not already running inside one
@@ -373,4 +369,3 @@ read unused_input
373
369
  rm -- "$0"
374
370
  `;
375
371
  }
376
-
@@ -1,80 +1,82 @@
1
- import {browserRuntimePlatformInfo} from './browserRuntime.js';
2
- import {generateMkvScriptForSystemFedora, generateMkvScriptForSystemWindows,} from './generate.js';
1
+ import { browserRuntimePlatformInfo } from "./browserRuntime.js";
2
+ import {
3
+ generateMkvScriptForSystemFedora,
4
+ generateMkvScriptForSystemWindows
5
+ } from "./generate.js";
3
6
 
4
7
  /**
5
- *
6
- * @param data
8
+ * @param data
7
9
  * @returns {Promise<void>}
8
10
  */
9
11
  export async function serviceCopyContentToClipboard(data) {
10
- return await window.navigator.clipboard.writeText(data);
12
+ return await window.navigator.clipboard.writeText(data);
11
13
  }
12
14
 
13
15
  /**
14
- * eg: fnName('this is content', 'this is a filename without ext', 'txt')
16
+ * Eg: fnName('this is content', 'this is a filename without ext', 'txt')
17
+ *
15
18
  * @param {string} content
16
- * @param {string} filename eg: abc
17
- * @param {string} ext txt json
19
+ * @param {string} filename Eg: abc
20
+ * @param {string} ext Txt json
18
21
  */
19
- export function serviceSaveContentToLocal(content, filename, ext = 'txt') {
20
- const eleBtn = document.createElement('button');
21
- eleBtn.addEventListener(
22
- 'click',
23
- function () {
24
- const eleA = document.createElement('a');
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");
25
28
 
26
- const extObj = {
27
- txt: 'text/plain',
28
- json: 'application/json',
29
- };
30
- const type = extObj[ext];
29
+ const extObj = {
30
+ txt: "text/plain",
31
+ json: "application/json"
32
+ };
33
+ const type = extObj[ext];
31
34
 
32
- const file = new Blob([content], {type});
33
- eleA.href = URL.createObjectURL(file);
34
- eleA.download = [filename, ext].join('.');
35
- eleA.click();
36
- URL.revokeObjectURL(eleA.href);
37
- },
38
- false,
39
- );
40
- eleBtn.click();
41
- // eleBtn.previousElementSibling
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
42
45
  }
43
46
 
44
47
  /**
45
- * need install nodejs mkvtoolnix
46
- * @param message{{
47
- * vid, title,
48
- * }}
48
+ * Need install nodejs mkvtoolnix
49
+ *
50
+ * @param message{{ Vid, title,
51
+ * }}
49
52
  * @returns {Promise<void>}
50
53
  */
51
- export async function serviceGenerateMkvToolNixScript({vid, title}) {
52
- let message = {vid, title};
53
- const platformInfo = await browserRuntimePlatformInfo();
54
- if (platformInfo.os === 'win') {
55
- let content = generateMkvScriptForSystemWindows(message);
56
- serviceSaveContentToLocal(content, title, 'js');
57
- }
58
- else if (platformInfo.os === 'linux') {
59
- let content = generateMkvScriptForSystemFedora(message);
60
- serviceSaveContentToLocal(content, title, 'sh');
61
- }
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
+ } else if (platformInfo.os === "linux") {
61
+ let content = generateMkvScriptForSystemFedora(message);
62
+ serviceSaveContentToLocal(content, title, "sh");
63
+ }
62
64
  }
63
65
 
64
66
  /**
65
- * @param {string} value -
66
- * @returns {string} -
67
+ * @param {string} value -
68
+ * @returns {string} -
67
69
  */
68
70
  export function serviceRemoveIllegalWord(value) {
69
- if (!value) return '';
71
+ if (!value) return "";
70
72
 
71
- let name = value.trim().split(/\r?\n/).shift();
73
+ let name = value.trim().split(/\r?\n/).shift();
72
74
 
73
- name = name.replace(/[\p{P}\p{S}\p{C}]/gu, ' ');
75
+ name = name.replace(/[\p{P}\p{S}\p{C}]/gu, " ");
74
76
 
75
- name = name.replace(/[~"#%&*:<>?/\\{|}]/g, ' ');
77
+ name = name.replace(/[~"#%&*:<>?/\\{|}]/g, " ");
76
78
 
77
- name = name.replace(/[\s\u3000]+/g, ' ').trim();
79
+ name = name.replace(/[\s\u3000]+/g, " ").trim();
78
80
 
79
- return name.replace(/^[-.]+|[-.]+$/g, '');
80
- }
81
+ return name.replace(/^[-.]+|[-.]+$/g, "");
82
+ }
@@ -109,8 +109,7 @@ export async function serviceElementPicker(message) {
109
109
  selector += '#' + el.id;
110
110
  path.unshift(selector);
111
111
  break;
112
- }
113
- else {
112
+ } else {
114
113
  let sib = el, nth = 1;
115
114
  while (sib = sib.previousElementSibling) {
116
115
  if (sib.nodeName.toLowerCase() === selector) nth++;
@@ -330,16 +329,13 @@ export async function serviceDealWithMagnetLink(message) {
330
329
 
331
330
  if (handleOption === 'clipboard') {
332
331
  await serviceCopyContentToClipboard(content);
333
- }
334
- else if (handleOption === 'txt') {
332
+ } else if (handleOption === 'txt') {
335
333
  serviceSaveContentToLocal(content, filename);
336
- }
337
- else if (handleOption === 'clipboardAndTxt') {
334
+ } else if (handleOption === 'clipboardAndTxt') {
338
335
  await serviceCopyContentToClipboard(content);
339
336
  serviceSaveContentToLocal(content, filename);
340
337
  }
341
- }
342
- else {
338
+ } else {
343
339
  // todo notification => magnet link not found!
344
340
  await browserNotificationCreate('magnet link not found!');
345
341
  }