🎄 Sync 2025-11-28 00:12:29

This commit is contained in:
actions-user
2025-11-28 00:12:29 +08:00
parent bf596d0fcb
commit 308506363e
10 changed files with 116 additions and 40 deletions

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall PKG_NAME:=luci-app-passwall
PKG_VERSION:=25.11.15 PKG_VERSION:=25.11.27
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)

File diff suppressed because one or more lines are too long

View File

@@ -204,7 +204,7 @@ o.cfgvalue = function(t, n)
str = str ~= "" and "<br>" .. str or "" str = str ~= "" and "<br>" .. str or ""
local num = 0 local num = 0
m.uci:foreach(appname, "nodes", function(s) m.uci:foreach(appname, "nodes", function(s)
if s["group"] ~= "" and s["group"] == remark then if s["group"] and s["group"]:lower() == remark:lower() then
num = num + 1 num = num + 1
end end
end) end)

View File

@@ -618,8 +618,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSO
o:depends({ [_n("use_xhttp_extra")] = true }) o:depends({ [_n("use_xhttp_extra")] = true })
o.rows = 15 o.rows = 15
o.wrap = "off" o.wrap = "off"
o.custom_cfgvalue = function(self, section, value)
local raw = m:get(section, "xhttp_extra")
if raw then
return api.base64Decode(raw)
end
end
o.custom_write = function(self, section, value) o.custom_write = function(self, section, value)
m:set(section, self.option:sub(1 + #option_prefix), value) m:set(section, "xhttp_extra", api.base64Encode(value))
local success, data = pcall(jsonc.parse, value) local success, data = pcall(jsonc.parse, value)
if success and data then if success and data then
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address) local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
@@ -642,7 +648,7 @@ o.validate = function(self, value)
return value return value
end end
o.custom_remove = function(self, section, value) o.custom_remove = function(self, section, value)
m:del(section, self.option:sub(1 + #option_prefix)) m:del(section, "xhttp_extra")
m:del(section, "download_address") m:del(section, "download_address")
end end

View File

@@ -113,19 +113,15 @@ function exec_call(cmd)
end end
function base64Decode(text) function base64Decode(text)
local raw = text
if not text then return '' end if not text then return '' end
text = text:gsub("%z", "") local encoded = text:gsub("%z", ""):gsub("%c", ""):gsub("_", "/"):gsub("-", "+")
text = text:gsub("%c", "") local mod4 = #encoded % 4
text = text:gsub("_", "/") encoded = encoded .. string.sub('====', mod4 + 1)
text = text:gsub("-", "+") local result = nixio.bin.b64decode(encoded)
local mod4 = #text % 4
text = text .. string.sub('====', mod4 + 1)
local result = nixio.bin.b64decode(text)
if result then if result then
return result:gsub("%z", "") return result:gsub("%z", "")
else else
return raw return text
end end
end end

View File

@@ -223,7 +223,7 @@ function gen_outbound(flag, node, tag, proxy_table)
host = node.xhttp_host, host = node.xhttp_host,
-- 如果包含 "extra" 节,取 "extra" 内的内容,否则直接赋值给 extra -- 如果包含 "extra" 节,取 "extra" 内的内容,否则直接赋值给 extra
extra = node.xhttp_extra and (function() extra = node.xhttp_extra and (function()
local success, parsed = pcall(jsonc.parse, node.xhttp_extra) local success, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra))
if success then if success then
return parsed.extra or parsed return parsed.extra or parsed
else else

View File

@@ -52,7 +52,7 @@ local api = require "luci.passwall.api"
function add_node() { function add_node() {
var nodes_link = document.getElementById("nodes_link").value; var nodes_link = document.getElementById("nodes_link").value;
var group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default"); var group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default");
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").trim(); nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/\s+/g, '').replace(/<[^>]*>/g, '').trim();
if (nodes_link != "") { if (nodes_link != "") {
var s = nodes_link.split('://'); var s = nodes_link.split('://');
if (s.length > 1) { if (s.length > 1) {

View File

@@ -1,6 +1,7 @@
<% <%
local api = require "luci.passwall.api" local api = require "luci.passwall.api"
-%> -%>
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
<style> <style>
table th, .table .th { table th, .table .th {
@@ -38,9 +39,9 @@ table td, .table .td {
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
._now_use_bg { ._now_use_bg {
background: #4a90e2 !important; background: #4a90e2 !important;
} }
} }
.td.cbi-section-actions { .td.cbi-section-actions {
@@ -55,18 +56,46 @@ table td, .table .td {
.node-wrapper .cbi-input-checkbox { .node-wrapper .cbi-input-checkbox {
flex-grow: 0 !important; flex-grow: 0 !important;
flex-shrink: 0;
flex-basis: auto; flex-basis: auto;
} }
.cbi-tabmenu > li { .cbi-tabmenu > li {
margin-right: 2px !important; margin-right: 2px !important;
} }
.cbi-tabmenu > li:last-child { .cbi-tabmenu > li:last-child {
margin-right: 0 !important; margin-right: 0 !important;
} }
.node-wrapper .drag-handle {
cursor: grab !important;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 100;
padding: 0 !important;
line-height: inherit;
user-select: none;
color: #00000070;
align-self: stretch;
}
.sortable-chosen {
background-color: rgba(220, 235, 245, 0.4) !important;
opacity: 0.7;
}
.sortable-ghost {
background: #cce5ff !important;
height: 3px !important;
}
.dragging-row {
background-color: rgba(131, 191, 255, 0.7) !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
</style> </style>
<% if api.is_js_luci() then -%> <% if api.is_js_luci() then -%>
@@ -210,21 +239,6 @@ table td, .table .td {
document.getElementById("set_node_name").innerHTML = ""; document.getElementById("set_node_name").innerHTML = "";
} }
function row_swap(btn, up) {
const row = btn.closest("tr");
if (!row) return;
const parent = row.parentNode;
if (up) {
const prev = row.previousElementSibling;
if (prev && !prev.classList.contains("cbi-section-table-titles")) {
parent.insertBefore(row, prev);
}
} else {
const next = row.nextElementSibling;
if (next) parent.insertBefore(next, row);
}
}
function row_top(btn) { function row_top(btn) {
const row = btn.closest("tr"); const row = btn.closest("tr");
if (!row) return; if (!row) return;
@@ -548,6 +562,60 @@ table td, .table .td {
} }
} }
} }
//列表拖动重排
function initSortableForTable(table) {
if (!table) return null;
var root = table.querySelector('tbody') || table;
if (root._sortable_initialized) return root._sortable_instance;
root._sortable_initialized = true;
var opts = {
handle: ".drag-handle",
draggable: "tr.cbi-section-table-row",
animation: 150,
ghostClass: "dragging-row",
fallbackOnBody: true,
forceFallback: false,
swapThreshold: 0.65,
onEnd: function (evt) {
//var group = evt.to.id.replace("cbi-passwall-nodes-", "").replace("-table", "");
//save_current_page_order(group); // 自动提交保存
}
};
try {
var instance = Sortable.create(root, opts);
root._sortable_instance = instance;
return instance;
} catch (err) {
root._sortable_initialized = false;
console.error("Sortable init failed:", err);
return null;
}
}
function initAllSortable(group_nodes) {
if (typeof Sortable === 'undefined') {
var retries = 0;
var maxRetries = 25;
var t = setInterval(function () {
retries++;
if (typeof Sortable !== 'undefined') {
clearInterval(t);
for (var group in group_nodes) {
var table = document.getElementById("cbi-passwall-nodes-" + group + "-table");
initSortableForTable(table);
}
} else if (retries >= maxRetries) {
clearInterval(t);
}
}, 200);
} else {
for (var group in group_nodes) {
var table = document.getElementById("cbi-passwall-nodes-" + group + "-table");
initSortableForTable(table);
}
}
}
</script> </script>
<script type="text/template" id="nodes-table-template"> <script type="text/template" id="nodes-table-template">
@@ -584,10 +652,9 @@ table td, .table .td {
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/> <input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/> <input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/>
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/> <input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>
<input class="btn cbi-button cbi-button-up" type="button" value="<%:Move up%>" onclick="return row_swap(this, true)" title="<%:Move up%>">
<input class="btn cbi-button cbi-button-down" type="button" value="<%:Move down%>" onclick="return row_swap(this, false)" title="<%:Move down%>">
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>" onclick="location.href='<%=api.url("node_config")%>/{{id}}'" alt="<%:Edit%>" title="<%:Edit%>"> <input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>" onclick="location.href='<%=api.url("node_config")%>/{{id}}'" alt="<%:Edit%>" title="<%:Edit%>">
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="del_node('{{id}}')" alt="<%:Delete%>" title="<%:Delete%>"> <input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="del_node('{{id}}')" alt="<%:Delete%>" title="<%:Delete%>">
<span class="drag-handle center" title="<%:Drag to reorder%>"></span>
</div> </div>
</td> </td>
</tr> </tr>
@@ -749,6 +816,8 @@ table td, .table .td {
cbi_t_switch("passwall.nodes", default_group) cbi_t_switch("passwall.nodes", default_group)
} }
initAllSortable(group_nodes);
//clear expire data //clear expire data
if (localStorage && localStorage.length > 0) { if (localStorage && localStorage.length > 0) {
const now = Date.now(); const now = Date.now();

View File

@@ -427,6 +427,9 @@ msgstr "保存当前顺序"
msgid "Saved current page order successfully." msgid "Saved current page order successfully."
msgstr "保存当前页面顺序成功。" msgstr "保存当前页面顺序成功。"
msgid "Drag to reorder"
msgstr "拖动以重排"
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"

View File

@@ -504,8 +504,8 @@ local function processData(szType, content, add_mode, group)
end end
result.obfs_param = base64Decode(params.obfsparam) result.obfs_param = base64Decode(params.obfsparam)
result.protocol_param = base64Decode(params.protoparam) result.protocol_param = base64Decode(params.protoparam)
local group = base64Decode(params.group) -- local ssr_group = base64Decode(params.group)
if group then result.group = group end -- if ssr_group then result.ssr_group = ssr_group end
result.remarks = base64Decode(params.remarks) result.remarks = base64Decode(params.remarks)
elseif szType == 'vmess' then elseif szType == 'vmess' then
local info = jsonParse(content) local info = jsonParse(content)
@@ -1207,7 +1207,7 @@ local function processData(szType, content, add_mode, group)
result.xhttp_path = params.path result.xhttp_path = params.path
result.xhttp_mode = params.mode or "auto" result.xhttp_mode = params.mode or "auto"
result.use_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil result.use_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil result.xhttp_extra = (params.extra and params.extra ~= "") and api.base64Encode(params.extra) or nil
local success, Data = pcall(jsonParse, params.extra) local success, Data = pcall(jsonParse, params.extra)
if success and Data then if success and Data then
local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address) local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)