From 4f9d8b7163a15ac6d49983645ff3c3da0d5cc05f Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Mon, 22 Jun 2026 20:15:44 +0800 Subject: [PATCH] chore: drop one-off playground capture script Co-authored-by: Cursor --- bin/capture-readme-playground.mjs | 327 ------------------------------ package.json | 3 +- 2 files changed, 1 insertion(+), 329 deletions(-) delete mode 100644 bin/capture-readme-playground.mjs diff --git a/bin/capture-readme-playground.mjs b/bin/capture-readme-playground.mjs deleted file mode 100644 index 4194e89eb..000000000 --- a/bin/capture-readme-playground.mjs +++ /dev/null @@ -1,327 +0,0 @@ -/* global liquidjs */ -import { spawn, execFile } from 'node:child_process' -import { access, copyFile, mkdir, rm } from 'node:fs/promises' -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' -import { promisify } from 'node:util' -import { chromium } from 'playwright' - -const execFileAsync = promisify(execFile) -const root = join(dirname(fileURLToPath(import.meta.url)), '..') -const docsDir = join(root, 'docs') -const framesDir = join(root, '.local/playground-gif-frames') -const outPath = join(docsDir, 'source/playground-demo.gif') -const url = 'http://127.0.0.1:4001/playground.html' - -const CAPTURE_WIDTH = 980 -const VIEWPORT_WIDTH = 1100 -const VIEWPORT_HEIGHT = 1000 -const DEVICE_SCALE = 2 -const FPS = 12 -const LAST_FRAME_HOLD = 2.5 -const CHAR_MS = 55 - -const LINE_HEIGHT = 21 -const WINDOW_CHROME = 36 -const GRID_GAP = 16 -const EDITOR_PAD = 16 - -function run (cmd, args, opts = {}) { - return new Promise((resolve, reject) => { - const child = spawn(cmd, args, { stdio: 'inherit', shell: true, ...opts }) - child.on('error', reject) - child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`${cmd} exited ${code}`)))) - }) -} - -async function waitForServer (maxMs = 120000) { - const start = Date.now() - while (Date.now() - start < maxMs) { - try { - const res = await fetch(url) - if (res.ok) return - } catch {} - await new Promise((resolve) => setTimeout(resolve, 500)) - } - throw new Error(`Timed out waiting for ${url}`) -} - -async function ensureDocsDeps () { - try { - await access(join(docsDir, 'node_modules/hexo/package.json')) - } catch { - await run('npm', ['ci'], { cwd: docsDir }) - } -} - -async function prepareCapture (page, { fullTemplate, fullContext }) { - await page.addStyleTag({ - content: ` - #editors .capture-pane { - margin: 0; - box-sizing: border-box; - font: 14px/1.5 "Source Code Pro", ui-monospace, Monaco, Menlo, Consolas, monospace; - white-space: pre; - overflow: hidden; - color: #24292f; - background: #fff; - } - #editors .capture-cursor { - color: #24292f; - } - #editors .capture-json .json-key { color: #953800; } - #editors .capture-json .json-string { color: #cf222e; } - ` - }) - - await page.evaluate((opts) => { - const { - captureWidth, - fullTemplate, - fullContext, - lineHeight, - windowChrome, - gridGap, - editorPad - } = opts - - function paneHeight (lines) { - return windowChrome + editorPad * 2 + lines * lineHeight + 4 - } - - function escapeHtml (str) { - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - } - - function highlightJson (text) { - return escapeHtml(text) - .replace(/"([^"]+)":/g, '"$1":') - .replace(/: "([^"]*)"/g, ': "$1"') - } - - function mountPane (shell, className, html) { - shell.innerHTML = '' - shell.style.paddingTop = '0' - shell.style.overflow = 'hidden' - const pre = document.createElement('pre') - pre.className = 'capture-pane ' + className - pre.style.height = '100%' - pre.style.padding = `${editorPad}px` - if (html) pre.innerHTML = html - shell.appendChild(pre) - return pre - } - - document.querySelector('#playground .version')?.remove() - document.querySelector('#playground h1')?.remove() - document.querySelector('.loader')?.remove() - document.querySelector('#playground > .wrapper')?.style.setProperty('margin-bottom', '0') - document.body.style.background = '#fff' - document.querySelector('#playground').style.background = '#fff' - - window.__captureEngine = new liquidjs.Liquid({ memoryLimit: 1e5, renderLimit: 1e5 }) - window.__captureContext = JSON.parse(fullContext) - window.__captureLastHtml = '' - - const templateLineCount = fullTemplate.split('\n').length - const contextLineCount = fullContext.split('\n').length - - const templateEditorH = paneHeight(templateLineCount) - const contextEditorH = paneHeight(contextLineCount) - - const leftRow1 = templateEditorH - const leftRow2 = contextEditorH - const rightCol = leftRow1 + gridGap + leftRow2 - const gridH = rightCol - - const editors = document.querySelector('#editors') - editors.classList.remove('inner') - editors.style.margin = '0 auto' - editors.style.width = captureWidth + 'px' - editors.style.maxWidth = captureWidth + 'px' - editors.style.height = gridH + 'px' - editors.style.minHeight = gridH + 'px' - editors.style.overflow = 'visible' - editors.style.display = 'grid' - editors.style.gridTemplateColumns = '1fr 1fr' - editors.style.gridTemplateRows = `${leftRow1}px ${leftRow2}px` - editors.style.gridGap = gridGap + 'px' - editors.style.alignItems = 'start' - - document.querySelectorAll('.editor-wrapper').forEach((wrapper) => { - wrapper.style.overflow = 'visible' - wrapper.style.minHeight = '0' - }) - - document.querySelector('.area-output').style.gridRow = '1 / -1' - document.querySelector('.area-output').style.gridColumn = '2' - - const tplShell = document.querySelector('#editorEl') - const ctxShell = document.querySelector('#dataEl') - const tplPane = tplShell.closest('.pane-window') - const ctxPane = ctxShell.closest('.pane-window') - tplShell.closest('.editor-wrapper').style.height = templateEditorH + 'px' - ctxShell.closest('.editor-wrapper').style.height = contextEditorH + 'px' - tplPane.style.height = templateEditorH + 'px' - ctxPane.style.height = contextEditorH + 'px' - tplShell.style.height = (templateEditorH - windowChrome) + 'px' - ctxShell.style.height = (contextEditorH - windowChrome) + 'px' - - window.__captureTpl = mountPane(tplShell, 'capture-liquid', '') - window.__captureCtx = mountPane(ctxShell, 'capture-json', highlightJson(fullContext)) - - const code = document.querySelector('#previewCode') - code.textContent = '' - if (window.Prism) { - delete code.dataset.highlighted - window.Prism.highlightElement(code) - } - - const outputPane = document.querySelector('.area-output .pane-window') - const outputShell = document.querySelector('.output-preview') - const outputWrap = document.querySelector('.area-output') - outputWrap.style.height = rightCol + 'px' - outputPane.style.height = rightCol + 'px' - outputShell.style.height = (rightCol - windowChrome) + 'px' - outputShell.style.flex = 'none' - outputShell.style.overflow = 'hidden' - outputShell.style.paddingTop = '0' - const pre = outputShell.querySelector('pre.highlight') - pre.style.minHeight = '0' - pre.style.height = '100%' - pre.style.padding = editorPad + 'px' - pre.style.margin = '0' - pre.style.boxSizing = 'border-box' - pre.style.border = 'none' - pre.style.boxShadow = 'none' - }, { - captureWidth: CAPTURE_WIDTH, - fullTemplate, - fullContext, - lineHeight: LINE_HEIGHT, - windowChrome: WINDOW_CHROME, - gridGap: GRID_GAP, - editorPad: EDITOR_PAD - }) - - await page.waitForTimeout(200) -} - -async function setCaptureFrame (page, templateText) { - await page.evaluate(async (tpl) => { - function escapeHtml (str) { - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - } - - window.__captureTpl.innerHTML = - escapeHtml(tpl) + '' - - let html = window.__captureLastHtml || '' - try { - html = await window.__captureEngine.parseAndRender(tpl, window.__captureContext) - window.__captureLastHtml = html - } catch {} - - const code = document.querySelector('#previewCode') - code.textContent = html - if (window.Prism) { - delete code.dataset.highlighted - window.Prism.highlightElement(code) - } - }, templateText) -} - -async function screenshotEditors (page, file) { - await page.locator('#editors').screenshot({ - path: file, - animations: 'disabled', - type: 'png', - scale: 'device' - }) -} - -async function encodeGif () { - await execFileAsync('ffmpeg', [ - '-y', - '-framerate', String(FPS), - '-i', join(framesDir, 'frame-%03d.png'), - '-vf', `tpad=stop_mode=clone:stop_duration=${LAST_FRAME_HOLD},split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=full[p];[s1][p]paletteuse=dither=floyd_steinberg`, - '-loop', '0', - outPath - ], { stdio: 'inherit' }) -} - -async function main () { - await run('npm', ['run', 'build:docs-liquid'], { cwd: root }) - await run('npm', ['run', 'build:contributors'], { cwd: root }) - await run('npm', ['run', 'build:used-by'], { cwd: root }) - await ensureDocsDeps() - await rm(framesDir, { recursive: true, force: true }) - await mkdir(framesDir, { recursive: true }) - - const server = spawn('npx', ['hexo', 'server', '-p', '4001'], { - cwd: docsDir, - shell: true, - stdio: 'ignore' - }) - - try { - await waitForServer() - - const browser = await chromium.launch() - const page = await browser.newPage({ - viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }, - deviceScaleFactor: DEVICE_SCALE - }) - await page.emulateMedia({ colorScheme: 'light' }) - await page.goto(url, { waitUntil: 'networkidle' }) - - const allowCookies = page.getByRole('button', { name: 'Allow all cookies' }) - if (await allowCookies.count()) { - await allowCookies.click() - } - - await page.waitForSelector('#editors:not(.hide)', { timeout: 60000 }) - await page.waitForFunction(() => { - return window.ace?.edit?.('editorEl')?.getValue().includes('name | capitalize') - }) - - const fullTemplate = await page.evaluate(() => window.ace.edit('editorEl').getValue()) - const fullContext = await page.evaluate(() => window.ace.edit('dataEl').getValue()) - - await prepareCapture(page, { fullTemplate, fullContext }) - - const framePaths = [] - let frameIndex = 0 - - for (let i = 0; i <= fullTemplate.length; i++) { - const partial = fullTemplate.slice(0, i) - await setCaptureFrame(page, partial) - await page.waitForTimeout(CHAR_MS) - const file = join(framesDir, `frame-${String(frameIndex).padStart(3, '0')}.png`) - framePaths.push(file) - await screenshotEditors(page, file) - frameIndex++ - } - - await encodeGif() - await mkdir(join(root, '.local'), { recursive: true }) - await copyFile(framePaths[framePaths.length - 1], join(root, '.local/playground-gif-last-frame.png')) - await browser.close() - console.log(`Wrote ${outPath} (${framePaths.length} frames @ ${FPS} fps, char typing + live output)`) - } finally { - server.kill() - await rm(framesDir, { recursive: true, force: true }) - } -} - -main().catch((err) => { - console.error(err) - process.exit(1) -}) diff --git a/package.json b/package.json index 948309347..2e1251bca 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,7 @@ "build:changelog": "node bin/build-changelog.js", "build:docs-hexo": "cd docs && npm ci && npm run build && shx cp CNAME public/", "docs:dev": "run-s build:docs-liquid build:contributors build:used-by docs:serve", - "docs:serve": "cd docs && npm run start", - "capture:readme-playground": "node bin/capture-readme-playground.mjs" + "docs:serve": "cd docs && npm run start" }, "bin": { "liquidjs": "./bin/liquid.js",