diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4cabe33..5e0a1d2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -220,11 +220,11 @@ "description": "Username selector label" }, "usernameSelectorPlaceholder": { - "message": "Empty=auto find, e.g. #username or input[name=account]", + "message": "Empty=auto find, or paste element HTML / CSS selector", "description": "Username selector placeholder" }, "usernameSelectorHelp": { - "message": "Leave empty. Wrong value will cause username not filled.", + "message": "Paste the input's DOM node, or a CSS selector; empty = auto find.", "description": "Username selector help" }, "passwordSelectorLabel": { @@ -232,11 +232,11 @@ "description": "Password selector label" }, "passwordSelectorPlaceholder": { - "message": "Empty=auto find, e.g. #password or input[type=password]", + "message": "Empty=auto find, or paste element HTML / CSS selector", "description": "Password selector placeholder" }, "passwordSelectorHelp": { - "message": "Leave empty. Wrong value will cause password not filled.", + "message": "Paste the input's DOM node, or a CSS selector; empty = auto find.", "description": "Password selector help" }, "submitSelectorLabel": { @@ -244,11 +244,11 @@ "description": "Submit selector label" }, "submitSelectorPlaceholder": { - "message": "Empty=auto find, e.g. #loginBtn or button[type=submit]", + "message": "Empty=auto find, or paste element HTML / CSS selector", "description": "Submit selector placeholder" }, "submitSelectorHelp": { - "message": "Leave empty. Wrong value will cause auto-submit not to click.", + "message": "Paste the button's DOM node, or a CSS selector; empty = auto find.", "description": "Submit selector help" }, "addConfigBtn": { diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index b343c67..4d2745c 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -220,11 +220,11 @@ "description": "用户名选择器 label" }, "usernameSelectorPlaceholder": { - "message": "留空=自动查找,如 #username 或 input[name=account]", + "message": "留空=自动查找,可粘贴元素 HTML 或填 CSS 选择器", "description": "用户名选择器 placeholder" }, "usernameSelectorHelp": { - "message": "留空即可。填错会导致用户名填不进框。", + "message": "直接粘贴该输入框的 DOM 节点,或填 CSS 选择器;留空则自动查找。", "description": "用户名选择器下方说明" }, "passwordSelectorLabel": { @@ -232,11 +232,11 @@ "description": "密码选择器 label" }, "passwordSelectorPlaceholder": { - "message": "留空=自动查找,如 #password 或 input[type=password]", + "message": "留空=自动查找,可粘贴元素 HTML 或填 CSS 选择器", "description": "密码选择器 placeholder" }, "passwordSelectorHelp": { - "message": "留空即可。填错会导致密码填不进框。", + "message": "直接粘贴该输入框的 DOM 节点,或填 CSS 选择器;留空则自动查找。", "description": "密码选择器下方说明" }, "submitSelectorLabel": { @@ -244,11 +244,11 @@ "description": "登录按钮选择器 label" }, "submitSelectorPlaceholder": { - "message": "留空=自动查找,如 #loginBtn 或 button[type=submit]", + "message": "留空=自动查找,可粘贴元素 HTML 或填 CSS 选择器", "description": "登录按钮选择器 placeholder" }, "submitSelectorHelp": { - "message": "留空即可。填错会导致不会自动点击登录。", + "message": "直接粘贴该按钮的 DOM 节点,或填 CSS 选择器;留空则自动查找。", "description": "登录按钮选择器下方说明" }, "addConfigBtn": { diff --git a/content.js b/content.js index 239948a..1fc9571 100644 --- a/content.js +++ b/content.js @@ -71,13 +71,49 @@ function setNativeValue(el, value) { el.dispatchEvent(new Event("change", { bubbles: true })); } -function findUsernameField(config) { - let searchScope = config.usernameSelector ? document.querySelector(config.usernameSelector) : null; - if (!searchScope) { - const passwordField = findPasswordField(config); - if (!passwordField) return null; - searchScope = passwordField.closest("form") || document; +function resolveElement(value, multiple) { + if (!value) return null; + const trimmed = value.trim(); + if (!trimmed.startsWith("<")) { + return multiple ? Array.from(document.querySelectorAll(trimmed)) : document.querySelector(trimmed); } + const doc = new DOMParser().parseFromString(trimmed, "text/html"); + const el = doc.body.firstElementChild; + if (!el) return null; + const tag = el.tagName.toLowerCase(); + const id = el.id; + const cls = Array.from(el.classList); + const type = el.getAttribute("type"); + const nameAttr = el.getAttribute("name"); + const candidates = []; + if (id) candidates.push(`${tag}#${CSS.escape(id)}`); + if (cls.length) candidates.push(`${tag}.${cls.map(CSS.escape).join(".")}`); + if (type) candidates.push(`${tag}[type="${type}"]`); + if (nameAttr) candidates.push(`${tag}[name="${CSS.escape(nameAttr)}"]`); + for (const c of cls) candidates.push(`${tag}.${CSS.escape(c)}`); + candidates.push(tag); + for (const sel of candidates) { + if (multiple) { + const els = Array.from(document.querySelectorAll(sel)); + if (els.length) return els; + } else { + const found = document.querySelector(sel); + if (found) return found; + } + } + return null; +} + +function findUsernameField(config) { + if (config.usernameSelector) { + const el = resolveElement(config.usernameSelector); + if (el) return el; + } + + const passwordField = findPasswordField(config); + if (!passwordField) return null; + + const searchScope = passwordField.closest("form") || document; const selectors = [ "input[type='text']", @@ -125,8 +161,12 @@ function findUsernameField(config) { } function findPasswordField(config) { - const scope = config.passwordSelector ? (document.querySelector(config.passwordSelector) || document) : document; - const passwordInputs = scope.querySelectorAll("input[type='password']"); + if (config.passwordSelector) { + const el = resolveElement(config.passwordSelector); + if (el) return el; + } + + const passwordInputs = document.querySelectorAll("input[type='password']"); for (const input of passwordInputs) { if (input.offsetParent !== null || input.offsetWidth > 0) { return input; @@ -137,12 +177,26 @@ function findPasswordField(config) { } function findSubmitButton(config, passwordField) { - let searchScope = config.submitSelector ? document.querySelector(config.submitSelector) : null; - if (!searchScope) { - const form = passwordField ? passwordField.closest("form") : null; - searchScope = form || document; + if (config.submitSelector) { + const els = resolveElement(config.submitSelector, true); + if (els && els.length === 1) return els[0]; + if (els && els.length > 1) { + for (const el of els) { + if (el.offsetParent === null && el.offsetWidth === 0) continue; + const compact = (el.textContent || el.value || "").toLowerCase().replace(/\s/g, ""); + if (compact.includes("登录") || compact.includes("login") || compact.includes("signin")) { + return el; + } + } + for (const el of els) { + if (el.offsetParent !== null || el.offsetWidth > 0) return el; + } + } } + const form = passwordField ? passwordField.closest("form") : null; + const searchScope = form || document; + const submitSelectors = [ "button[type='submit']", "input[type='submit']", diff --git a/popup.js b/popup.js index 60b6153..c53ecdd 100644 --- a/popup.js +++ b/popup.js @@ -1052,6 +1052,7 @@ document.addEventListener("DOMContentLoaded", () => { } const value = u.pathname && u.pathname !== "/" ? host + u.pathname : host; domainInput.value = value; + if (tab.title) aliasInput.value = tab.title; saveDraft(); showToast(t("toastDomainFilled", value)); } catch { diff --git a/utils.js b/utils.js index f22f38b..2cbed2b 100644 --- a/utils.js +++ b/utils.js @@ -19,18 +19,20 @@ if (!url || !pattern) return false; try { const u = new URL(url); - const hostname = u.hostname; + const rawPattern = String(pattern).trim(); if (rawPattern.includes("/")) { const slashIdx = rawPattern.indexOf("/"); const hostPart = rawPattern.slice(0, slashIdx); const pathPart = rawPattern.slice(slashIdx + 1) || ""; - if (!matchHost(hostname, hostPart)) return false; + const hostForMatch = hostPart.includes(":") ? u.host : u.hostname; + if (!matchHost(hostForMatch, hostPart)) return false; return matchPath(u.pathname, pathPart); } - return matchHost(hostname, rawPattern); + const hostForMatch = rawPattern.includes(":") ? u.host : u.hostname; + return matchHost(hostForMatch, rawPattern); } catch { return false; }