@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.
- package/.prettierrc.json +12 -0
- package/README.md +4 -4
- package/package.json +3 -17
- package/src/browserDownload.js +11 -8
- package/src/generate.js +195 -208
- package/src/serviceOpContent.js +55 -53
- package/src/serviceOpJavascript.js +4 -8
package/.prettierrc.json
ADDED
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
|
-
|
|
96
|
-
|
|
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 =
|
|
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.
|
|
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
|
}
|
package/src/browserDownload.js
CHANGED
|
@@ -10,11 +10,14 @@ export async function browserDownloadByDownlink(
|
|
|
10
10
|
downlink,
|
|
11
11
|
filename = null,
|
|
12
12
|
}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
|
7
|
-
* @param
|
|
8
|
-
* @returns {
|
|
15
|
+
* @param {Object} userSettings
|
|
16
|
+
* @param {Function} radioItemClickCallback
|
|
17
|
+
* @returns {Promise<DocumentFragment>}
|
|
9
18
|
*/
|
|
10
|
-
export function generateHtmlByUserSettings(
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
export async function generateHtmlByUserSettings(
|
|
20
|
+
userSettings,
|
|
21
|
+
radioItemClickCallback
|
|
13
22
|
) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
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
|
-
|
|
197
|
+
export function generateMkvScriptForSystemWindows({ vid, title }) {
|
|
198
|
+
let args = { vid, title };
|
|
211
199
|
|
|
212
|
-
|
|
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
|
-
|
|
285
|
+
export function generateMkvScriptForSystemFedora({ vid, title }) {
|
|
286
|
+
let args = { vid, title };
|
|
299
287
|
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
package/src/serviceOpContent.js
CHANGED
|
@@ -1,80 +1,82 @@
|
|
|
1
|
-
import {browserRuntimePlatformInfo} from
|
|
2
|
-
import {
|
|
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
|
-
|
|
12
|
+
return await window.navigator.clipboard.writeText(data);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Eg: fnName('this is content', 'this is a filename without ext', 'txt')
|
|
17
|
+
*
|
|
15
18
|
* @param {string} content
|
|
16
|
-
* @param {string} filename
|
|
17
|
-
* @param {string} ext
|
|
19
|
+
* @param {string} filename Eg: abc
|
|
20
|
+
* @param {string} ext Txt json
|
|
18
21
|
*/
|
|
19
|
-
export function serviceSaveContentToLocal(content, filename, ext =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const extObj = {
|
|
30
|
+
txt: "text/plain",
|
|
31
|
+
json: "application/json"
|
|
32
|
+
};
|
|
33
|
+
const type = extObj[ext];
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
66
|
-
* @returns {string}
|
|
67
|
+
* @param {string} value -
|
|
68
|
+
* @returns {string} -
|
|
67
69
|
*/
|
|
68
70
|
export function serviceRemoveIllegalWord(value) {
|
|
69
|
-
|
|
71
|
+
if (!value) return "";
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
let name = value.trim().split(/\r?\n/).shift();
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
name = name.replace(/[\p{P}\p{S}\p{C}]/gu, " ");
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
name = name.replace(/[~"#%&*:<>?/\\{|}]/g, " ");
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
name = name.replace(/[\s\u3000]+/g, " ").trim();
|
|
78
80
|
|
|
79
|
-
|
|
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
|
}
|