diff --git a/01_content/lib/client.js b/01_content/lib/client.js index 194a682..a3f50b3 100644 --- a/01_content/lib/client.js +++ b/01_content/lib/client.js @@ -16884,14 +16884,41 @@ function ctxOf(t) { const text2 = (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 60); let out = "<" + tag + (cls ? " ." + cls : "") + ">"; if (text2) out += "\u300C" + text2 + "\u300D"; + try { + const cs = getComputedStyle(el); + out += "\uFF08\u5B57\u53F7 " + cs.fontSize + "\uFF09"; + } catch { + } return out; } -function elementsInBox(x0, y0, x1, y1) { +function boxPayload(x0, y0, x1, y1) { const L = Math.min(x0, x1); const T2 = Math.min(y0, y1); const R = Math.max(x0, x1); const B = Math.max(y0, y1); - const out = []; + const cx = (L + R) / 2; + const cy = (T2 + B) / 2; + let primary = null; + try { + const stack = document.elementsFromPoint(cx, cy); + for (const el of stack) { + const tag = el.tagName.toLowerCase(); + if (tag === "script" || tag === "style" || tag === "svg" || tag === "path" || tag === "br" || tag === "template" || tag === "iframe") continue; + if (el === document.body || el === document.documentElement) continue; + const rect = el.getBoundingClientRect(); + if (rect.width <= 0 || rect.height <= 0) continue; + if (rect.right < L || rect.left > R || rect.bottom < T2 || rect.top > B) continue; + const text2 = (el.textContent || "").trim().replace(/\s+/g, " "); + if (!text2) continue; + const cs = getComputedStyle(el); + primary = { text: text2.slice(0, 60), fontSize: cs.fontSize, lineHeight: cs.lineHeight, color: cs.color }; + break; + } + } catch { + } + const seen = /* @__PURE__ */ new Set(); + if (primary) seen.add(primary.text); + const candidates = []; for (const el of document.querySelectorAll("*")) { const tag = el.tagName.toLowerCase(); if (tag === "script" || tag === "style" || tag === "svg" || tag === "path" || tag === "br" || tag === "template" || tag === "iframe") continue; @@ -16899,19 +16926,13 @@ function elementsInBox(x0, y0, x1, y1) { if (rect.width <= 0 || rect.height <= 0) continue; if (rect.right < L || rect.left > R || rect.bottom < T2 || rect.top > B) continue; const text2 = (el.textContent || "").trim().replace(/\s+/g, " "); - if (text2.length < 1 || text2.length > 80) continue; - out.push({ text: text2, area: rect.width * rect.height }); + if (text2.length < 1 || text2.length > 30) continue; + if (seen.has(text2)) continue; + seen.add(text2); + candidates.push(text2); + if (candidates.length >= 4) break; } - out.sort((a, b) => a.area - b.area); - const seen = /* @__PURE__ */ new Set(); - const res = []; - for (const o of out) { - if (seen.has(o.text)) continue; - seen.add(o.text); - res.push(o.text); - if (res.length >= 5) break; - } - return res; + return { primary, candidates }; } function fillHostInput(text2) { try { @@ -17000,8 +17021,9 @@ function AnnotationOverlay() { const ry2 = ((B - pr.top) / Math.max(1, pr.height) * 100).toFixed(1); stat += "\uFF0C\u7A97\u53E3\u5185 (" + rx1 + "%," + ry1 + "%)\u2192(" + rx2 + "%," + ry2 + "%)"; } - const texts = elementsInBox(L, T2, R, B); - if (texts.length > 0) stat += "\uFF0C\u6846\u5185\u6587\u5B57\uFF1A" + texts.map((t, i) => "[" + (i + 1) + "]" + t + (i === 0 ? "\u2190\u6700\u53EF\u80FD\u76EE\u6807" : "")).join(" "); + const hit = boxPayload(L, T2, R, B); + if (hit.primary) stat += "\uFF0C\u4E3B\u76EE\u6807\uFF1A" + hit.primary.text + "\uFF08\u5B57\u53F7 " + hit.primary.fontSize + "\uFF0C\u884C\u9AD8 " + hit.primary.lineHeight + "\uFF09"; + if (hit.candidates.length > 0) stat += "\uFF0C\u5019\u9009\uFF1A" + hit.candidates.map((t, i) => "[" + (i + 1) + "]" + t).join(" "); } else { stat = "\u5C4F\u5E55\u5750\u6807 (" + Math.round(e.clientX) + ", " + Math.round(e.clientY) + ")"; if (paneEl) { diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index a4d93b7..247f555 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -374,37 +374,54 @@ function ctxOf(t: EventTarget | null): string { const text = (el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 60) let out = '<' + tag + (cls ? ' .' + cls : '') + '>' if (text) out += '「' + text + '」' + try { const cs = getComputedStyle(el); out += '(字号 ' + cs.fontSize + ')' } catch {} return out } -/** 框选区域内的可见文字(最小元素优先、去重,最多 5 条) */ -function elementsInBox(x0: number, y0: number, x1: number, y1: number): string[] { +/** 框选 payload v3:主目标 = 框中心点处最具体的可见元素(elementsFromPoint 顶层优先), + * 直接带出它的文字与 computed style(字号/行高/颜色——「字号多少」类问题直接给答案); + * 候选 = 与框相交的独立短文本(去重,最多 4)。 */ +function boxPayload(x0: number, y0: number, x1: number, y1: number): { primary: { text: string; fontSize: string; lineHeight: string; color: string } | null; candidates: string[] } { const L = Math.min(x0, x1) const T = Math.min(y0, y1) const R = Math.max(x0, x1) const B = Math.max(y0, y1) - const out: { text: string; area: number }[] = [] + const cx = (L + R) / 2 + const cy = (T + B) / 2 + let primary: { text: string; fontSize: string; lineHeight: string; color: string } | null = null + try { + const stack = document.elementsFromPoint(cx, cy) + for (const el of stack) { + const tag = el.tagName.toLowerCase() + if (tag === 'script' || tag === 'style' || tag === 'svg' || tag === 'path' || tag === 'br' || tag === 'template' || tag === 'iframe') continue + if (el === document.body || el === document.documentElement) continue + const rect = el.getBoundingClientRect() + if (rect.width <= 0 || rect.height <= 0) continue + if (rect.right < L || rect.left > R || rect.bottom < T || rect.top > B) continue + const text = (el.textContent || '').trim().replace(/\s+/g, ' ') + if (!text) continue + const cs = getComputedStyle(el) + primary = { text: text.slice(0, 60), fontSize: cs.fontSize, lineHeight: cs.lineHeight, color: cs.color } + break + } + } catch {} + const seen = new Set() + if (primary) seen.add(primary.text) + const candidates: string[] = [] for (const el of document.querySelectorAll('*')) { const tag = el.tagName.toLowerCase() if (tag === 'script' || tag === 'style' || tag === 'svg' || tag === 'path' || tag === 'br' || tag === 'template' || tag === 'iframe') continue const rect = el.getBoundingClientRect() if (rect.width <= 0 || rect.height <= 0) continue - // 相交即入:元素矩形与框有任意重叠就收录(小框框住一半文字也能抓到) if (rect.right < L || rect.left > R || rect.bottom < T || rect.top > B) continue const text = (el.textContent || '').trim().replace(/\s+/g, ' ') - if (text.length < 1 || text.length > 80) continue - out.push({ text, area: rect.width * rect.height }) + if (text.length < 1 || text.length > 30) continue + if (seen.has(text)) continue + seen.add(text) + candidates.push(text) + if (candidates.length >= 4) break } - out.sort((a, b) => a.area - b.area) - const seen = new Set() - const res: string[] = [] - for (const o of out) { - if (seen.has(o.text)) continue - seen.add(o.text) - res.push(o.text) - if (res.length >= 5) break - } - return res + return { primary, candidates } } /** 注入宿主对话框输入框(不发送);失败返回 false */ @@ -493,8 +510,9 @@ function AnnotationOverlay() { const ry2 = (((B - pr.top) / Math.max(1, pr.height)) * 100).toFixed(1) stat += ',窗口内 (' + rx1 + '%,' + ry1 + '%)→(' + rx2 + '%,' + ry2 + '%)' } - const texts = elementsInBox(L, T, R, B) - if (texts.length > 0) stat += ',框内文字:' + texts.map((t, i) => '[' + (i + 1) + ']' + t + (i === 0 ? '←最可能目标' : '')).join(' ') + const hit = boxPayload(L, T, R, B) + if (hit.primary) stat += ',主目标:' + hit.primary.text + '(字号 ' + hit.primary.fontSize + ',行高 ' + hit.primary.lineHeight + ')' + if (hit.candidates.length > 0) stat += ',候选:' + hit.candidates.map((t, i) => '[' + (i + 1) + ']' + t).join(' ') } else { stat = '屏幕坐标 (' + Math.round(e.clientX) + ', ' + Math.round(e.clientY) + ')' if (paneEl) {