chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,170 @@
|
||||
import './wb-legacy-disabled.mjs'
|
||||
|
||||
/**
|
||||
* 采集首页的垂直结构(各区块 y 坐标与尺寸)。
|
||||
*
|
||||
* 用法:node scripts/wb-home-layout.mjs <port> [outJson]
|
||||
*/
|
||||
|
||||
import { writeFile } from 'node:fs/promises'
|
||||
|
||||
const port = Number(process.argv[2] ?? 9223)
|
||||
const outJson = process.argv[3] ?? null
|
||||
|
||||
async function main() {
|
||||
const list = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json()
|
||||
const page = list.filter((t) => t.type === 'page')[0]
|
||||
if (!page) throw new Error('未找到页面')
|
||||
|
||||
const ws = new WebSocket(page.webSocketDebuggerUrl)
|
||||
await new Promise((r, j) => {
|
||||
const t = setTimeout(() => j(new Error('连接超时')), 8000)
|
||||
ws.addEventListener('open', () => {
|
||||
clearTimeout(t)
|
||||
r()
|
||||
})
|
||||
ws.addEventListener('error', () => {
|
||||
clearTimeout(t)
|
||||
j(new Error('连接失败'))
|
||||
})
|
||||
})
|
||||
|
||||
const send = (method, params = {}) =>
|
||||
new Promise((res, rej) => {
|
||||
const id = Math.floor(Math.random() * 1e9)
|
||||
const timer = setTimeout(() => rej(new Error(`超时 ${method}`)), 25000)
|
||||
const h = (ev) => {
|
||||
const m = JSON.parse(ev.data)
|
||||
if (m.id === id) {
|
||||
clearTimeout(timer)
|
||||
ws.removeEventListener('message', h)
|
||||
res(m.result)
|
||||
}
|
||||
}
|
||||
ws.addEventListener('message', h)
|
||||
ws.send(JSON.stringify({ id, method, params }))
|
||||
})
|
||||
|
||||
// 逐类名精确取矩形;找不到就跳过
|
||||
const expr = `(() => {
|
||||
const SEL = [
|
||||
'.workbuddy-topbar',
|
||||
'.conversation-sidebar',
|
||||
'[class*="welcome"]',
|
||||
'[class*="greeting"]',
|
||||
'.wb-segmented',
|
||||
'.quick-actions',
|
||||
'.quick-actions__list',
|
||||
'[class*="input-box"]',
|
||||
'[class*="cr-input"]',
|
||||
'.cr-input-container',
|
||||
'.cr-input-editor-host',
|
||||
'[class*="permission-setting"]',
|
||||
'.cr-input-footer',
|
||||
'[class*="suggestion"]',
|
||||
'[class*="quick-prompt"]',
|
||||
'img[class*="mascot"]',
|
||||
'[class*="mascot"]',
|
||||
'[class*="empty"]',
|
||||
'[class*="scene"]'
|
||||
];
|
||||
const clean = (v) => (v == null ? '' : String(v).replace(/\\s+/g, ' ').trim());
|
||||
const out = [];
|
||||
for (const sel of SEL) {
|
||||
let nodes = [];
|
||||
try { nodes = [...document.querySelectorAll(sel)]; } catch { continue; }
|
||||
if (!nodes.length) continue;
|
||||
out.push({
|
||||
sel,
|
||||
count: nodes.length,
|
||||
items: nodes.slice(0, 6).map((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
const cs = getComputedStyle(el);
|
||||
return {
|
||||
cls: clean(el.className).slice(0, 90),
|
||||
rect: [Math.round(r.x), Math.round(r.y), Math.round(r.width), Math.round(r.height)],
|
||||
display: cs.display,
|
||||
flexDirection: cs.flexDirection,
|
||||
alignItems: cs.alignItems,
|
||||
justifyContent: cs.justifyContent,
|
||||
gap: cs.gap,
|
||||
padding: cs.padding,
|
||||
margin: cs.margin,
|
||||
radius: cs.borderRadius,
|
||||
bg: cs.backgroundColor,
|
||||
shadow: cs.boxShadow === 'none' ? '' : cs.boxShadow,
|
||||
fontSize: cs.fontSize,
|
||||
fontWeight: cs.fontWeight
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 主内容容器的直接子元素序列(看垂直堆叠顺序)
|
||||
const side = document.querySelector('.conversation-sidebar');
|
||||
const sideRight = side ? side.getBoundingClientRect().right : 240;
|
||||
let best = null, bestArea = 0;
|
||||
document.querySelectorAll('div').forEach((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.x >= sideRight - 4 && r.width > 500 && r.height > 300) {
|
||||
const a = r.width * r.height;
|
||||
if (a > bestArea) { bestArea = a; best = el; }
|
||||
}
|
||||
});
|
||||
let stack = [];
|
||||
if (best) {
|
||||
const walk = (el, depth, path) => {
|
||||
if (depth > 4 || stack.length > 60) return;
|
||||
[...el.children].forEach((c, i) => {
|
||||
const r = c.getBoundingClientRect();
|
||||
if (r.width < 60 || r.height < 8) return;
|
||||
stack.push({
|
||||
depth,
|
||||
path: path + '/' + i,
|
||||
tag: c.tagName.toLowerCase(),
|
||||
cls: clean(c.className).slice(0, 70),
|
||||
rect: [Math.round(r.x), Math.round(r.y), Math.round(r.width), Math.round(r.height)],
|
||||
text: clean(c.textContent).slice(0, 40)
|
||||
});
|
||||
walk(c, depth + 1, path + '/' + i);
|
||||
});
|
||||
};
|
||||
walk(best, 0, '');
|
||||
}
|
||||
|
||||
return JSON.stringify({ viewport: { w: innerWidth, h: innerHeight }, out, stack });
|
||||
})()`
|
||||
|
||||
const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true })
|
||||
const data = JSON.parse(r.result.value)
|
||||
|
||||
console.log(`viewport ${data.viewport.w}x${data.viewport.h}\n`)
|
||||
for (const entry of data.out) {
|
||||
console.log(`### ${entry.sel} (${entry.count})`)
|
||||
for (const it of entry.items) {
|
||||
console.log(` .${it.cls}`)
|
||||
console.log(` rect ${JSON.stringify(it.rect)} display=${it.display}/${it.flexDirection} align=${it.alignItems} justify=${it.justifyContent} gap=${it.gap}`)
|
||||
console.log(` pad=${it.padding} margin=${it.margin}`)
|
||||
console.log(` radius=${it.radius} bg=${it.bg} fs=${it.fontSize}/${it.fontWeight}`)
|
||||
if (it.shadow) console.log(` shadow=${it.shadow}`)
|
||||
}
|
||||
console.log('')
|
||||
}
|
||||
|
||||
console.log('### 垂直堆叠(y 升序)')
|
||||
const sorted = [...data.stack].sort((a, b) => a.rect[1] - b.rect[1])
|
||||
for (const s of sorted) {
|
||||
console.log(` y=${String(s.rect[1]).padStart(4)} h=${String(s.rect[3]).padStart(4)} x=${String(s.rect[0]).padStart(4)} w=${String(s.rect[2]).padStart(4)} ${' '.repeat(s.depth)}${s.tag}.${s.cls.split(/\s+/)[0]} "${s.text}"`)
|
||||
}
|
||||
|
||||
if (outJson) {
|
||||
await writeFile(outJson, JSON.stringify(data, null, 2), 'utf8')
|
||||
console.log(`\n已写出 ${outJson}`)
|
||||
}
|
||||
ws.close()
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error('失败:', e.message)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in new issue
Block a user