@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 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
- content,
37
- title = browserRuntimeManifestName(),
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
- url = '',
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
- userSettings,
96
- radioItemClickCallback
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
- serverUrl,
171
- message,
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 = "txt") { }
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.0751",
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
- 'Cannot construct BaseORM instances directly (Abstract Class).');
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.');
@@ -6,18 +6,18 @@
6
6
  * @returns {Promise<void>}
7
7
  */
8
8
  export async function browserDownloadByDownlink(
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
- }
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
- content,
11
- title = browserRuntimeManifestName(),
10
+ content,
11
+ title = browserRuntimeManifestName(),
12
12
  ) {
13
13
 
14
14
  const tag = 'browserNotificationCreate';
@@ -7,7 +7,7 @@ export function browserRuntimeReload() {
7
7
  * @param url{string}
8
8
  */
9
9
  export async function browserRuntimeSetUninstallURL(
10
- url = '',
10
+ url = '',
11
11
  ) {
12
12
  await browser.runtime.setUninstallURL(url);
13
13
  }
@@ -4,34 +4,35 @@ import {browserTabSendMessage} from './browserTab.js';
4
4
  import {browserNotificationCreate} from './browserNotification.js';
5
5
 
6
6
  /**
7
- * offer common act <=> function, eg: actRemoveTab, actLog
7
+ * Offer common act <=> function
8
8
  *
9
9
  * @param act{
10
- * |'actLog'
11
- * |'actRequestTabIdTabUrl'
12
- * |'actNotification'
13
- * |'actRemoveTab'
14
- * |'actDownloadFile'
15
- * |'actSendMessageToTab'
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
- async function lis(tabId, changeInfo) {
14
- if (changeInfo.status === 'complete') {
15
- browser.tabs.onUpdated.removeListener(lis);
16
- await browserTabSendMessage(tabId, message);
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
- async function lis(tabId, changeInfo) {
45
- if (changeInfo.status === 'complete') {
46
- browser.tabs.onUpdated.removeListener(lis);
47
- // todo code here
48
- await tabOpRemove(tabId);
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
- 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}));
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
- async function lis(tabId, changeInfo) {
95
- if (changeInfo.status === 'complete') {
96
- browser.tabs.onUpdated.removeListener(lis);
97
- // todo code here
98
- await tabOpRemove(tabId);
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 { stoOpGet, stoOpSet } from "./opStorage.js";
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
- let timeout;
6
- return function (...args) {
7
- clearTimeout(timeout);
8
- timeout = setTimeout(() => func.apply(this, args), wait);
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
- userSettings,
21
- radioItemClickCallback
20
+ userSettings,
21
+ radioItemClickCallback,
22
22
  ) {
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;
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({ vid, title }) {
198
- let args = { vid, title };
198
+ export function generateMkvScriptForSystemWindows({vid, title}) {
199
+ let args = {vid, title};
199
200
 
200
- return `if (true) {
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({ vid, title }) {
286
- let args = { vid, title };
286
+ export function generateMkvScriptForSystemFedora({vid, title}) {
287
+ let args = {vid, title};
287
288
 
288
- // We wrap the Node.js script inside a Linux Shell script block
289
- return `#!/usr/bin/env bash
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
@@ -5,8 +5,8 @@
5
5
  * @returns {Promise<Response>}
6
6
  */
7
7
  export async function servicePostJson(
8
- serverUrl,
9
- message,
8
+ serverUrl,
9
+ message,
10
10
  ) {
11
11
 
12
12
  let body = JSON.stringify(message);
@@ -1,15 +1,15 @@
1
- import { browserRuntimePlatformInfo } from "./browserRuntime.js";
1
+ import {browserRuntimePlatformInfo} from './browserRuntime.js';
2
2
  import {
3
- generateMkvScriptForSystemFedora,
4
- generateMkvScriptForSystemWindows
5
- } from "./generate.js";
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
- return await window.navigator.clipboard.writeText(data);
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 = "txt") {
23
- const eleBtn = document.createElement("button");
24
- eleBtn.addEventListener(
25
- "click",
26
- function () {
27
- 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');
28
28
 
29
- const extObj = {
30
- txt: "text/plain",
31
- json: "application/json"
32
- };
33
- const type = extObj[ext];
29
+ const extObj = {
30
+ txt: 'text/plain',
31
+ json: 'application/json',
32
+ };
33
+ const type = extObj[ext];
34
34
 
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
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({ 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
- }
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
- if (!value) return "";
72
+ if (!value) return '';
72
73
 
73
- let name = value.trim().split(/\r?\n/).shift();
74
+ let name = value.trim().split(/\r?\n/).shift();
74
75
 
75
- name = name.replace(/[\p{P}\p{S}\p{C}]/gu, " ");
76
+ name = name.replace(/[\p{P}\p{S}\p{C}]/gu, ' ');
76
77
 
77
- name = name.replace(/[~"#%&*:<>?/\\{|}]/g, " ");
78
+ name = name.replace(/[~"#%&*:<>?/\\{|}]/g, ' ');
78
79
 
79
- name = name.replace(/[\s\u3000]+/g, " ").trim();
80
+ name = name.replace(/[\s\u3000]+/g, ' ').trim();
80
81
 
81
- return name.replace(/^[-.]+|[-.]+$/g, "");
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
- tabId,
25
- filename,
26
- rect,
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
- {dataURI, filename},
34
+ {},
35
+ {dataURI, filename},
36
36
  );
37
37
  await browser.scripting.executeScript({
38
38
  target: {tabId},
39
39
  args: [assign],
40
- func: function (message) {
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 (message) {
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
- } else {
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
- 'important');
140
+ 'important');
140
141
  overlay.style.setProperty('height', `${clientRect.height}px`,
141
- 'important');
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
- {}, // Start with a fresh, empty object
168
- message, // Put the original message first so it doesn't overwrite your new data
169
- {rect},
170
- {
171
- // The guaranteed unique CSS path (e.g., "div#wrap > ul > li:nth-of-type(2)")
172
- uniqueSelector: getUniqueSelector(target),
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
- message,
229
- {rect},
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
- 'div, span, td, p, a, button');
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
- message,
297
- {
298
- data: findAllMagnetLinks(),
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
- } else if (handleOption === 'txt') {
333
+ }
334
+ else if (handleOption === 'txt') {
333
335
  serviceSaveContentToLocal(content, filename);
334
- } else if (handleOption === 'clipboardAndTxt') {
336
+ }
337
+ else if (handleOption === 'clipboardAndTxt') {
335
338
  await serviceCopyContentToClipboard(content);
336
339
  serviceSaveContentToLocal(content, filename);
337
340
  }
338
- } else {
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
- document.body,
35
- NodeFilter.SHOW_TEXT,
36
- null,
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
- document.body,
100
- NodeFilter.SHOW_ELEMENT,
101
- null,
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
- .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
- });
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
@@ -1,12 +0,0 @@
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
- }