Files
2026-01-20 21:53:59 +03:30

218 lines
5.8 KiB
JavaScript

// ---------- Bulk Edit (Side Effect) ----------------------
class BulkEdit {
static {
const div = document.querySelector('.bulk-edit');
[this.t1, this.t2, this.s1, this.s2, this.select] = div.children;
this.t1.addEventListener('change', () => this.toggleProxy('t1'));
this.s1.addEventListener('change', () => this.toggleProxy('s1'));
this.t2.addEventListener('change', () => this.togglePattern());
this.select.addEventListener('change', () => this.process());
}
static toggleProxy(i) {
// remove previous selection
document.querySelector(`details.proxy.${i}`)?.classList.remove(i);
const n = this.getNumber(i);
if (!n) { return; }
document.querySelector(`details.proxy:nth-of-type(${n})`)?.classList.add(i);
// reselect t2
this.togglePattern();
}
static togglePattern() {
// remove previous selection
const prev = document.querySelector('.pattern-row.t2');
if (prev) {
prev.classList.remove('t2');
prev.closest('details').open = false;
}
const n = this.getNumber('t2');
if (!n) { return; }
const t = this.getNumber('t1') || this.getNumber('s1');
if (!t) { return; }
const elem = document.querySelector(`details.proxy:nth-of-type(${t}) .pattern-row:nth-of-type(${n})`);
if (elem) {
elem.classList.add('t2');
elem.closest('details').open = true;
}
}
static process() {
if (!this.select.value) { return; }
const id = this.select.value;
switch (id) {
case 'openAll':
case 'closeAll':
this.getProxies().forEach(i => i.open = id === 'openAll');
break;
case 'setType':
case 'setPort':
case 'setTitle':
case 'setUsername':
case 'setPassword':
let s2 = this.s2.value.trim();
if (!s2) { break; }
const ref = id.substring(3).toLowerCase();
if (ref === 'type') {
s2 = s2.toLowerCase();
if (!['http', 'https', 'socks4', 'socks5', 'quic', 'pac', 'direct'].includes(s2)) { break; }
}
document.querySelectorAll(`[data-id="${ref}"]`).forEach(i =>
s2.startsWith('+') ? i.value += s2.substring(1) : i.value = s2);
break;
case 'deleteProxy':
this.deleteProxy();
this.reset();
break;
case 'moveProxy':
this.moveProxy();
this.reset();
break;
case 'movePattern':
this.movePattern();
this.reset();
break;
}
// --- reset
this.select.selectedIndex = 0;
}
static reset() {
document.querySelectorAll('details.proxy:is(.t1, .s1), .pattern-row.t2').forEach(i =>
i.classList.remove('t1', 't2', 's1'));
// ['t1', 't2', 's1'].forEach(i => this[i].value = '');
}
static getProxies() {
return document.querySelectorAll('details.proxy');
}
static getNumber(i) {
return this[i].checkValidity() && this[i].value ? this[i].value : null;
}
static getSourceNumbers() {
const n = this.s2.value.match(/\d+-\d+|\d+/g);
if (!n) { return; }
let arr = [];
n.forEach(i => {
// check if number range e.g. 5-8
const [a, b] = i.split('-');
b ? arr.push(...Array.from({length: b - a + 1}, (_, i) => (a * 1) + i)) : arr.push(a);
});
// map to index (-1), sort, remove duplicates
arr = [...new Set(arr.map(i => i - 1).sort((a, b) => a - b))];
return arr.length ? arr : null;
}
static deleteProxy() {
const n = this.getSourceNumbers();
if (!n) { return; }
const p = this.getProxies();
n.forEach(i => p[i]?.remove());
}
static moveProxy() {
let n = this.getSourceNumbers();
if (!n) { return; }
const t1 = this.t1.value - 1;
const p = this.getProxies();
// filter target, map to elements, filter non-existing
n = n.filter(i => i !== t1).map(i => p[i]).filter(Boolean);
if (!n[0]) { return; }
// before target or after all
p[t1] ? p[t1].before(...n) : p[0].parentElement.append(...n);
}
static movePattern() {
const t1 = this.t1.value - 1;
const s1 = this.s1.value - 1;
switch (true) {
// move withing the same proxy
case t1 === -1 || t1 === s1:
s1 !== -1 && this.movePatternWithin(s1);
break;
// move all patterns to target
case s1 === -1:
this.movePatternAll(t1);
break;
// move source patterns to target
default:
this.movePatternSome(t1, s1);
}
}
static movePatternWithin(s1) {
let n = this.getSourceNumbers();
if (!n) { return; }
const p = this.getProxies();
if (!p[s1]) { return; }
const t2 = this.t2.value - 1;
// filter target, map to elements, filter non-existing
const pat = p[s1].querySelectorAll('.pattern-row');
n = n.filter(i => i !== t2).map(i => pat[i]).filter(Boolean);
if (!n[0]) { return; }
pat[t2] ? pat[t2].before(...n) : pat[0].parentElement.append(...n);
}
static movePatternAll(t1) {
const n = this.getSourceNumbers();
if (!n) { return; }
const p = this.getProxies();
if (!p[t1]) { return; }
// filter target, map to elements
const pat = [];
n.filter(i => i !== t1).forEach(i => p[i] && pat.push(...p[i].querySelectorAll('.pattern-row')));
const target = p[t1].querySelector('.pattern-box');
const row = target.children?.[this.t2.value - 1];
row ? row.before(...pat) : target.append(...pat);
}
static movePatternSome(t1, s1) {
let n = this.getSourceNumbers();
if (!n) { return; }
const p = this.getProxies();
if (!p[t1] || !p[s1]) { return; }
// map to elements, filter non-existing
const pat = p[s1].querySelectorAll('.pattern-row');
n = n.map(i => pat[i]).filter(Boolean);
if (!n[0]) { return; }
const target = p[t1].querySelector('.pattern-box');
const row = target.children?.[this.t2.value - 1];
row ? row.before(...n) : target.append(...n);
}
}