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,153 @@
|
||||
import './wb-legacy-disabled.mjs'
|
||||
|
||||
/**
|
||||
* 导出真实 WorkBuddy 某个子树的结构与样式(CDP)。
|
||||
*
|
||||
* 用法:node scripts/wb-tree.mjs <port> <rootSelector> [maxNodes]
|
||||
* 例: node scripts/wb-tree.mjs 9223 .conversation-sidebar 200
|
||||
*/
|
||||
|
||||
import { writeFile, mkdir } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const port = Number(process.argv[2] ?? 9223)
|
||||
const rootSelector = process.argv[3] ?? '.conversation-sidebar'
|
||||
const maxNodes = Number(process.argv[4] ?? 200)
|
||||
const outDir = process.argv[5] ?? null
|
||||
|
||||
const PROPS = [
|
||||
'display',
|
||||
'flex-direction',
|
||||
'align-items',
|
||||
'justify-content',
|
||||
'width',
|
||||
'min-width',
|
||||
'max-width',
|
||||
'height',
|
||||
'min-height',
|
||||
'padding-top',
|
||||
'padding-right',
|
||||
'padding-bottom',
|
||||
'padding-left',
|
||||
'margin-top',
|
||||
'margin-bottom',
|
||||
'gap',
|
||||
'background-color',
|
||||
'color',
|
||||
'border-top-width',
|
||||
'border-top-color',
|
||||
'border-radius',
|
||||
'box-shadow',
|
||||
'font-size',
|
||||
'font-weight',
|
||||
'line-height',
|
||||
'letter-spacing',
|
||||
'text-transform',
|
||||
'opacity',
|
||||
'overflow-y',
|
||||
'position'
|
||||
]
|
||||
|
||||
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' && !/windowKind=/.test(t.url))[0]
|
||||
if (!page) {
|
||||
console.error('未找到主页面目标')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const ws = new WebSocket(page.webSocketDebuggerUrl)
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('连接超时')), 10000)
|
||||
ws.addEventListener('open', () => {
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
})
|
||||
ws.addEventListener('error', () => {
|
||||
clearTimeout(timer)
|
||||
reject(new Error('连接失败'))
|
||||
})
|
||||
})
|
||||
|
||||
const send = (method, params = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const id = Math.floor(Math.random() * 1e9)
|
||||
const timer = setTimeout(() => reject(new Error(`超时 ${method}`)), 20000)
|
||||
const handler = (ev) => {
|
||||
const msg = JSON.parse(ev.data)
|
||||
if (msg.id === id) {
|
||||
clearTimeout(timer)
|
||||
ws.removeEventListener('message', handler)
|
||||
resolve(msg.result)
|
||||
}
|
||||
}
|
||||
ws.addEventListener('message', handler)
|
||||
ws.send(JSON.stringify({ id, method, params }))
|
||||
})
|
||||
|
||||
const expression = `(() => {
|
||||
const ROOT = ${JSON.stringify(rootSelector)};
|
||||
const PROPS = ${JSON.stringify(PROPS)};
|
||||
const MAX = ${maxNodes};
|
||||
const clean = (v) => (v == null ? '' : String(v).replace(/\\s+/g, ' ').trim());
|
||||
const root = document.querySelector(ROOT);
|
||||
if (!root) return JSON.stringify({ error: 'selector not found: ' + ROOT });
|
||||
const rows = [];
|
||||
const walk = (el, depth) => {
|
||||
if (rows.length >= MAX) return;
|
||||
const cs = getComputedStyle(el);
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) return;
|
||||
const st = {};
|
||||
for (const p of PROPS) {
|
||||
const v = clean(cs.getPropertyValue(p));
|
||||
if (v && v !== 'normal' && v !== 'auto' && v !== 'none' && v !== '0px' && v !== 'rgba(0, 0, 0, 0)') st[p] = v;
|
||||
}
|
||||
rows.push({
|
||||
depth,
|
||||
tag: el.tagName.toLowerCase(),
|
||||
cls: clean(el.className).slice(0, 120),
|
||||
text: clean(el.childElementCount === 0 ? el.textContent : '').slice(0, 70),
|
||||
rect: { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) },
|
||||
styles: st
|
||||
});
|
||||
for (const c of el.children) walk(c, depth + 1);
|
||||
};
|
||||
walk(root, 0);
|
||||
return JSON.stringify({ selector: ROOT, viewport: { w: innerWidth, h: innerHeight }, rows });
|
||||
})()`
|
||||
|
||||
const res = await send('Runtime.evaluate', { expression, returnByValue: true })
|
||||
const data = JSON.parse(res.result.value)
|
||||
if (data.error) {
|
||||
console.error(data.error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`root=${data.selector} viewport=${data.viewport.w}x${data.viewport.h} nodes=${data.rows.length}`)
|
||||
const lines = []
|
||||
for (const row of data.rows) {
|
||||
const indent = ' '.repeat(row.depth)
|
||||
const cls = row.cls ? '.' + row.cls.split(/\s+/).slice(0, 2).join('.') : ''
|
||||
const txt = row.text ? ` "${row.text}"` : ''
|
||||
lines.push(`${indent}${row.tag}${cls} [${row.rect.x},${row.rect.y} ${row.rect.w}x${row.rect.h}]${txt}`)
|
||||
const keys = Object.keys(row.styles)
|
||||
if (keys.length) {
|
||||
lines.push(`${indent} ${JSON.stringify(row.styles)}`)
|
||||
}
|
||||
}
|
||||
const text = lines.join('\n')
|
||||
console.log(text.slice(0, 12000))
|
||||
|
||||
if (outDir) {
|
||||
await mkdir(outDir, { recursive: true })
|
||||
await writeFile(join(outDir, 'tree.json'), JSON.stringify(data, null, 2), 'utf8')
|
||||
console.log(`\n已写出 ${join(outDir, 'tree.json')}`)
|
||||
}
|
||||
ws.close()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('失败:', err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in new issue
Block a user