diff --git a/frontend/theme/Nebula/assets/app.js b/frontend/theme/Nebula/assets/app.js index 5e54fab..73fe321 100644 --- a/frontend/theme/Nebula/assets/app.js +++ b/frontend/theme/Nebula/assets/app.js @@ -3,7 +3,7 @@ const theme = window.NEBULA_THEME || {}; const app = document.getElementById('app'); - const loader = document.getElementById('nebula-loader'); + let loader = document.getElementById('nebula-loader'); const themeMediaQuery = getThemeMediaQuery(); if (!app) { @@ -73,7 +73,7 @@ } if (state.authToken) { - var loaded = await loadDashboard(); + const loaded = await loadDashboard(); if (!loaded) { state.loading = false; } @@ -652,40 +652,64 @@ function findNodeLinkByName(links, name) { if (!name) return null; - var target = name.trim(); + const target = name.trim(); + const targetLower = target.toLowerCase(); - console.log("Nebula: Searching for node '" + name + "' among " + links.length + " links (Strict Match)"); + console.log('Nebula: Searching for node \'' + name + '\' among ' + links.length + ' links'); - for (var i = 0; i < links.length; i++) { - var link = links[i].trim(); - var remark = ""; + let fuzzyMatch = null; + + for (let i = 0; i < links.length; i++) { + const link = links[i].trim(); + let remark = ''; - if (link.indexOf("#") !== -1) { - var parts = link.split("#"); - var rawRemark = parts[parts.length - 1]; + if (link.indexOf('#') !== -1) { + const parts = link.split('#'); + const rawRemark = parts[parts.length - 1]; try { - remark = decodeURIComponent(rawRemark.replace(/\+/g, "%20")); + remark = decodeURIComponent(rawRemark.replace(/\+/g, '%20')); } catch (e) { remark = rawRemark; } - } else if (link.indexOf("vmess://") === 0) { + } else if (link.indexOf('vmess://') === 0) { try { - var jsonStr = utf8Base64Decode(link.slice(8)); - var json = JSON.parse(jsonStr); - remark = json.ps || ""; + const jsonStr = utf8Base64Decode(link.slice(8)); + const json = JSON.parse(jsonStr); + remark = json.ps || ''; } catch (e) {} - } else if (link.indexOf("name:") !== -1) { - var yamlMatch = link.match(/name:\s*["']?([^"']+)["']?/); + } else if (link.indexOf('name:') !== -1) { + const yamlMatch = link.match(/name:\s*["']?([^"']+)["']?/); if (yamlMatch) remark = yamlMatch[1]; } - // Strict exact match after trimming - if ((remark || "").trim() === target) { + const normalizedRemark = (remark || '').trim(); + const normalizedLower = normalizedRemark.toLowerCase(); + + // Exact match + if (normalizedRemark === target) { return link; } + + // Case-insensitive match as candidate + if (!fuzzyMatch && normalizedLower === targetLower) { + fuzzyMatch = link; + } + + // If one contains the other (very common with emojis or plan prefixes) + if (!fuzzyMatch && (normalizedLower.indexOf(targetLower) !== -1 || targetLower.indexOf(normalizedLower) !== -1)) { + // Only use this if it's a "strong" enough match (at least 50% length overlap) + if (Math.abs(normalizedLower.length - targetLower.length) < Math.max(normalizedLower.length, targetLower.length) * 0.4) { + fuzzyMatch = link; + } + } } - console.warn("Nebula: No exact match found for node '" + name + "'"); + if (fuzzyMatch) { + console.log('Nebula: Using fuzzy match for node \'' + name + '\''); + return fuzzyMatch; + } + + console.warn('Nebula: No match found for node \'' + name + '\''); return null; } @@ -2635,13 +2659,42 @@ if (!value) { return; } - navigator.clipboard.writeText(value).then(function () { - showMessage(successMessage || "内容已复制到剪贴板", "success"); + + const resolve = () => { + showMessage(successMessage || '内容已复制到剪贴板', 'success'); render(); - }).catch(function () { - showMessage("复制失败,请尝试手动复制", "error"); + }; + + const reject = () => { + showMessage('复制失败,请尝试手动复制', 'error'); render(); - }); + }; + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(value).then(resolve).catch(reject); + } else { + // Fallback for non-secure contexts or unsupported browsers + try { + const textArea = document.createElement('textarea'); + textArea.value = value; + textArea.style.position = 'fixed'; + textArea.style.left = '-9999px'; + textArea.style.top = '0'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + if (successful) { + resolve(); + } else { + reject(); + } + } catch (err) { + console.error('Fallback copy failed', err); + reject(); + } + } } function formatTraffic(value) {