feat(extension): redesign welcome page with Prism.js syntax highlighting
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --project . && vite build --config vite.config.mts",
|
"build": "tsc --project . && vite build --config vite.config.mts && tsx scripts/download-prism.ts",
|
||||||
"reload": "bun run build && osascript -e 'tell application \"Google Chrome\" to open location \"chrome://extensions/?id=pebbngnfojnignonigcnkdilknapkgid\"'",
|
"reload": "bun run build && osascript -e 'tell application \"Google Chrome\" to open location \"chrome://extensions/?id=pebbngnfojnignonigcnkdilknapkgid\"'",
|
||||||
"watch": "vite build --watch --config vite.config.mts",
|
"watch": "vite build --watch --config vite.config.mts",
|
||||||
"clean": "rm -rf dist"
|
"clean": "rm -rf dist"
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Downloads Prism.js assets into dist/src/ for the welcome page.
|
||||||
|
// Chrome extension CSP blocks external scripts, so we bundle them locally.
|
||||||
|
import https from 'node:https'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const BASE = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/'
|
||||||
|
const DEST = path.join('dist', 'src')
|
||||||
|
|
||||||
|
const files: [string, string][] = [
|
||||||
|
['prism.min.js', 'prism.min.js'],
|
||||||
|
['components/prism-bash.min.js', 'prism-bash.min.js'],
|
||||||
|
]
|
||||||
|
|
||||||
|
function download(url: string, dest: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(url, (res) => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
reject(new Error(`Failed to download ${url}: ${res.statusCode}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const chunks: Buffer[] = []
|
||||||
|
res.on('data', (chunk: Buffer) => { chunks.push(chunk) })
|
||||||
|
res.on('end', () => {
|
||||||
|
fs.writeFileSync(dest, Buffer.concat(chunks))
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
res.on('error', reject)
|
||||||
|
}).on('error', reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await Promise.all(
|
||||||
|
files.map(([src, dest]) => {
|
||||||
|
return download(BASE + src, path.join(DEST, dest))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
console.log(`Downloaded ${files.length} Prism.js files to ${DEST}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
+64
-30
@@ -10,7 +10,7 @@
|
|||||||
--color-text: #262626;
|
--color-text: #262626;
|
||||||
--color-heading: #171717;
|
--color-heading: #171717;
|
||||||
--color-border: #d4d4d4;
|
--color-border: #d4d4d4;
|
||||||
--color-code-bg: #f5f5f5;
|
--color-code-bg: transparent;
|
||||||
--color-link: #0969da;
|
--color-link: #0969da;
|
||||||
--color-link-hover: #0550ae;
|
--color-link-hover: #0550ae;
|
||||||
--color-success: #16a34a;
|
--color-success: #16a34a;
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
--color-text: #e5e5e5;
|
--color-text: #e5e5e5;
|
||||||
--color-heading: #fafafa;
|
--color-heading: #fafafa;
|
||||||
--color-border: #404040;
|
--color-border: #404040;
|
||||||
--color-code-bg: #171717;
|
--color-code-bg: transparent;
|
||||||
--color-link: #4493f8;
|
--color-link: #4493f8;
|
||||||
--color-link-hover: #539bf5;
|
--color-link-hover: #539bf5;
|
||||||
--color-success: #22c55e;
|
--color-success: #22c55e;
|
||||||
@@ -90,24 +90,22 @@
|
|||||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
|
||||||
"Liberation Mono", monospace;
|
"Liberation Mono", monospace;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
background-color: var(--color-code-bg);
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background-color: var(--color-code-bg);
|
background: transparent !important;
|
||||||
padding: 1rem;
|
border: none !important;
|
||||||
border-radius: 6px;
|
padding: 0 !important;
|
||||||
overflow-x: auto;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border: 1px solid var(--color-border);
|
overflow: visible;
|
||||||
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code {
|
pre code {
|
||||||
background-color: transparent;
|
background: transparent !important;
|
||||||
padding: 0;
|
padding: 0 !important;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol,
|
ol,
|
||||||
@@ -125,8 +123,6 @@
|
|||||||
color: var(--color-heading);
|
color: var(--color-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.step-number {
|
.step-number {
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -144,6 +140,7 @@
|
|||||||
|
|
||||||
.step-content {
|
.step-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
@@ -154,6 +151,41 @@
|
|||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tomorrow (light) token colors */
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #8959a8;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.token.comment, .token.block-comment, .token.prolog, .token.doctype, .token.cdata { color: #8e908c; }
|
||||||
|
.token.punctuation { color: #8959a8; }
|
||||||
|
.token.tag, .token.attr-name, .token.namespace, .token.deleted { color: #c82829; }
|
||||||
|
.token.function-name { color: #4271ae; }
|
||||||
|
.token.boolean, .token.number, .token.function { color: #f5871f; }
|
||||||
|
.token.property, .token.class-name, .token.constant, .token.symbol { color: #eab700; }
|
||||||
|
.token.selector, .token.important, .token.atrule, .token.keyword, .token.builtin { color: #8959a8; }
|
||||||
|
.token.string, .token.char, .token.attr-value, .token.regex, .token.variable { color: #718c00; }
|
||||||
|
.token.operator, .token.entity, .token.url { color: #3e999f; }
|
||||||
|
.token.inserted { color: #718c00; }
|
||||||
|
|
||||||
|
/* Tomorrow Night (dark) token colors */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #b294bb;
|
||||||
|
}
|
||||||
|
.token.comment, .token.block-comment, .token.prolog, .token.doctype, .token.cdata { color: #969896; }
|
||||||
|
.token.punctuation { color: #b294bb; }
|
||||||
|
.token.tag, .token.attr-name, .token.namespace, .token.deleted { color: #cc6666; }
|
||||||
|
.token.function-name { color: #81a2be; }
|
||||||
|
.token.boolean, .token.number, .token.function { color: #de935f; }
|
||||||
|
.token.property, .token.class-name, .token.constant, .token.symbol { color: #f0c674; }
|
||||||
|
.token.selector, .token.important, .token.atrule, .token.keyword, .token.builtin { color: #b294bb; }
|
||||||
|
.token.string, .token.char, .token.attr-value, .token.regex, .token.variable { color: #b5bd68; }
|
||||||
|
.token.operator, .token.entity, .token.url { color: #8abeb7; }
|
||||||
|
.token.inserted { color: #b5bd68; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -171,18 +203,10 @@
|
|||||||
<div class="step">
|
<div class="step">
|
||||||
<span class="step-number">1</span>
|
<span class="step-number">1</span>
|
||||||
<div class="step-content">
|
<div class="step-content">
|
||||||
<strong>Install the extension</strong>
|
<strong>Pin the extension</strong>
|
||||||
<p>
|
<p>
|
||||||
Add the Playwriter extension from the Chrome Web Store.
|
Click the puzzle icon in Chrome's toolbar, then pin Playwriter so
|
||||||
</p>
|
it's always visible.
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
href="https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Open Chrome Web Store
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +228,9 @@
|
|||||||
<p>
|
<p>
|
||||||
Install the CLI and start automating the browser:
|
Install the CLI and start automating the browser:
|
||||||
</p>
|
</p>
|
||||||
<pre><code>npm i -g playwriter
|
<pre><code class="language-bash"># install the CLI globally
|
||||||
|
npm i -g playwriter
|
||||||
|
# navigate to a URL in the active tab
|
||||||
playwriter -s 1 -e "await page.goto('https://example.com')"</code></pre>
|
playwriter -s 1 -e "await page.goto('https://example.com')"</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,15 +242,20 @@ playwriter -s 1 -e "await page.goto('https://example.com')"</code></pre>
|
|||||||
<p>
|
<p>
|
||||||
Install the Playwriter skill so your agent can call the CLI:
|
Install the Playwriter skill so your agent can call the CLI:
|
||||||
</p>
|
</p>
|
||||||
<pre><code>npx -y skills add remorses/playwriter</code></pre>
|
<pre><code class="language-bash"># add playwriter skill to your coding agent
|
||||||
|
npx -y skills add remorses/playwriter</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Quick Start (CLI)</h3>
|
<h3>CLI Examples</h3>
|
||||||
|
|
||||||
<pre><code>playwriter session new # creates stateful sandbox, outputs session id (e.g. 1)
|
<pre><code class="language-bash"># create a stateful sandbox, outputs session id (e.g. 1)
|
||||||
|
playwriter session new
|
||||||
|
# navigate to a URL
|
||||||
playwriter -s 1 -e "await page.goto('https://example.com')"
|
playwriter -s 1 -e "await page.goto('https://example.com')"
|
||||||
|
# get the accessibility tree of the page
|
||||||
playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page }))"
|
playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page }))"
|
||||||
|
# click an element by its accessibility reference
|
||||||
playwriter -s 1 -e "await page.locator('aria-ref=e5').click()"</code></pre>
|
playwriter -s 1 -e "await page.locator('aria-ref=e5').click()"</code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -280,7 +311,8 @@ playwriter -s 1 -e "await page.locator('aria-ref=e5').click()"</code></pre>
|
|||||||
server setup, auto-configure it with:
|
server setup, auto-configure it with:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre><code>npx -y @playwriter/install-mcp playwriter@latest</code></pre>
|
<pre><code class="language-bash"># auto-configure MCP server for your editor
|
||||||
|
npx -y @playwriter/install-mcp playwriter@latest</code></pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
For full MCP instructions, see
|
For full MCP instructions, see
|
||||||
@@ -311,5 +343,7 @@ playwriter -s 1 -e "await page.locator('aria-ref=e5').click()"</code></pre>
|
|||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
|
<script src="prism.min.js"></script>
|
||||||
|
<script src="prism-bash.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default defineConfig({
|
|||||||
src: resolve(__dirname, 'icons/*'),
|
src: resolve(__dirname, 'icons/*'),
|
||||||
dest: 'icons'
|
dest: 'icons'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
src: resolve(__dirname, 'manifest.json'),
|
src: resolve(__dirname, 'manifest.json'),
|
||||||
dest: '.',
|
dest: '.',
|
||||||
|
|||||||
@@ -141,8 +141,7 @@ cli
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${serverUrl}/cli/session/suggest`)
|
const res = await fetch(`${serverUrl}/cli/session/suggest`)
|
||||||
const { next } = await res.json() as { next: number }
|
const { next } = await res.json() as { next: number }
|
||||||
console.log(`Session ${next} created.`)
|
console.log(`Session ${next} created. Use with: playwriter -s ${next} -e "..."`)
|
||||||
console.log(`Use it with: playwriter -s ${next} -e "await page.goto('https://example.com')"`)
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error: ${error.message}`)
|
console.error(`Error: ${error.message}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user