mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 16:25:13 +08:00
🍉 Sync 2026-06-12 21:11:08
This commit is contained in:
1 parent
a42ec416b0
commit
e073b2d2a1
29 files changed
+646
-185
No files matched your search
@@ -1,12 +1,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=boltbrowser
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/br0xen/boltbrowser.git
|
||||
PKG_SOURCE_DATE:=2023-05-31
|
||||
PKG_SOURCE_VERSION:=898a1e52a566bd63350c406118f7c6627f5303e9
|
||||
PKG_SOURCE_VERSION:=6ad3511ddb3c271fbaed2f22e409aa453ed89042
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-daede
|
||||
PKG_VERSION:=1.7
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=1.10
|
||||
PKG_RELEASE:=3
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
|
||||
@@ -75,9 +75,12 @@ function detectInstalledBackends() {
|
||||
}
|
||||
|
||||
function detectRunning() {
|
||||
/* pidof matches the program name exactly and, unlike busybox `pgrep -x`
|
||||
(which compares the full cmdline `/usr/bin/dae run …` and never hits),
|
||||
reliably tells dae from daed without substring false positives. */
|
||||
return Promise.all([
|
||||
execOk('/usr/bin/pgrep', ['-x', 'dae']),
|
||||
execOk('/usr/bin/pgrep', ['-x', 'daed'])
|
||||
execOk('/bin/pidof', ['dae']),
|
||||
execOk('/bin/pidof', ['daed'])
|
||||
]).then(function(r) {
|
||||
return { dae: r[0], daed: r[1] };
|
||||
});
|
||||
|
||||
@@ -114,6 +114,29 @@ function ellipsisCell(section_id) {
|
||||
return E('span', { 'title': v, 'style': 'font-family:ui-monospace,Menlo,monospace;font-size:12px' }, short);
|
||||
}
|
||||
|
||||
/* Tag is auto-generated and rarely relevant to beginners — render it muted. */
|
||||
function mutedCell(section_id) {
|
||||
const v = this.cfgvalue(section_id) || '';
|
||||
return E('span', { 'class': 'dd-meta', 'style': 'opacity:.55;font-size:11.5px' }, v);
|
||||
}
|
||||
|
||||
/* Fill blank tags of a section type with unique <prefix>_<n> values so dae's
|
||||
config generator always has a stable identifier. Runs after m.save() wrote
|
||||
the form values into the uci session, before uci.save(). */
|
||||
function autofillTags(stype, prefix) {
|
||||
const secs = uci.sections('dae', stype) || [];
|
||||
const used = {};
|
||||
secs.forEach(function(s) { if (s.tag) used[s.tag] = 1; });
|
||||
let n = 1;
|
||||
secs.forEach(function(s) {
|
||||
if (s.tag) return;
|
||||
while (used[prefix + '_' + n]) n++;
|
||||
const t = prefix + '_' + n;
|
||||
uci.set('dae', s['.name'], 'tag', t);
|
||||
used[t] = 1; n++;
|
||||
});
|
||||
}
|
||||
|
||||
/* Wrap every form section in a collapsible accordion. openTitles lists the
|
||||
section titles that should start expanded; the rest start collapsed. */
|
||||
function accordionizeSections(mapNode, openTitles) {
|
||||
@@ -156,7 +179,8 @@ function renderDaeForms(ctx) {
|
||||
s.anonymous = true;
|
||||
s.sortable = false;
|
||||
o = s.option(form.Value, 'tag', _('Tag'));
|
||||
o.placeholder = 'my_sub';
|
||||
o.placeholder = _('auto-generated');
|
||||
o.textvalue = mutedCell;
|
||||
o = s.option(form.Value, 'url', _('Subscription URL'));
|
||||
o.rmempty = false;
|
||||
o.placeholder = 'https://example.com/sub';
|
||||
@@ -169,65 +193,16 @@ function renderDaeForms(ctx) {
|
||||
o = s.option(form.Flag, 'enabled', _('Enabled'));
|
||||
o.default = '1';
|
||||
o.editable = true;
|
||||
/* add an Update button next to Edit/Delete in each row; dae re-pulls all
|
||||
subscriptions on reload, so the action is global regardless of the row */
|
||||
const origRowActions = s.renderRowActions;
|
||||
s.renderRowActions = function(section_id, more_label) {
|
||||
const cell = origRowActions.call(this, section_id, more_label);
|
||||
/* only real subscription links (http/https) are re-fetchable; fixed share
|
||||
links (vless:// ss:// …) are static. Keep the button on every row for a
|
||||
consistent layout, but grey it out (disabled) on the fixed ones. */
|
||||
const subUrl = uci.get('dae', section_id, 'url') || '';
|
||||
const fetchable = /^https?:\/\//i.test(subUrl);
|
||||
const btn = E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'style': 'margin-right:.25em',
|
||||
'disabled': fetchable ? null : '',
|
||||
'title': fetchable ? '' : _('Fixed link — nothing to fetch.')
|
||||
}, _('Update'));
|
||||
const upMsg = E('span', { 'class': 'dd-meta', 'style': 'margin-left:8px;display:none' }, '');
|
||||
function setUpMsg(text, kind) {
|
||||
upMsg.textContent = text;
|
||||
upMsg.style.display = '';
|
||||
upMsg.style.color = (kind === 'err') ? 'var(--error-color, #d33)' : (kind === 'ok') ? 'var(--success-color, #2a8)' : '';
|
||||
if (kind !== 'err')
|
||||
setTimeout(function() { if (upMsg.textContent === text) upMsg.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (fetchable) {
|
||||
btn.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
btn.disabled = true;
|
||||
setUpMsg(_('Updating…'));
|
||||
backend.detectRunning().then(function(r) {
|
||||
if (!r || !r.dae)
|
||||
return setUpMsg(_('dae is stopped; it fetches subscriptions on start.'), 'warn');
|
||||
return fs.exec(backend.BACKENDS.dae.initd, ['hot_reload']).then(function(res) {
|
||||
if (res && res.code !== 0)
|
||||
setUpMsg(_('Update failed: %s').format(res.stderr || res.stdout || ('exit ' + res.code)), 'err');
|
||||
else
|
||||
setUpMsg(_('Subscriptions updated (dae reloaded)'), 'ok');
|
||||
});
|
||||
}).catch(function(e) {
|
||||
setUpMsg(_('Update failed: %s').format(e.message || e), 'err');
|
||||
}).finally(function() { btn.disabled = false; });
|
||||
});
|
||||
}
|
||||
const firstBtn = cell.querySelector('button, a.cbi-button');
|
||||
if (firstBtn && firstBtn.parentNode) firstBtn.parentNode.insertBefore(btn, firstBtn);
|
||||
else cell.appendChild(btn);
|
||||
if (btn.parentNode) btn.parentNode.insertBefore(upMsg, btn.nextSibling);
|
||||
else cell.appendChild(upMsg);
|
||||
return cell;
|
||||
};
|
||||
|
||||
/* Manual nodes (share links) */
|
||||
s = m.section(form.GridSection, 'node', _('Nodes'),
|
||||
s = m.section(form.GridSection, 'node', _('Manual nodes'),
|
||||
_('One node share link per line — ss, vmess, vless, trojan, tuic, hysteria2, socks5.'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.sortable = false;
|
||||
o = s.option(form.Value, 'tag', _('Tag'));
|
||||
o.placeholder = 'node1';
|
||||
o.placeholder = _('auto-generated');
|
||||
o.textvalue = mutedCell;
|
||||
o = s.option(form.Value, 'link', _('Share Link'));
|
||||
o.rmempty = false;
|
||||
o.placeholder = 'vmess://...';
|
||||
@@ -325,55 +300,98 @@ function renderDaeForms(ctx) {
|
||||
statusTimer = setTimeout(function() { status.classList.remove('show'); }, hold || 3000);
|
||||
}
|
||||
|
||||
const save = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save and Hot Reload'));
|
||||
/* one save pipeline, parameterised by intent:
|
||||
- opts.start: after generate, enable + start dae (stopped → "Save and Start")
|
||||
- otherwise just save + generate; generate hot-reloads itself when dae runs */
|
||||
function doSave(opts, btns) {
|
||||
btns.forEach(function(b) { b.disabled = true; });
|
||||
flash(_('Saving…'));
|
||||
/* ensure the singleton sections exist before parsing the form —
|
||||
on installs where uci-defaults never seeded them, writing an
|
||||
option to a missing named section fails with ubus NOT_FOUND (4) */
|
||||
if (!uci.get('dae', 'routing')) uci.add('dae', 'routing', 'routing');
|
||||
if (!uci.get('dae', 'dns')) uci.add('dae', 'dns', 'dns');
|
||||
if (!uci.get('dae', 'config')) uci.add('dae', 'dae', 'config');
|
||||
return m.save(null, true)
|
||||
.then(function() {
|
||||
/* assign unique tags to any rows the user left blank */
|
||||
autofillTags('subscription', 'subscription');
|
||||
autofillTags('node', 'node');
|
||||
})
|
||||
.then(function() { return uci.save(); })
|
||||
.then(function() {
|
||||
/* apply flushes the rpc session to /etc/config so the
|
||||
generator (reads /etc/config/dae) sees the form data;
|
||||
skip it when nothing changed — apply on an empty
|
||||
changeset throws ubus NO_DATA (code 5). */
|
||||
return uci.changes().then(function(ch) {
|
||||
return (ch && Object.keys(ch).length) ? uci.apply() : null;
|
||||
});
|
||||
})
|
||||
.then(function() { return fs.exec(GEN, ['generate']); })
|
||||
.then(function(res) {
|
||||
if (res && res.code !== 0) {
|
||||
const err = (res.stderr || res.stdout || ('exit ' + res.code)).trim().split('\n')[0];
|
||||
flash(_('Apply failed: %s').format(err), 'err', 9000);
|
||||
throw new Error('gen-failed');
|
||||
}
|
||||
if (!opts.start) {
|
||||
/* generate already hot-reloaded if dae was running */
|
||||
flash(opts.okMsg || _('Saved'), 'ok');
|
||||
setTimeout(function() { window.location.reload(); }, 900);
|
||||
return;
|
||||
}
|
||||
flash(_('Starting…'));
|
||||
return fs.exec('/sbin/uci', ['set', 'dae.config.enabled=1'])
|
||||
.then(function() { return fs.exec('/sbin/uci', ['commit', 'dae']); })
|
||||
.then(function() { return fs.exec(backend.BACKENDS.dae.initd, ['enable']); })
|
||||
.then(function() { return fs.exec(backend.BACKENDS.dae.initd, ['start']); })
|
||||
.then(function(r2) {
|
||||
if (r2 && r2.code !== 0)
|
||||
flash(_('Start failed: %s').format(r2.stderr || r2.stdout || ('exit ' + r2.code)), 'err', 9000);
|
||||
else {
|
||||
flash(_('Saved · started'), 'ok');
|
||||
setTimeout(function() { window.location.reload(); }, 900);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function(e) {
|
||||
if (e && e.message === 'gen-failed') return;
|
||||
if (e && e.name === 'CBIValidationError') {
|
||||
flash(_('Please fix the highlighted fields.'), 'err', 6000);
|
||||
return;
|
||||
}
|
||||
flash(_('Save failed: %s').format(e.message || e), 'err', 9000);
|
||||
})
|
||||
.finally(function() { btns.forEach(function(b) { b.disabled = false; }); });
|
||||
}
|
||||
|
||||
return m.render().then(function(mapNode) {
|
||||
save.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
save.disabled = true;
|
||||
flash(_('Saving…'));
|
||||
/* ensure the singleton sections exist before parsing the form —
|
||||
on installs where uci-defaults never seeded them, writing an
|
||||
option to a missing named section fails with ubus NOT_FOUND (4) */
|
||||
if (!uci.get('dae', 'routing')) uci.add('dae', 'routing', 'routing');
|
||||
if (!uci.get('dae', 'dns')) uci.add('dae', 'dns', 'dns');
|
||||
if (!uci.get('dae', 'config')) uci.add('dae', 'dae', 'config');
|
||||
/* commit dae UCI (apply flushes the rpc session to /etc/config,
|
||||
which a CLI `uci commit` cannot see), then regenerate + hot reload */
|
||||
m.save(null, true)
|
||||
.then(function() { return uci.save(); })
|
||||
.then(function() {
|
||||
/* apply flushes the rpc session to /etc/config so the
|
||||
generator (reads /etc/config/dae) sees the form data;
|
||||
skip it when nothing changed — apply on an empty
|
||||
changeset throws ubus NO_DATA (code 5). */
|
||||
return uci.changes().then(function(ch) {
|
||||
return (ch && Object.keys(ch).length) ? uci.apply() : null;
|
||||
});
|
||||
})
|
||||
.then(function() { return fs.exec(GEN, ['generate']); })
|
||||
.then(function(res) {
|
||||
if (res && res.code !== 0) {
|
||||
const err = (res.stderr || res.stdout || ('exit ' + res.code)).trim().split('\n')[0];
|
||||
flash(_('Apply failed: %s').format(err), 'err', 9000);
|
||||
return;
|
||||
}
|
||||
/* reload to reflect committed state and clear the change badge */
|
||||
flash(_('Saved · validated · hot-reloaded'), 'ok');
|
||||
setTimeout(function() { window.location.reload(); }, 900);
|
||||
})
|
||||
.catch(function(e) {
|
||||
if (e && e.name === 'CBIValidationError') {
|
||||
flash(_('Please fix the highlighted fields.'), 'err', 6000);
|
||||
return;
|
||||
}
|
||||
flash(_('Save failed: %s').format(e.message || e), 'err', 9000);
|
||||
})
|
||||
.finally(function() { save.disabled = false; });
|
||||
});
|
||||
|
||||
/* make each form section collapsible; keep the two everyday ones open */
|
||||
accordionizeSections(mapNode, [ _('Subscriptions'), _('Nodes') ]);
|
||||
/* beginner default: only Subscriptions starts open; nodes/groups/routing/dns/logging collapse */
|
||||
accordionizeSections(mapNode, [ _('Subscriptions') ]);
|
||||
|
||||
/* fold groups / routing / dns / logging under one "Advanced settings"
|
||||
collapsible so the everyday view is just subscriptions + nodes */
|
||||
const advTitles = [ _('Groups'), _('Routing'), _('DNS'), _('Logging') ];
|
||||
const advWraps = [];
|
||||
mapNode.querySelectorAll('.dd-adv').forEach(function(w) {
|
||||
const t = w.querySelector('.dd-adv-bar span');
|
||||
if (t && advTitles.indexOf(t.textContent.trim()) >= 0) advWraps.push(w);
|
||||
});
|
||||
if (advWraps.length) {
|
||||
const outerBody = E('div', { 'class': 'dd-adv-body' });
|
||||
const outer = E('div', { 'class': 'dd-adv dd-closed' }, [
|
||||
E('div', { 'class': 'dd-adv-bar' }, [
|
||||
E('span', {}, _('Advanced settings')),
|
||||
E('span', { 'class': 'dd-adv-chevron' }, '›')
|
||||
]),
|
||||
outerBody
|
||||
]);
|
||||
outer.firstChild.addEventListener('click', function() { outer.classList.toggle('dd-closed'); });
|
||||
advWraps[0].parentNode.insertBefore(outer, advWraps[0]);
|
||||
advWraps.forEach(function(w) { outerBody.appendChild(w); });
|
||||
}
|
||||
|
||||
const cardChildren = [
|
||||
E('h4', { 'class': 'dd-card-title' }, _('dae Configuration')),
|
||||
@@ -381,9 +399,72 @@ function renderDaeForms(ctx) {
|
||||
_('Add a subscription or node, then save — it takes effect automatically.'))
|
||||
];
|
||||
cardChildren.push(mapNode);
|
||||
cardChildren.push(E('div', { 'class': 'dd-editor-actions' }, [ save, status ]));
|
||||
|
||||
return E('div', { 'class': 'dd-card dd-settings-card' }, cardChildren);
|
||||
/* state-aware primary action: running → one "Save and Apply"; stopped →
|
||||
"Save config" (secondary) + "Save and Start" (primary). Starting the
|
||||
proxy is an explicit choice, never an implicit side effect of saving. */
|
||||
return backend.detectRunning().then(function(rr) {
|
||||
const running = rr && rr.dae;
|
||||
|
||||
/* "Update all subscriptions" only helps while dae runs (hot reload
|
||||
re-pulls them). A stopped dae re-fetches every subscription on
|
||||
start, so there is nothing to update — hide the button then. */
|
||||
if (running) {
|
||||
const upBtn = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Update all subscriptions'));
|
||||
const upMsg = E('span', { 'class': 'dd-meta', 'style': 'margin-left:8px;display:none' }, '');
|
||||
const setUp = function(text, kind) {
|
||||
upMsg.textContent = text;
|
||||
upMsg.style.display = '';
|
||||
upMsg.style.color = (kind === 'err') ? 'var(--error-color, #d33)' : 'var(--success-color, #2a8)';
|
||||
if (kind !== 'err')
|
||||
setTimeout(function() { if (upMsg.textContent === text) upMsg.style.display = 'none'; }, 5000);
|
||||
};
|
||||
upBtn.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
upBtn.disabled = true;
|
||||
setUp(_('Updating…'));
|
||||
fs.exec(backend.BACKENDS.dae.initd, ['hot_reload']).then(function(res) {
|
||||
if (res && res.code !== 0)
|
||||
setUp(_('Update failed: %s').format(res.stderr || res.stdout || ('exit ' + res.code)), 'err');
|
||||
else
|
||||
setUp(_('Subscriptions updated (dae reloaded)'), 'ok');
|
||||
}).catch(function(e) {
|
||||
setUp(_('Update failed: %s').format(e.message || e), 'err');
|
||||
}).finally(function() { upBtn.disabled = false; });
|
||||
});
|
||||
const upWrap = E('div', { 'class': 'dd-actions', 'style': 'margin:0 0 8px' }, [ upBtn, upMsg ]);
|
||||
const advs = mapNode.querySelectorAll('.dd-adv');
|
||||
for (let i = 0; i < advs.length; i++) {
|
||||
const t = advs[i].querySelector('.dd-adv-bar span');
|
||||
if (t && t.textContent.trim() === _('Subscriptions')) {
|
||||
const body = advs[i].querySelector('.dd-adv-body');
|
||||
const tbl = body && body.querySelector('.cbi-section-table');
|
||||
if (tbl) tbl.parentNode.insertBefore(upWrap, tbl);
|
||||
else if (body) body.insertBefore(upWrap, body.firstChild);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actions = [];
|
||||
let btns;
|
||||
if (running) {
|
||||
const apply = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save and Apply'));
|
||||
btns = [ apply ];
|
||||
apply.addEventListener('click', function(ev) { ev.preventDefault(); doSave({ okMsg: _('Saved · applied') }, btns); });
|
||||
actions.push(apply);
|
||||
} else {
|
||||
const saveOnly = E('button', { 'class': 'cbi-button' }, _('Save config'));
|
||||
const saveStart = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save and Start'));
|
||||
btns = [ saveOnly, saveStart ];
|
||||
saveOnly.addEventListener('click', function(ev) { ev.preventDefault(); doSave({ okMsg: _('Saved') }, btns); });
|
||||
saveStart.addEventListener('click', function(ev) { ev.preventDefault(); doSave({ start: true }, btns); });
|
||||
actions.push(saveOnly, saveStart);
|
||||
}
|
||||
actions.push(status);
|
||||
cardChildren.push(E('div', { 'class': 'dd-editor-actions' }, actions));
|
||||
return E('div', { 'class': 'dd-card dd-settings-card' }, cardChildren);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -425,12 +506,36 @@ function renderDaeImportBanner() {
|
||||
}).catch(function() { return null; });
|
||||
}
|
||||
|
||||
/* Lightweight dae-DSL syntax highlighter: tokenises into spans for a <pre>
|
||||
layer drawn behind a transparent textarea (no editor library needed). */
|
||||
function highlightDae(src) {
|
||||
const re = /(#[^\n]*)|('[^'\n]*')|\b(global|subscription|node|group|routing|dns|include|upstream|request|response)\b|\b([A-Za-z_][\w-]*)(?=\s*\()|(->|&&|\|\|)|\b(geoip|geosite)(?=:)/g;
|
||||
let out = '', last = 0, m;
|
||||
const esc = function(s) { return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); };
|
||||
while ((m = re.exec(src)) !== null) {
|
||||
out += esc(src.slice(last, m.index));
|
||||
const cls = m[1] ? 'c' : m[2] ? 's' : m[3] ? 'k' : m[4] ? 'f' : m[5] ? 'o' : 'g';
|
||||
out += '<span class="dh-' + cls + '">' + esc(m[0]) + '</span>';
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
out += esc(src.slice(last));
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderDaeEditor() {
|
||||
const textarea = E('textarea', {
|
||||
'class': 'dd-editor',
|
||||
'class': 'dd-editor dd-editor-hl',
|
||||
'spellcheck': 'false',
|
||||
'placeholder': _('dae config file is empty. Click "Initialize from example" to start, or paste your config here.')
|
||||
}, '');
|
||||
const hlCode = E('code', {});
|
||||
const hlPre = E('pre', { 'class': 'dd-hl', 'aria-hidden': 'true' }, hlCode);
|
||||
const editWrap = E('div', { 'class': 'dd-edit-wrap' }, [ hlPre, textarea ]);
|
||||
function syncHL() {
|
||||
hlCode.innerHTML = highlightDae(textarea.value) + '\n';
|
||||
hlPre.scrollTop = textarea.scrollTop;
|
||||
}
|
||||
textarea.addEventListener('scroll', function() { hlPre.scrollTop = textarea.scrollTop; });
|
||||
const save = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save manual config'));
|
||||
const init = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Initialize from example'));
|
||||
const status = E('span', { 'class': 'dd-editor-status' }, '');
|
||||
@@ -514,6 +619,7 @@ function renderDaeEditor() {
|
||||
}
|
||||
|
||||
function refreshPlaceholders() {
|
||||
syncHL();
|
||||
const hits = detectPlaceholders(textarea.value);
|
||||
const titleEl = phWarn.querySelector('.dd-ph-warn-title');
|
||||
const howtoEl = phWarn.querySelector('.dd-ph-howto');
|
||||
@@ -634,7 +740,7 @@ function renderDaeEditor() {
|
||||
_('Edit config DSL — subscriptions, nodes, routing, DNS. Load template via Initialize. Replace placeholder URL before saving. Switch to daed for GUI.')
|
||||
]),
|
||||
phWarn,
|
||||
textarea,
|
||||
editWrap,
|
||||
footer,
|
||||
E('div', { 'class': 'dd-editor-actions' }, [ save, init, insertSelect, status ])
|
||||
])
|
||||
|
||||
@@ -17,7 +17,7 @@ const CSS = [
|
||||
'.dd-log-toolbar label{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;cursor:pointer;margin:0;opacity:.85}',
|
||||
'.dd-log-toolbar input[type="checkbox"]{margin:0}',
|
||||
'.dd-log-toolbar input[type="text"]{font-size:11.5px;padding:4px 8px;border-radius:5px;border:1px solid rgba(128,128,128,.28);background:transparent;color:inherit;min-width:160px}',
|
||||
'.dd-log-toolbar .dd-log-btn{font-size:11.5px;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.28);background:transparent;color:inherit;cursor:pointer}',
|
||||
'.dd-log-toolbar .dd-log-btn{font-size:11.5px;line-height:1.4;min-height:0;height:auto;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.28);background:transparent;color:inherit;cursor:pointer}',
|
||||
'.dd-log-toolbar .dd-log-btn:hover{background:rgba(128,128,128,.1)}',
|
||||
'.dd-log-toolbar .dd-log-meta{margin-left:auto;font-size:10.5px;opacity:.55;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}',
|
||||
'.dd-log-pane{height:60vh;min-height:360px;overflow:auto;padding:8px 10px;border:1px solid rgba(0,0,0,.08);border-radius:7px;background:#1a1d21;color:#d8dde6;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:11.5px;line-height:1.55;white-space:pre-wrap;word-break:break-all}',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
const CSS = [
|
||||
'.dd-wrap{padding:4px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}',
|
||||
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:10px 14px;margin-bottom:10px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
|
||||
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:9px 14px;margin-bottom:7px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
|
||||
'.dd-card-title{font-size:11px;font-weight:600;opacity:.55;margin:0 0 8px;letter-spacing:.3px;text-transform:uppercase}',
|
||||
'.dd-status-row{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:0}',
|
||||
'.dd-status-row .dd-grow{flex:1 1 auto}',
|
||||
@@ -18,7 +18,7 @@ const CSS = [
|
||||
'.dd-actions{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 0}',
|
||||
'.dd-actions .cbi-button{font-size:11.5px;padding:4px 12px;border-radius:5px}',
|
||||
'.dd-actions a.cbi-button{display:inline-flex;align-items:center;gap:4px}',
|
||||
'.dd-wrap .cbi-button{font-size:11px !important;padding:4px 12px !important;border-radius:5px !important;border:1px solid rgba(128,128,128,.35) !important;background:transparent !important;color:inherit !important;box-shadow:none !important;cursor:pointer;white-space:nowrap}',
|
||||
'.dd-wrap .cbi-button{font-size:11px !important;line-height:1.4 !important;min-height:0 !important;height:auto !important;padding:4px 12px !important;border-radius:5px !important;border:1px solid rgba(128,128,128,.35) !important;background:transparent !important;color:inherit !important;box-shadow:none !important;cursor:pointer;white-space:nowrap}',
|
||||
'.dd-wrap .cbi-button:hover{background:rgba(128,128,128,.12) !important}',
|
||||
'.dd-wrap .cbi-button:disabled{opacity:.45;cursor:not-allowed}',
|
||||
'.dd-wrap .cbi-button-action,.dd-wrap .cbi-button-positive,.dd-wrap .cbi-button-add,.dd-wrap .cbi-button-edit{border-color:#4aa065 !important;color:#4aa065 !important}',
|
||||
@@ -56,13 +56,18 @@ const CSS = [
|
||||
'.dd-settings-card .cbi-value-description,.dd-settings-card .cbi-value-helptext{font-size:11.5px !important;opacity:.6;line-height:1.45;padding-top:3px}',
|
||||
'.dd-settings-card .cbi-section-remove{background:transparent !important;border:0 !important;box-shadow:none !important}',
|
||||
'.dd-settings-card .cbi-section-remove .cbi-button{border-color:#d96d6d !important;color:#d96d6d !important}',
|
||||
'.dd-settings-card input[type="checkbox"]{box-shadow:none !important;background-repeat:no-repeat !important;background-position:center !important;background-size:62% !important}',
|
||||
'.dd-settings-card input[type="checkbox"]:before{content:none !important;display:none !important;box-shadow:none !important}',
|
||||
/* themes (esp. Argon) restyle checkboxes into faint, near-invisible boxes;
|
||||
force native rendering + brand accent so the checkmark is always clear */
|
||||
'.dd-settings-card input[type="checkbox"]{-webkit-appearance:checkbox !important;appearance:checkbox !important;accent-color:#4aa065;width:16px;height:16px;background:none !important;border:0 !important;box-shadow:none !important;background-image:none !important}',
|
||||
'.dd-settings-card input[type="checkbox"]:before,.dd-settings-card input[type="checkbox"]:after{content:none !important;display:none !important}',
|
||||
/* line the Subscriptions and Nodes grid tables up column-for-column. fixed
|
||||
layout + explicit per-column percentages make the widths authoritative
|
||||
across themes (Argon's auto layout otherwise sizes the empty Nodes table
|
||||
by content, and even ignores px cell widths, so they drift apart) */
|
||||
'.dd-settings-card .cbi-section-table{table-layout:fixed !important;width:100%}',
|
||||
/* flatten the table header: Argon tints header cells (#f6f9fc), which reads
|
||||
as an off-colour band on the white card — keep it transparent */
|
||||
'.dd-settings-card .cbi-section-table-titles,.dd-settings-card .cbi-section-table-titles .cbi-section-table-cell{background:transparent !important;background-image:none !important}',
|
||||
'.dd-settings-card .cbi-section-table-cell:nth-child(1){width:14% !important}',
|
||||
'.dd-settings-card .cbi-section-table-cell:nth-child(2){width:50% !important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
||||
'.dd-settings-card .cbi-section-table-cell:nth-child(3){width:12% !important}',
|
||||
@@ -79,13 +84,37 @@ const CSS = [
|
||||
'.dd-adv-body{margin-top:8px;padding:2px 4px 4px}',
|
||||
'.dd-adv.dd-closed .dd-adv-body{display:none}',
|
||||
'.dd-editor{width:100%;min-height:460px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:12px;line-height:1.5;box-sizing:border-box;resize:vertical;border-radius:6px 6px 0 0}',
|
||||
/* syntax-highlight overlay: a <pre> sits behind a transparent <textarea>.
|
||||
both share identical box metrics so glyphs line up; the textarea stays the
|
||||
real editor (caret, selection, insert-at-cursor, jump-to-line all native) */
|
||||
'.dd-edit-wrap{position:relative}',
|
||||
'.dd-edit-wrap .dd-editor,.dd-edit-wrap .dd-hl{margin:0;padding:10px 12px;border-width:1px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:12px;line-height:1.5;letter-spacing:0;tab-size:4;white-space:pre-wrap;word-break:break-word;box-sizing:border-box}',
|
||||
'.dd-edit-wrap .dd-hl{position:absolute;inset:0;margin:0;overflow:hidden;border:1px solid transparent;border-radius:6px 6px 0 0;pointer-events:none;background:transparent;color:#3b4252;z-index:1}',
|
||||
'.dd-edit-wrap .dd-hl code{font:inherit;white-space:inherit;word-break:inherit;display:block}',
|
||||
'.dd-edit-wrap .dd-editor-hl{position:relative;z-index:2;color:transparent;background:transparent;caret-color:#2f7288;border:1px solid rgba(128,128,128,.28)}',
|
||||
'.dd-edit-wrap .dd-editor-hl::placeholder{color:rgba(128,128,128,.55)}',
|
||||
'.dd-edit-wrap .dd-editor-hl::selection{background:rgba(56,134,161,.25)}',
|
||||
'.dh-c{color:#8a919a;font-style:italic}',
|
||||
'.dh-s{color:#2a8a4a}',
|
||||
'.dh-k{color:#9a3fb5;font-weight:600}',
|
||||
'.dh-f{color:#2f7288}',
|
||||
'.dh-o{color:#c2772a}',
|
||||
'.dh-g{color:#b0226a}',
|
||||
'html[data-darkmode="true"] .dd-edit-wrap .dd-hl,body.dark .dd-edit-wrap .dd-hl,html[data-bs-theme="dark"] .dd-edit-wrap .dd-hl{color:#c8cdd6}',
|
||||
'html[data-darkmode="true"] .dd-edit-wrap .dd-editor-hl,body.dark .dd-edit-wrap .dd-editor-hl,html[data-bs-theme="dark"] .dd-edit-wrap .dd-editor-hl{caret-color:#7fd0e8}',
|
||||
'html[data-darkmode="true"] .dh-c,body.dark .dh-c{color:#6b7280}',
|
||||
'html[data-darkmode="true"] .dh-s,body.dark .dh-s{color:#7ec699}',
|
||||
'html[data-darkmode="true"] .dh-k,body.dark .dh-k{color:#c792ea}',
|
||||
'html[data-darkmode="true"] .dh-f,body.dark .dh-f{color:#82c4dd}',
|
||||
'html[data-darkmode="true"] .dh-o,body.dark .dh-o{color:#e0a35a}',
|
||||
'html[data-darkmode="true"] .dh-g,body.dark .dh-g{color:#ec6fa8}',
|
||||
'.dd-editor-footer{display:flex;flex-wrap:wrap;align-items:center;gap:14px;padding:5px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10.5px;background:rgba(128,128,128,.06);border:1px solid rgba(0,0,0,.06);border-top:0;border-radius:0 0 6px 6px;color:inherit;opacity:.78}',
|
||||
'.dd-editor-footer .dd-fb-item{display:inline-flex;align-items:center;gap:5px}',
|
||||
'.dd-editor-footer .dd-fb-key{opacity:.55}',
|
||||
'.dd-editor-footer .dd-fb-warn{color:#b07d00;font-weight:600}',
|
||||
'.dd-editor-footer .dd-fb-ok{color:#3da66a;font-weight:600}',
|
||||
'body.dark .dd-editor-footer,html[data-theme="dark"] .dd-editor-footer,html[data-bs-theme="dark"] .dd-editor-footer{border-color:rgba(255,255,255,.1);background:rgba(255,255,255,.04)}',
|
||||
'body.dark .dd-editor-footer .dd-fb-warn,html[data-theme="dark"] .dd-editor-footer .dd-fb-warn,html[data-bs-theme="dark"] .dd-editor-footer .dd-fb-warn{color:#e0b34a}',
|
||||
'body.dark .dd-editor-footer,html[data-theme="dark"] .dd-editor-footer,html[data-bs-theme="dark"] .dd-editor-footer,html[data-darkmode="true"] .dd-editor-footer{border-color:rgba(255,255,255,.1);background:rgba(255,255,255,.04)}',
|
||||
'body.dark .dd-editor-footer .dd-fb-warn,html[data-theme="dark"] .dd-editor-footer .dd-fb-warn,html[data-bs-theme="dark"] .dd-editor-footer .dd-fb-warn,html[data-darkmode="true"] .dd-editor-footer .dd-fb-warn{color:#e0b34a}',
|
||||
'.dd-insert-select{font-size:11.5px;padding:4px 8px;border-radius:5px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer}',
|
||||
'.dd-insert-select:hover{border-color:rgba(56,134,161,.55)}',
|
||||
'.dd-editor-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px}',
|
||||
@@ -108,7 +137,7 @@ const CSS = [
|
||||
'.dd-ph-warn{display:none;font-size:12px;line-height:1.6;margin:0 0 8px;padding:10px 12px;border-radius:6px;background:rgba(217,158,0,.08);color:inherit}',
|
||||
'.dd-ph-warn.show{display:block}',
|
||||
'.dd-ph-warn-title{font-weight:600;margin-bottom:6px;color:#b07d00;font-size:12.5px}',
|
||||
'body.dark .dd-ph-warn-title,html[data-theme="dark"] .dd-ph-warn-title,html[data-bs-theme="dark"] .dd-ph-warn-title{color:#e0b34a}',
|
||||
'body.dark .dd-ph-warn-title,html[data-theme="dark"] .dd-ph-warn-title,html[data-bs-theme="dark"] .dd-ph-warn-title,html[data-darkmode="true"] .dd-ph-warn-title{color:#e0b34a}',
|
||||
'.dd-ph-howto{margin:4px 0 8px;opacity:.85}',
|
||||
'.dd-ph-howto code{padding:1px 5px;border-radius:3px;background:rgba(128,128,128,.18);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}',
|
||||
'.dd-ph-list{margin:6px 0 0;padding:6px 0 0;list-style:none;border-top:1px dashed rgba(217,158,0,.3)}',
|
||||
@@ -116,11 +145,21 @@ const CSS = [
|
||||
'.dd-ph-list li{padding:2px 0;display:flex;gap:8px;align-items:baseline;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px}',
|
||||
'.dd-ph-list .dd-ph-ln{display:inline-block;min-width:38px;text-align:center;padding:1px 4px;border-radius:3px;background:rgba(217,158,0,.18);color:#8a6300;opacity:.95;cursor:pointer;font-weight:600;text-decoration:none}',
|
||||
'.dd-ph-list .dd-ph-ln:hover{background:rgba(217,158,0,.32)}',
|
||||
'body.dark .dd-ph-list .dd-ph-ln,html[data-theme="dark"] .dd-ph-list .dd-ph-ln,html[data-bs-theme="dark"] .dd-ph-list .dd-ph-ln{color:#f0c763;background:rgba(217,158,0,.22)}',
|
||||
'body.dark .dd-ph-list .dd-ph-ln,html[data-theme="dark"] .dd-ph-list .dd-ph-ln,html[data-bs-theme="dark"] .dd-ph-list .dd-ph-ln,html[data-darkmode="true"] .dd-ph-list .dd-ph-ln{color:#f0c763;background:rgba(217,158,0,.22)}',
|
||||
'.dd-ph-list .dd-ph-txt{opacity:.7;word-break:break-all}',
|
||||
'body.dark .dd-card,html[data-theme="dark"] .dd-card,html[data-bs-theme="dark"] .dd-card{border-color:rgba(255,255,255,.08);background:rgba(255,255,255,.02)}',
|
||||
'body.dark .dd-adv-bar,html[data-theme="dark"] .dd-adv-bar,html[data-bs-theme="dark"] .dd-adv-bar{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.10)}',
|
||||
'body.dark .dd-settings-card .cbi-value-field input,body.dark .dd-settings-card .cbi-value-field select,body.dark .dd-settings-card .cbi-value-field textarea,html[data-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-theme="dark"] .dd-settings-card .cbi-value-field textarea,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field textarea{border-color:rgba(255,255,255,.18)}'
|
||||
'body.dark .dd-card,html[data-theme="dark"] .dd-card,html[data-bs-theme="dark"] .dd-card,html[data-darkmode="true"] .dd-card{border-color:rgba(255,255,255,.08);background:rgba(255,255,255,.02)}',
|
||||
'body.dark .dd-adv-bar,html[data-theme="dark"] .dd-adv-bar,html[data-bs-theme="dark"] .dd-adv-bar,html[data-darkmode="true"] .dd-adv-bar{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.10)}',
|
||||
'body.dark .dd-settings-card .cbi-value-field input,body.dark .dd-settings-card .cbi-value-field select,body.dark .dd-settings-card .cbi-value-field textarea,html[data-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-theme="dark"] .dd-settings-card .cbi-value-field textarea,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field textarea,html[data-darkmode="true"] .dd-settings-card .cbi-value-field input,html[data-darkmode="true"] .dd-settings-card .cbi-value-field select,html[data-darkmode="true"] .dd-settings-card .cbi-value-field textarea{border-color:rgba(255,255,255,.18)}',
|
||||
/* phone: render each subscription/node row as a self-contained card instead
|
||||
of a cramped table (LuCI already block-stacks the cells; we add the card
|
||||
frame, drop the desktop column widths, and shrink the action buttons) */
|
||||
'@media (max-width:600px){',
|
||||
'.dd-settings-card .cbi-section-table-cell:nth-child(1),.dd-settings-card .cbi-section-table-cell:nth-child(2),.dd-settings-card .cbi-section-table-cell:nth-child(3),.dd-settings-card .cbi-section-table-cell:nth-child(4){width:auto !important;min-width:0 !important;white-space:normal !important;overflow:visible !important;text-overflow:clip !important}',
|
||||
'.dd-settings-card .cbi-section-table-row{display:block;border:1px solid rgba(128,128,128,.2);border-radius:8px;padding:6px 10px;margin:0 0 8px;background:rgba(128,128,128,.03)}',
|
||||
'.dd-settings-card .cbi-section-table-cell{display:block;padding:3px 0 !important}',
|
||||
'.dd-settings-card .cbi-section-actions{display:flex;flex-wrap:wrap;gap:6px;padding-top:6px !important}',
|
||||
'.dd-settings-card .cbi-section-actions .cbi-button{width:auto;margin:0;flex:0 0 auto}',
|
||||
'}'
|
||||
].join('');
|
||||
|
||||
return baseclass.extend({ CSS: CSS });
|
||||
@@ -31,7 +31,7 @@ const CSS = [
|
||||
'.dd-up-name{font-weight:600;opacity:.85}',
|
||||
'.dd-up-meta{opacity:.7;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
||||
'.dd-up-version{font-size:11px;opacity:.55;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:nowrap}',
|
||||
'.dd-up-btn{font-size:11px;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer;white-space:nowrap}',
|
||||
'.dd-up-btn{font-size:11px;line-height:1.4;min-height:0;height:auto;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer;white-space:nowrap}',
|
||||
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
|
||||
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
|
||||
'.dd-up-btn-primary{border-color:#4aa065;color:#4aa065}',
|
||||
|
||||
@@ -202,7 +202,7 @@ function renderStatusCard(ctx, listenAddr) {
|
||||
});
|
||||
actions.push(restart);
|
||||
}
|
||||
if (be.name === 'dae') {
|
||||
if (state.running && be.name === 'dae') {
|
||||
const hot = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Hot Reload'));
|
||||
hot.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
@@ -376,3 +376,51 @@ msgstr ""
|
||||
#: dae.js
|
||||
msgid " are placeholder addresses from dae's template, not real subscriptions. Replace the URL on any one line with your airport subscription link (delete the spare lines) and save."
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Manual nodes"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Update all subscriptions"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "auto-generated"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Save and Apply"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Save config"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Save and Start"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Saved · applied"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Saved"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Saved · started"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Starting…"
|
||||
msgstr ""
|
||||
|
||||
#: dae.js
|
||||
msgid "Start failed: %s"
|
||||
msgstr ""
|
||||
@@ -664,3 +664,36 @@ msgstr "这些 "
|
||||
|
||||
msgid " are placeholder addresses from dae's template, not real subscriptions. Replace the URL on any one line with your airport subscription link (delete the spare lines) and save."
|
||||
msgstr " 都是 dae 官方模板留的假地址,不是真订阅。把其中任意一行的 URL 换成你机场的订阅链接(删掉多余行),保存即可跑通。"
|
||||
|
||||
msgid "Manual nodes"
|
||||
msgstr "手动添加节点"
|
||||
|
||||
msgid "Update all subscriptions"
|
||||
msgstr "更新全部订阅"
|
||||
|
||||
msgid "Advanced settings"
|
||||
msgstr "高级设置"
|
||||
|
||||
msgid "auto-generated"
|
||||
msgstr "自动生成"
|
||||
|
||||
msgid "Save config"
|
||||
msgstr "保存配置"
|
||||
|
||||
msgid "Save and Start"
|
||||
msgstr "保存并启动"
|
||||
|
||||
msgid "Saved · applied"
|
||||
msgstr "已保存并应用"
|
||||
|
||||
msgid "Saved"
|
||||
msgstr "已保存"
|
||||
|
||||
msgid "Saved · started"
|
||||
msgstr "已保存并启动"
|
||||
|
||||
msgid "Starting…"
|
||||
msgstr "启动中…"
|
||||
|
||||
msgid "Start failed: %s"
|
||||
msgstr "启动失败:%s"
|
||||
@@ -457,3 +457,39 @@ msgstr "这些 "
|
||||
|
||||
msgid " are placeholder addresses from dae's template, not real subscriptions. Replace the URL on any one line with your airport subscription link (delete the spare lines) and save."
|
||||
msgstr " 都是 dae 官方模板留的假地址,不是真订阅。把其中任意一行的 URL 换成你机场的订阅链接(删掉多余行),保存即可跑通。"
|
||||
|
||||
msgid "Manual nodes"
|
||||
msgstr "手动添加节点"
|
||||
|
||||
msgid "Update all subscriptions"
|
||||
msgstr "更新全部订阅"
|
||||
|
||||
msgid "Advanced settings"
|
||||
msgstr "高级设置"
|
||||
|
||||
msgid "auto-generated"
|
||||
msgstr "自动生成"
|
||||
|
||||
msgid "Save and Apply"
|
||||
msgstr "保存并应用"
|
||||
|
||||
msgid "Save config"
|
||||
msgstr "保存配置"
|
||||
|
||||
msgid "Save and Start"
|
||||
msgstr "保存并启动"
|
||||
|
||||
msgid "Saved · applied"
|
||||
msgstr "已保存并应用"
|
||||
|
||||
msgid "Saved"
|
||||
msgstr "已保存"
|
||||
|
||||
msgid "Saved · started"
|
||||
msgstr "已保存并启动"
|
||||
|
||||
msgid "Starting…"
|
||||
msgstr "启动中…"
|
||||
|
||||
msgid "Start failed: %s"
|
||||
msgstr "启动失败:%s"
|
||||
@@ -27,6 +27,19 @@ sanitize() {
|
||||
printf '%s' "$1" | tr -d "'" | tr -d '\n\r'
|
||||
}
|
||||
|
||||
# dae subscription/node tags are bareword identifiers: only [A-Za-z0-9_] are
|
||||
# valid tokens, so a CJK/spaced tag (e.g. 白月光) makes the parser choke. Map
|
||||
# any such tag to a stable ASCII slug; pure-word tags pass through unchanged.
|
||||
# Deterministic on the input, so a group's source reference resolves to the
|
||||
# same slug as the subscription it points at. Empty stays empty (anonymous).
|
||||
dae_tag() {
|
||||
[ -n "$1" ] || return 0
|
||||
case "$1" in
|
||||
*[!A-Za-z0-9_]*) printf 's_%s' "$(printf '%s' "$1" | md5sum | cut -c1-10)" ;;
|
||||
*) printf '%s' "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ===================== generate =====================
|
||||
|
||||
SUB_BUF=""
|
||||
@@ -42,7 +55,7 @@ emit_sub() {
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
url="$(sanitize "$url")"
|
||||
[ -n "$url" ] || return 0
|
||||
tag="$(sanitize "$tag")"
|
||||
tag="$(dae_tag "$(sanitize "$tag")")"
|
||||
if [ -n "$tag" ]; then
|
||||
SUB_BUF="${SUB_BUF} ${tag}: '${url}'
|
||||
"
|
||||
@@ -60,7 +73,7 @@ emit_node() {
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
link="$(sanitize "$link")"
|
||||
[ -n "$link" ] || return 0
|
||||
tag="$(sanitize "$tag")"
|
||||
tag="$(dae_tag "$(sanitize "$tag")")"
|
||||
if [ -n "$tag" ]; then
|
||||
NODE_BUF="${NODE_BUF} ${tag}: '${link}'
|
||||
"
|
||||
@@ -125,7 +138,7 @@ KW_ACC=""
|
||||
SUB_TAGS=""
|
||||
collect_subtag() {
|
||||
local t; config_get t "$1" tag ""
|
||||
t="$(sanitize "$t")"
|
||||
t="$(dae_tag "$(sanitize "$t")")"
|
||||
[ -n "$t" ] && SUB_TAGS="${SUB_TAGS} ${t} "
|
||||
}
|
||||
is_subtag() {
|
||||
@@ -153,8 +166,11 @@ kw_arg() {
|
||||
_collect_source() {
|
||||
local v="$(sanitize "$1")"
|
||||
[ -n "$v" ] || return 0
|
||||
if is_subtag "$v"; then
|
||||
SUBS_ACC="${SUBS_ACC:+$SUBS_ACC, }$(filter_arg "$v")"
|
||||
# subscription refs resolve to the (ASCII) dae tag; node-name refs keep the
|
||||
# original text (matched as a quoted regex against node names)
|
||||
local dt="$(dae_tag "$v")"
|
||||
if is_subtag "$dt"; then
|
||||
SUBS_ACC="${SUBS_ACC:+$SUBS_ACC, }$(filter_arg "$dt")"
|
||||
else
|
||||
NODES_ACC="${NODES_ACC:+$NODES_ACC, }$(filter_arg "$v")"
|
||||
fi
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
"/tmp/luci-app-daede.pkg-dae.log": [ "read" ],
|
||||
"/tmp/luci-app-daede.pkg-daed.log": [ "read" ],
|
||||
"/tmp/luci-app-daede.pkg-luci-app-daede.log": [ "read" ],
|
||||
"/usr/bin/pgrep -x dae": [ "exec" ],
|
||||
"/usr/bin/pgrep -x daed": [ "exec" ],
|
||||
"/bin/pidof dae": [ "exec" ],
|
||||
"/bin/pidof daed": [ "exec" ],
|
||||
"/usr/bin/dae validate -c /tmp/dae-validate.dae": [ "exec" ],
|
||||
"/usr/bin/dae --version": [ "exec" ],
|
||||
"/bin/rm -f /tmp/dae-validate.dae": [ "exec" ],
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# Copyright (C) 2024 Openwrt.org
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
# This is free software, licensed under the GNU Affero General Public License v3.0 only.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-gecoosac
|
||||
PKG_VERSION:=2.2
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
LUCI_TITLE:=LuCI Support for gecoosac
|
||||
LUCI_DEPENDS:=+luci-compat +gecoosac
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ local fs = require "nixio.fs"
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci"
|
||||
|
||||
local ACL_DEPENDS = { "luci-app-gecoosac" }
|
||||
|
||||
local function service_running()
|
||||
if not fs.access("/etc/init.d/gecoosac") then
|
||||
return false
|
||||
@@ -19,15 +21,18 @@ function index()
|
||||
local page
|
||||
page = entry({"admin", "services", "gecoosac"}, cbi("gecoosac"), _("Gecoos AC"), 100)
|
||||
page.dependent = true
|
||||
page.acl_depends = ACL_DEPENDS
|
||||
page = entry({"admin", "services", "gecoosac", "status"}, call("act_status"))
|
||||
page.leaf = true
|
||||
page.acl_depends = ACL_DEPENDS
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local cur = uci.cursor()
|
||||
local enabled = cur:get("gecoosac", "config", "enabled") == "1"
|
||||
local e = {
|
||||
running = enabled and service_running()
|
||||
enabled = enabled,
|
||||
running = service_running()
|
||||
}
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
|
||||
@@ -31,22 +31,34 @@ end
|
||||
local function remove_tree(path)
|
||||
local stat = fs.lstat and fs.lstat(path) or fs.stat(path)
|
||||
if not stat then
|
||||
return
|
||||
return true
|
||||
end
|
||||
|
||||
if stat.type == "dir" then
|
||||
local iter = fs.dir(path)
|
||||
if iter then
|
||||
for entry in iter do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
remove_tree(path .. "/" .. entry)
|
||||
if not iter then
|
||||
return nil, translatef("Unable to read %s", path)
|
||||
end
|
||||
|
||||
for entry in iter do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
local ok, err = remove_tree(path .. "/" .. entry)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
fs.rmdir(path)
|
||||
|
||||
if not fs.rmdir(path) and fs.stat(path, "type") == "dir" then
|
||||
return nil, translatef("Unable to remove %s", path)
|
||||
end
|
||||
else
|
||||
fs.unlink(path)
|
||||
if not fs.unlink(path) and (fs.lstat and fs.lstat(path) or fs.stat(path)) then
|
||||
return nil, translatef("Unable to remove %s", path)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function clear_upload_dir(path)
|
||||
@@ -71,10 +83,15 @@ local function clear_upload_dir(path)
|
||||
end
|
||||
|
||||
local iter = fs.dir(real)
|
||||
if iter then
|
||||
for entry in iter do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
remove_tree(real .. "/" .. entry)
|
||||
if not iter then
|
||||
return nil, translatef("Unable to read %s", real)
|
||||
end
|
||||
|
||||
for entry in iter do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
local ok, err = remove_tree(real .. "/" .. entry)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -165,13 +182,15 @@ log = s:option(Flag, "log", translate("Enable Log"))
|
||||
log.default = 0
|
||||
log.rmempty = false
|
||||
|
||||
clear_upload = s:option(Button, "clear_upload", translate("Clear Upload Directory"))
|
||||
clear_upload = s:option(Button, "clear_upload", translate("Clear Upload Directory"), translate("Only files under the configured Gecoos upload directory will be removed."))
|
||||
clear_upload.inputstyle = "remove"
|
||||
clear_upload.write = function(self, section)
|
||||
local path = upload_dir:formvalue(section) or upload_dir:cfgvalue(section) or DEFAULT_UPLOAD_DIR
|
||||
local ok, err = clear_upload_dir(path)
|
||||
if not ok then
|
||||
self.map.message = err or translate("Upload directory was not cleared")
|
||||
else
|
||||
self.map.message = translate("Upload directory cleared")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
msgstr ""
|
||||
"Project-Id-Version: luci-app-gecoosac\n"
|
||||
"PO-Revision-Date: 2026-06-12 00:00+0800\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: Chinese\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Default certificate files are generated when HTTPS starts; custom paths must point to readable files."
|
||||
msgstr "默认证书文件会在 HTTPS 启动时生成;自定义路径必须指向可读取的文件。"
|
||||
@@ -37,9 +45,6 @@ msgstr "设置管理端口"
|
||||
msgid "Enable HTTPS service"
|
||||
msgstr "开启 HTTPS 服务"
|
||||
|
||||
msgid "A certificate file must be specified, otherwise it will fail to start."
|
||||
msgstr "需要指定证书文件,否则开启失败。"
|
||||
|
||||
msgid "Specify crt certificate file"
|
||||
msgstr "指定 crt 证书文件"
|
||||
|
||||
@@ -100,6 +105,9 @@ msgstr "收集数据..."
|
||||
msgid "Clear Upload Directory"
|
||||
msgstr "清理上传目录"
|
||||
|
||||
msgid "Only files under the configured Gecoos upload directory will be removed."
|
||||
msgstr "只会删除已配置的集客 AC 上传目录中的文件。"
|
||||
|
||||
msgid "Expecting an absolute path"
|
||||
msgstr "请输入绝对路径"
|
||||
|
||||
@@ -108,3 +116,12 @@ msgstr "只能清理集客 AC 上传目录"
|
||||
|
||||
msgid "Upload directory was not cleared"
|
||||
msgstr "上传目录未清理"
|
||||
|
||||
msgid "Upload directory cleared"
|
||||
msgstr "上传目录已清理"
|
||||
|
||||
msgid "Unable to read %s"
|
||||
msgstr "无法读取 %s"
|
||||
|
||||
msgid "Unable to remove %s"
|
||||
msgstr "无法删除 %s"
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 12a7.5 7.5 0 0 0 15 0m-15 0a7.5 7.5 0 1 1 15 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077l1.41-.513m14.095-5.13l1.41-.513M5.106 17.785l1.15-.964m11.49-9.642l1.149-.964M7.501 19.795l.75-1.3m7.5-12.99l.75-1.3m-6.063 16.658l.26-1.477m2.605-14.772l.26-1.477m0 17.726l-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205L12 12m6.894 5.785l-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864l-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495"/></svg>
|
||||
|
After Width: | Height: | Size: 652 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z"/></svg>
|
||||
|
After Width: | Height: | Size: 468 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/></svg>
|
||||
|
After Width: | Height: | Size: 280 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z"/></svg>
|
||||
|
After Width: | Height: | Size: 494 B |
@@ -7,11 +7,90 @@ header {
|
||||
|
||||
[data-nav-type="mega-menu"] & {
|
||||
& .desktop-menu-container {
|
||||
@apply bg-header-bg/40 dark:bg-header-bg/90 pointer-events-none absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden opacity-0 backdrop-blur-xl transition-[clip-path,opacity] duration-200 ease-out [clip-path:inset(0_0_100%_0)] [will-change:clip-path,opacity] max-md:hidden;
|
||||
@apply bg-header-bg/40 dark:bg-header-bg/90 pointer-events-none absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden opacity-0 backdrop-blur-xl transition-[clip-path,opacity] duration-200 ease-out [will-change:clip-path,opacity] [clip-path:inset(0_0_100%_0)] max-md:hidden;
|
||||
|
||||
&.active {
|
||||
@apply pointer-events-auto opacity-100 [clip-path:inset(0)];
|
||||
}
|
||||
|
||||
/* Category panels live inside the container, not the nav items: the
|
||||
transformed #topmenu would otherwise become the containing block of
|
||||
a position:fixed panel and shrink the canvas to the menu's width.
|
||||
Fixed coordinates shared by every category, so switching panels
|
||||
never shifts anything: link columns start at 50% - 13rem (just
|
||||
under the centered nav triggers), the anchor column sits at a
|
||||
fixed slot left of them. The entering panel floats up 6px —
|
||||
transform + opacity only. */
|
||||
& .desktop-nav {
|
||||
@apply pointer-events-none absolute inset-x-0 top-14 min-h-60 translate-y-1.5 pt-7 pb-8 pl-[calc(50%-13rem)] opacity-0 transition-[opacity,transform] duration-200 ease-out;
|
||||
|
||||
&.active {
|
||||
@apply pointer-events-auto translate-y-0 opacity-100;
|
||||
}
|
||||
|
||||
& .desktop-nav-anchor {
|
||||
@apply absolute top-7 left-[calc(50%-30rem)] w-56 max-lg:hidden;
|
||||
|
||||
& .desktop-nav-title {
|
||||
@apply text-foreground text-2xl font-semibold tracking-tight;
|
||||
}
|
||||
}
|
||||
|
||||
& .desktop-nav-list {
|
||||
/* Vertical fill: columns of equal height, no orphan rows. Row
|
||||
count is set per submenu via --menu-rows in menu-aurora.js.
|
||||
w-max is load-bearing: without it the block-level list fills
|
||||
the viewport and justify-content:normal stretches the auto
|
||||
column tracks across all of it. */
|
||||
@apply grid w-max grid-flow-col grid-rows-[repeat(var(--menu-rows,6),auto)] content-start gap-x-8 gap-y-1;
|
||||
|
||||
& > li {
|
||||
@apply m-0 list-none;
|
||||
|
||||
& > a {
|
||||
@apply text-foreground block w-fit py-2.5 text-sm whitespace-nowrap no-underline hover:underline hover:decoration-2 hover:underline-offset-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Constant part of the canvas, Apple-flyout style: the device
|
||||
summary lives once at container level, pinned just below the
|
||||
fixed category-title slot (header 3.5rem + panel pt 1.75rem +
|
||||
title line 2rem + 1.25rem gap = top-34). Its position never
|
||||
depends on an individual panel's height, and switching categories
|
||||
never re-fades it. */
|
||||
& .desktop-menu-board {
|
||||
@apply absolute top-34 left-[calc(50%-30rem)] flex w-56 flex-col gap-2 max-lg:hidden;
|
||||
|
||||
& .board-line {
|
||||
@apply text-muted-foreground/80 flex max-w-full items-center gap-2.5 text-xs;
|
||||
|
||||
&::before {
|
||||
@apply size-4 shrink-0 bg-current content-[''];
|
||||
}
|
||||
|
||||
& .board-text {
|
||||
@apply min-w-0 truncate;
|
||||
}
|
||||
}
|
||||
|
||||
& .board-line-host::before {
|
||||
@apply [mask:url('@assets/icons/server.svg')_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .board-line-model::before {
|
||||
@apply [mask:url('@assets/icons/cpu-chip.svg')_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .board-line-firmware::before {
|
||||
@apply [mask:url('@assets/icons/cube.svg')_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .board-line-kernel::before {
|
||||
@apply [mask:url('@assets/icons/cog.svg')_center/contain_no-repeat];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +118,10 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
/* Boxed-dropdown panels only — mega-menu panels are reparented into
|
||||
.desktop-menu-container (see above). */
|
||||
& .desktop-nav {
|
||||
@apply pointer-events-none left-0 z-40 px-0 pt-0 opacity-0 transition-opacity duration-150;
|
||||
[data-nav-type="mega-menu"] & {
|
||||
@apply fixed top-14 left-1/2 -translate-x-1/2 pb-6;
|
||||
}
|
||||
[data-nav-type="boxed-dropdown"] & {
|
||||
@apply absolute top-full mt-2 min-w-48;
|
||||
}
|
||||
@@ -52,10 +130,6 @@ header {
|
||||
}
|
||||
|
||||
& .desktop-nav-list {
|
||||
[data-nav-type="mega-menu"] & {
|
||||
@apply grid w-max grid-cols-4 gap-x-4 gap-y-2;
|
||||
}
|
||||
|
||||
[data-nav-type="boxed-dropdown"] & {
|
||||
@apply border-border bg-header-bg/90 flex flex-col gap-y-1 rounded-3xl border py-2 shadow-lg backdrop-blur-md backdrop-saturate-150;
|
||||
}
|
||||
@@ -63,13 +137,10 @@ header {
|
||||
@apply m-0 list-none;
|
||||
|
||||
& > a {
|
||||
@apply text-foreground block px-4 whitespace-nowrap no-underline;
|
||||
@apply text-foreground block whitespace-nowrap no-underline;
|
||||
|
||||
[data-nav-type="mega-menu"] & {
|
||||
@apply py-3 hover:underline hover:decoration-2 hover:underline-offset-4;
|
||||
}
|
||||
[data-nav-type="boxed-dropdown"] & {
|
||||
@apply hover:bg-header-interactive mx-2 rounded-xl py-2;
|
||||
@apply hover:bg-header-interactive mx-2 rounded-xl px-4 py-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,9 +514,24 @@ return baseclass.extend({
|
||||
let nav = null;
|
||||
|
||||
if (hasSubmenu) {
|
||||
nav = E("div", { class: "desktop-nav" }, [
|
||||
this.renderMainMenu(child, `${url}/${child.name}`, 1),
|
||||
]);
|
||||
const list = this.renderMainMenu(child, `${url}/${child.name}`, 1);
|
||||
const children = [list];
|
||||
|
||||
if (document.body?.dataset?.navType === "mega-menu") {
|
||||
// Constant canvas: links fill top-to-bottom; capping at 4 columns
|
||||
// keeps the canvas height stable until a submenu exceeds 24 items.
|
||||
list.style.setProperty(
|
||||
"--menu-rows",
|
||||
Math.max(6, Math.ceil(submenu.length / 4)),
|
||||
);
|
||||
children.unshift(
|
||||
E("div", { class: "desktop-nav-anchor" }, [
|
||||
E("span", { class: "desktop-nav-title" }, [_(child.title)]),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
nav = E("div", { class: "desktop-nav" }, children);
|
||||
li.appendChild(nav);
|
||||
menuLink.addEventListener("click", (e) => e.preventDefault());
|
||||
}
|
||||
@@ -545,6 +560,33 @@ return baseclass.extend({
|
||||
let showTimer = null;
|
||||
let hideTimer = null;
|
||||
|
||||
// Constant canvas: every category opens at the same height — the
|
||||
// tallest submenu wins. Measured lazily on first open (after fonts
|
||||
// settle) and cached, so switching categories is a pure cross-fade
|
||||
// with zero layout work. A resize invalidates the cache.
|
||||
let canvasHeight = 0;
|
||||
|
||||
const applyCanvasHeight = () => {
|
||||
if (!canvasHeight) {
|
||||
const headerHeight =
|
||||
header.querySelector(".header-content")?.offsetHeight || 56;
|
||||
let maxPanel = 0;
|
||||
header
|
||||
.querySelectorAll(".desktop-nav")
|
||||
.forEach((nav) => (maxPanel = Math.max(maxPanel, nav.scrollHeight)));
|
||||
canvasHeight = headerHeight + maxPanel;
|
||||
}
|
||||
container?.style.setProperty("--mega-menu-height", `${canvasHeight}px`);
|
||||
};
|
||||
|
||||
let resizeTimer = null;
|
||||
window.addEventListener("resize", () => {
|
||||
canvasHeight = 0;
|
||||
if (!container?.classList.contains("active")) return;
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(applyCanvasHeight, 150);
|
||||
});
|
||||
|
||||
children.forEach((child) => {
|
||||
const { li, nav, menuLink, hasSubmenu } = this.buildDropdownItem(
|
||||
child,
|
||||
@@ -553,6 +595,13 @@ return baseclass.extend({
|
||||
);
|
||||
if (!hasSubmenu) return;
|
||||
|
||||
// Reparent the panel into the canvas container: the transformed
|
||||
// #topmenu would otherwise become the containing block of the
|
||||
// absolutely-positioned panel and shrink the canvas to the menu's
|
||||
// width. Inside the container the panel is also clipped by the same
|
||||
// clip-path reveal as the rest of the canvas.
|
||||
if (container) container.appendChild(nav);
|
||||
|
||||
li.addEventListener("mouseenter", () => {
|
||||
if (hideTimer) {
|
||||
clearTimeout(hideTimer);
|
||||
@@ -567,16 +616,9 @@ return baseclass.extend({
|
||||
if (wasActive) return;
|
||||
|
||||
menuLink.classList.add("menu-active");
|
||||
const navHeight = nav.scrollHeight;
|
||||
const headerHeight =
|
||||
header.querySelector(".header-content")?.offsetHeight || 56;
|
||||
const totalHeight = headerHeight + navHeight;
|
||||
|
||||
if (container) {
|
||||
container.style.setProperty(
|
||||
"--mega-menu-height",
|
||||
`${totalHeight}px`,
|
||||
);
|
||||
applyCanvasHeight();
|
||||
container.classList.add("active");
|
||||
overlay.classList.add("active");
|
||||
}
|
||||
@@ -668,10 +710,10 @@ return baseclass.extend({
|
||||
const wasActive = container.classList.contains("active");
|
||||
container.classList.remove("active");
|
||||
|
||||
// --mega-menu-height is set per-submenu in initMegaMenu and otherwise
|
||||
// never cleared. Drop it back to the h-0 fallback once the container is
|
||||
// fully hidden, so a closed menu doesn't leave invisible scrollable
|
||||
// space below the header on pages shorter than the last submenu.
|
||||
// --mega-menu-height is set on open in initMegaMenu and otherwise never
|
||||
// cleared. Drop it back to the h-0 fallback once the container is fully
|
||||
// hidden, so a closed menu doesn't leave invisible scrollable space
|
||||
// below the header on pages shorter than the canvas.
|
||||
const resetHeight = () => {
|
||||
if (!container.classList.contains("active")) {
|
||||
container.style.removeProperty("--mega-menu-height");
|
||||
|
||||
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=0.12.6
|
||||
PKG_RELEASE:=24
|
||||
PKG_VERSION:=0.12.7
|
||||
PKG_RELEASE:=25
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:"";--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base)*2);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkitLine truncated
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:"";--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base)*2);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkitLine truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:"";--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-2xl:calc(var(--radius-base)*2);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}Line truncated
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-content:"";--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-2xl:calc(var(--radius-base)*2);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}Line truncated
|
||||
@@ -1 +1 @@
|
||||
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const resetMobileSubmenus=()=>{document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(item=>item.classList.remove("submenu-expanded"))};const expandActiveMobileGroup=()=>{document.querySelector(".mobile-nav-item.has-active")?.classList.add("submenu-expanded")};const updateToggleState=expanded=>{navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.style.overflow="";resetMobileSubmenus()};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)expandActiveMobileGroup();else resetMobileSubmenus();updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const link=e.target.closest(".mobile-nav-link");if(!link)return;const item=link.closest(".mobile-nav-item");const submenu=item?.querySelector(".mobile-nav-submenu");if(submenu){e.preventDefault();e.stopPropagation();const isExpanded=item.classList.contains("submenu-expanded");document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(i=>{if(i!==item)i.classList.remove("submenu-expanded")});item.classList.toggle("submenu-expanded",!isExpanded)}})},renderMobileMenu(tree,url){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");const children=ui.menu.getChildren(tree);if(!list||!footerAction||!children.length)return;list.innerHTML="";footerAction.innerHTML="";children.forEach(child=>{const submenu=ui.menu.getChildren(child);const hasSubmenu=submenu.length>0;const isActive=this.isActivePath(child.name);if(child.name==="logout"){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:L.url(url,child.name)},[_(child.title)]));return}const itemClasses=["mobile-nav-item",hasSubmenu?"has-submenu":"",isActive&&hasSubmenu?"has-active":""].filter(Boolean);const li=E("li",{class:itemClasses.join(" ")},[E("a",{class:`mobile-nav-link${isActive&&!hasSubmenu?" nav-link-active":""}`,href:hasSubmenu?"#":L.url(url,child.name)},[_(child.title)])]);if(hasSubmenu){li.appendChild(this.buildCategoryPreview(child.name,submenu));const wrap=E("div",{class:"mobile-nav-submenu"});const ul=E("ul",{class:"mobile-nav-submenu-list"});submenu.forEach(item=>{const subActive=this.isActivePath(child.name,item.name);ul.appendChild(E("li",{class:"mobile-nav-subitem"},[E("a",{class:`mobile-nav-sublink${subActive?" nav-link-active":""}`,href:L.url(url,child.name,item.name)},[_(item.title)])]))});wrap.appendChild(ul);li.appendChild(wrap)}list.appendChild(li)})},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,Line truncated
|
||||
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const resetMobileSubmenus=()=>{document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(item=>item.classList.remove("submenu-expanded"))};const expandActiveMobileGroup=()=>{document.querySelector(".mobile-nav-item.has-active")?.classList.add("submenu-expanded")};const updateToggleState=expanded=>{navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.style.overflow="";resetMobileSubmenus()};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)expandActiveMobileGroup();else resetMobileSubmenus();updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const link=e.target.closest(".mobile-nav-link");if(!link)return;const item=link.closest(".mobile-nav-item");const submenu=item?.querySelector(".mobile-nav-submenu");if(submenu){e.preventDefault();e.stopPropagation();const isExpanded=item.classList.contains("submenu-expanded");document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(i=>{if(i!==item)i.classList.remove("submenu-expanded")});item.classList.toggle("submenu-expanded",!isExpanded)}})},renderMobileMenu(tree,url){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");const children=ui.menu.getChildren(tree);if(!list||!footerAction||!children.length)return;list.innerHTML="";footerAction.innerHTML="";children.forEach(child=>{const submenu=ui.menu.getChildren(child);const hasSubmenu=submenu.length>0;const isActive=this.isActivePath(child.name);if(child.name==="logout"){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:L.url(url,child.name)},[_(child.title)]));return}const itemClasses=["mobile-nav-item",hasSubmenu?"has-submenu":"",isActive&&hasSubmenu?"has-active":""].filter(Boolean);const li=E("li",{class:itemClasses.join(" ")},[E("a",{class:`mobile-nav-link${isActive&&!hasSubmenu?" nav-link-active":""}`,href:hasSubmenu?"#":L.url(url,child.name)},[_(child.title)])]);if(hasSubmenu){li.appendChild(this.buildCategoryPreview(child.name,submenu));const wrap=E("div",{class:"mobile-nav-submenu"});const ul=E("ul",{class:"mobile-nav-submenu-list"});submenu.forEach(item=>{const subActive=this.isActivePath(child.name,item.name);ul.appendChild(E("li",{class:"mobile-nav-subitem"},[E("a",{class:`mobile-nav-sublink${subActive?" nav-link-active":""}`,href:L.url(url,child.name,item.name)},[_(item.title)])]))});wrap.appendChild(ul);li.appendChild(wrap)}list.appendChild(li)})},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,Line truncated
|
||||
@@ -202,7 +202,28 @@
|
||||
</div>
|
||||
|
||||
{% if (nav_submenu_type == 'mega-menu'): %}
|
||||
<div class="desktop-menu-container"></div>
|
||||
{%
|
||||
/* boardinfo fields are not guaranteed on every platform; the
|
||||
summary tolerates a missing model/firmware line (hostname is
|
||||
always set by the kernel). */
|
||||
const board_model = boardinfo.model ?? boardinfo.system ?? boardinfo.board_name;
|
||||
const board_firmware = boardinfo.release?.description ??
|
||||
trim(`${boardinfo.release?.distribution ?? ''} ${boardinfo.release?.version ?? ''}`);
|
||||
-%}
|
||||
<div class="desktop-menu-container">
|
||||
<div class="desktop-menu-board" aria-hidden="true">
|
||||
<span class="board-line board-line-host" title="{{ entityencode(striptags(boardinfo.hostname ?? '?'), true) }}"><span class="board-text">{{ striptags(boardinfo.hostname ?? '?') }}</span></span>
|
||||
{% if (board_model): %}
|
||||
<span class="board-line board-line-model" title="{{ entityencode(striptags(board_model), true) }}"><span class="board-text">{{ striptags(board_model) }}</span></span>
|
||||
{% endif %}
|
||||
{% if (board_firmware): %}
|
||||
<span class="board-line board-line-firmware" title="{{ entityencode(striptags(board_firmware), true) }}"><span class="board-text">{{ striptags(board_firmware) }}</span></span>
|
||||
{% endif %}
|
||||
{% if (boardinfo.kernel): %}
|
||||
<span class="board-line board-line-kernel" title="{{ entityencode(striptags(boardinfo.kernel), true) }}"><span class="board-text">{{ striptags(boardinfo.kernel) }}</span></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="desktop-menu-overlay"></div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=opera-proxy
|
||||
PKG_VERSION:=1.23.0
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=1.24.0
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
Reference in new issue
Block a user