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

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
@@ -91,9 +91,9 @@ export class DomainORM extends BaseORM { }
91
91
 
92
92
  ### 📄 File: `src/generate.js`
93
93
  ```javascript
94
- export function generateHtmlByUserSettings(
95
- userSettings,
96
- radioItemClickCallback,
94
+ export async function generateHtmlByUserSettings(
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.0606.0751",
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
  }
@@ -10,11 +10,14 @@ export async function browserDownloadByDownlink(
10
10
  downlink,
11
11
  filename = null,
12
12
  }) {
13
-
14
- let url = downlink;
15
- let options = {url};
16
- if (filename) {
17
- Object.assign(options, {filename});
18
- }
19
- await browser.downloads.download(options);
20
- }
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
+ }
package/src/generate.js CHANGED
@@ -1,215 +1,203 @@
1
- import {stoOpGet, stoOpSet} from './opStorage.js';
1
+ import { stoOpGet, stoOpSet } from "./opStorage.js";
2
+
3
+ // [Optimization 1] Introduce a debounce function to prevent triggering storage writes on every keystroke.
4
+ function debounce(func, wait) {
5
+ let timeout;
6
+ return function (...args) {
7
+ clearTimeout(timeout);
8
+ timeout = setTimeout(() => func.apply(this, args), wait);
9
+ };
10
+ }
2
11
 
3
12
  /**
4
13
  * Generates HTML elements based on a user settings schema object.
5
14
  *
6
- * @param {Object} userSettings
7
- * @param {Function} radioItemClickCallback
8
- * @returns {HTMLFieldSetElement[]}
15
+ * @param {Object} userSettings
16
+ * @param {Function} radioItemClickCallback
17
+ * @returns {Promise<DocumentFragment>}
9
18
  */
10
- export function generateHtmlByUserSettings(
11
- userSettings,
12
- radioItemClickCallback,
19
+ export async function generateHtmlByUserSettings(
20
+ userSettings,
21
+ radioItemClickCallback
13
22
  ) {
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;
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
+ } else if (type === "radio") {
94
+ if (option === initialValue) {
95
+ eleInput.checked = true; // Apply state synchronously
96
+ }
97
+
98
+ // It is recommended to bind the 'change' event to the input rather than 'onclick' to the label
99
+ eleInput.addEventListener("change", () => {
100
+ stoOpSet(storageKey, option).then(() => {
101
+ if (typeof radioItemClickCallback === "function") {
102
+ radioItemClickCallback(storageKey, option);
103
+ }
104
+ triggerVisibility(storageKey, option);
105
+ });
106
+ });
107
+ }
108
+
109
+ // Fix: Native HTML typically places the input element before the text node inside a label.
110
+ eleLabel.prepend(eleInput);
111
+ eleWrap.append(eleLabel);
112
+ });
113
+ }
114
+
115
+ // --- CONDITION 2: TOGGLE BUTTON ---
116
+ else if (type === "button") {
117
+ const eleButton = document.createElement("button");
118
+ eleButton.type = "button";
119
+
120
+ let currentStatus = initialValue === true || initialValue === "true";
121
+ eleButton.textContent = String(currentStatus); // Apply state synchronously
122
+
123
+ eleButton.addEventListener("click", async () => {
124
+ currentStatus = !currentStatus;
125
+ eleButton.textContent = String(currentStatus);
126
+ await stoOpSet(storageKey, currentStatus);
127
+ triggerVisibility(storageKey, currentStatus);
128
+ });
129
+
130
+ eleWrap.append(eleButton);
131
+ }
132
+
133
+ // --- CONDITION 3: NUMBER & TEXT INPUTS ---
134
+ else if (type === "number" || type === "text") {
135
+ const eleInput = document.createElement("input");
136
+ eleInput.type = type;
137
+ eleInput.name = storageKey;
138
+ eleInput.value = initialValue !== undefined ? initialValue : ""; // Apply state synchronously
139
+
140
+ // [Optimization 4] Wrap the write operation with debounce, delaying storage writes by 500ms.
141
+ // This eliminates UI freezing regardless of typing speed.
142
+ const debouncedSave = debounce(async (val) => {
143
+ const finalizedValue = type === "number" ? Number(val) : val;
144
+ await stoOpSet(storageKey, finalizedValue);
145
+ }, 500);
146
+
147
+ eleInput.addEventListener("input", () => {
148
+ const rawValue = eleInput.value;
149
+ debouncedSave(rawValue);
150
+ triggerVisibility(storageKey, rawValue); // Visual UI updates do not require a delay
151
+ });
152
+
153
+ eleWrap.append(eleInput);
154
+ }
155
+
156
+ // --- CONDITION 4: SPAN / READ-ONLY TEXT ---
157
+ else if (type === "span") {
158
+ const eleSpan = document.createElement("span");
159
+ eleSpan.textContent = String(initialValue); // Apply state synchronously
160
+ eleWrap.append(eleSpan);
161
+ }
162
+ });
163
+
164
+ // [Optimization 5] Third pass: Globally trigger a single visibility check.
165
+ // At this point, all DOM elements exist and all initialValues are fully derived,
166
+ // preventing undefined errors or invalid target hiding.
167
+ keys.forEach((storageKey) => {
168
+ const storedValue = storageData[storageKey];
169
+ const initialValue =
170
+ storedValue !== undefined && storedValue !== null
171
+ ? storedValue
172
+ : userSettings[storageKey].selected;
173
+
174
+ triggerVisibility(storageKey, initialValue);
175
+ });
176
+
177
+ /**
178
+ * Evaluates the visibility rules for a given source key based on its current
179
+ * value.
180
+ */
181
+ function triggerVisibility(sourceKey, currentValue) {
182
+ const config = userSettings[sourceKey];
183
+ if (config && config.visibilityControl) {
184
+ const { targetField, expectedValue } = config.visibilityControl;
185
+ const targetElement = elementsMap[targetField];
186
+
187
+ if (targetElement) {
188
+ const shouldBeVisible = String(currentValue) === String(expectedValue);
189
+ targetElement.style.display = shouldBeVisible ? "" : "none";
190
+ }
191
+ }
192
+ }
193
+
194
+ return fragment;
207
195
  }
208
196
 
209
- export function generateMkvScriptForSystemWindows({vid, title}) {
210
- let args = {vid, title};
197
+ export function generateMkvScriptForSystemWindows({ vid, title }) {
198
+ let args = { vid, title };
211
199
 
212
- return `if (true) {
200
+ return `if (true) {
213
201
  const path = require('path');
214
202
  const fs = require('fs');
215
203
  const {execSync, exec} = require('node:child_process');
@@ -294,11 +282,11 @@ export function generateMkvScriptForSystemWindows({vid, title}) {
294
282
  `;
295
283
  }
296
284
 
297
- export function generateMkvScriptForSystemFedora({vid, title}) {
298
- let args = {vid, title};
285
+ export function generateMkvScriptForSystemFedora({ vid, title }) {
286
+ let args = { vid, title };
299
287
 
300
- // We wrap the Node.js script inside a Linux Shell script block
301
- return `#!/usr/bin/env bash
288
+ // We wrap the Node.js script inside a Linux Shell script block
289
+ return `#!/usr/bin/env bash
302
290
  # This header tells Fedora to treat this file as a runnable bash script
303
291
 
304
292
  # Open a terminal window if not already running inside one
@@ -373,4 +361,3 @@ read unused_input
373
361
  rm -- "$0"
374
362
  `;
375
363
  }
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
  }