mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 21:00:40 -07:00
docs(readme): playground GIF, used-by grid, and docs homepage sync
Add data/used-by.json with build:used-by for README and docs homepage, playground demo capture, and shared home-section layout. Used by lists products with site logos; Financial Support keeps org and individual sponsors. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -15,6 +15,7 @@ node_modules/
|
||||
docs/themes/navy/source/js/liquid.browser.min.js
|
||||
docs/themes/navy/layout/partial/all-contributors.swig
|
||||
docs/themes/navy/layout/partial/financial-contributors.swig
|
||||
docs/themes/navy/layout/partial/used-by.swig
|
||||
dist/
|
||||
demo/*/yarn.json
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# LiquidJS
|
||||
|
||||
> A simple, expressive and safe [Shopify Liquid][shopify/liquid] template engine for JavaScript — compatible with Jekyll, GitHub Pages, and Shopify themes.
|
||||
A simple, expressive [Shopify Liquid][shopify/liquid] template engine for JavaScript — compatible with Jekyll and GitHub Pages dialects, runs in Node.js, the browser (UMD/ESM), and the CLI via `npx liquidjs`, and ships with TypeScript definitions. Extend it with custom tags, filters, and [plugins][plugins]; optional `memoryLimit`, `renderLimit`, and `ownPropertyOnly` help harden untrusted templates (not a sandbox — see the [security model][security-model]).
|
||||
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
@@ -10,6 +10,12 @@
|
||||
|
||||
[Documentation][doc] · [Playground](https://liquidjs.com/playground.html) · [Setup guide][setup] · [Contributing][contribution]
|
||||
|
||||
<a href="https://liquidjs.com/playground.html">
|
||||
<img src="docs/source/playground-demo.gif" alt="LiquidJS playground: edit a template and context, see live HTML output" width="980" style="display: block; margin: 0 auto;" />
|
||||
</a>
|
||||
|
||||
<p align="center"><sub>Template, context, and live output in the <a href="https://liquidjs.com/playground.html">playground</a>.</sub></p>
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
@@ -23,14 +29,6 @@ const html = await engine.parseAndRender(
|
||||
//=> 'Hello, Liquid!'
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Compatible** — Shopify Liquid, Jekyll, and GitHub Pages dialects
|
||||
- **Safe by default** — `ownPropertyOnly`, `memoryLimit`, and `renderLimit` help sandbox untrusted templates
|
||||
- **Runs everywhere** — Node.js, browser (UMD/ESM), and CLI via `npx liquidjs`
|
||||
- **Extensible** — custom tags, filters, and [plugins][plugins]
|
||||
- **Typed** — TypeScript definitions included
|
||||
|
||||
## Installation
|
||||
|
||||
**Node.js**
|
||||
@@ -53,35 +51,28 @@ npx liquidjs --template 'Hello, {{ name }}!' --context '{"name": "Liquid"}'
|
||||
|
||||
See the [setup guide][setup] for partials, layouts, caching, and other options.
|
||||
|
||||
## Example
|
||||
|
||||
Liquid templates use tags (`{% %}`) and outputs (`{{ }}`):
|
||||
|
||||
```liquid
|
||||
{% if username %}
|
||||
{{ username | append: ", welcome to LiquidJS!" | capitalize }}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
Try it in the [playground](https://liquidjs.com/playground.html) or read the [Liquid syntax tutorial](https://liquidjs.com/tutorials/intro-to-liquid.html).
|
||||
|
||||
## Used by
|
||||
|
||||
- [Eleventy](https://www.11ty.dev/)
|
||||
- [GitHub Docs](https://github.com/github/docs)
|
||||
- [Kibana](https://github.com/elastic/kibana)
|
||||
- [Microsoft Power Pages](https://learn.microsoft.com/en-us/power-pages/introduction)
|
||||
- [Azure API Management developer portal](https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-developer-portal)
|
||||
- [Directus](https://docs.directus.io/)
|
||||
- [Builder.io](https://www.builder.io/m/developers)
|
||||
- [Mitosis](https://github.com/BuilderIO/mitosis)
|
||||
- [Pattern Lab](https://patternlab.io/)
|
||||
- [Opensense](https://www.opensense.com/)
|
||||
- [Rock RMS](https://www.rockrms.com/)
|
||||
- [WISMOlabs](https://wismolabs.com/)
|
||||
- [Freshet](https://chromewebstore.google.com/detail/freshet/mpclplhdencffbilobpcapccnihpelcg)
|
||||
<!-- generated by npm run build:used-by — edit data/used-by.json -->
|
||||
<!-- USED-BY-BEGIN -->
|
||||
<p align="center" style="line-height: 2.5;">
|
||||
<a href="https://www.11ty.dev/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwww.11ty.dev%2F&size=128" height="80" style="vertical-align: middle;" alt="Eleventy" title="Eleventy"/></a>
|
||||
<a href="https://docs.github.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fdocs.github.com%2F&size=128" height="80" style="vertical-align: middle;" alt="GitHub Docs" title="GitHub Docs"/></a>
|
||||
<a href="https://www.elastic.co/kibana" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwww.elastic.co%2Fkibana&size=128" height="80" style="vertical-align: middle;" alt="Kibana" title="Kibana"/></a>
|
||||
<a href="https://learn.microsoft.com/en-us/power-pages/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fpower-pages%2F&size=128" height="80" style="vertical-align: middle;" alt="Power Pages, Azure API Management developer portal" title="Power Pages, Azure API Management developer portal"/></a>
|
||||
<a href="https://www.shopify.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwww.shopify.com%2F&size=128" height="80" style="vertical-align: middle;" alt="Shopify CLI, Checkout Blocks" title="Shopify CLI, Checkout Blocks"/></a>
|
||||
<a href="https://directus.io/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fdirectus.io%2F&size=128" height="80" style="vertical-align: middle;" alt="Directus" title="Directus"/></a>
|
||||
<a href="https://www.builder.io/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwww.builder.io%2F&size=128" height="80" style="vertical-align: middle;" alt="Builder.io, Mitosis" title="Builder.io, Mitosis"/></a>
|
||||
<br/>
|
||||
<a href="https://patternlab.io/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fpatternlab.io%2F&size=128" height="80" style="vertical-align: middle;" alt="Pattern Lab" title="Pattern Lab"/></a>
|
||||
<a href="https://www.rockrms.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwww.rockrms.com%2F&size=128" height="80" style="vertical-align: middle;" alt="Rock RMS" title="Rock RMS"/></a>
|
||||
<a href="https://wismolabs.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwismolabs.com%2F&size=128" height="80" style="vertical-align: middle;" alt="WISMOlabs" title="WISMOlabs"/></a>
|
||||
<a href="https://www.dropkiq.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https%3A%2F%2Fwww.dropkiq.com%2F&size=128" height="80" style="vertical-align: middle;" alt="Dropkiq" title="Dropkiq"/></a>
|
||||
<a href="https://chromewebstore.google.com/detail/freshet/mpclplhdencffbilobpcapccnihpelcg" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://raw.githubusercontent.com/MattAltermatt/freshet/main/public/icon-128.png" height="80" style="vertical-align: middle;" alt="Freshet" title="Freshet"/></a>
|
||||
</p>
|
||||
<!-- USED-BY-END -->
|
||||
|
||||
Using LiquidJS in production? [Open a PR](https://github.com/harttle/liquidjs/edit/master/README.md) to add your project.
|
||||
Products and projects running on LiquidJS. [Open a PR](https://github.com/harttle/liquidjs/edit/master/data/used-by.json) to add yours.
|
||||
|
||||
## Financial Support
|
||||
|
||||
@@ -93,7 +84,7 @@ If you personally love LiquidJS or it's benefiting your business, please conside
|
||||
<a href="https://www.opensense.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/opensense-inc/bf840ae/logo/256.png?height=100" height="80" style="vertical-align: middle;" alt="Opensense Inc." title="Opensense"/></a>
|
||||
<a href="https://github.com/microsoft" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/6154722?v=4&s=100" height="80" style="vertical-align: middle;" alt="Microsoft" title="Microsoft"/></a>
|
||||
<a href="https://sentry.io/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/1396951?v=4&s=100" height="80" style="vertical-align: middle;" alt="Sentry" title="Sentry"/></a>
|
||||
<a href="https://www.checkoutblocks.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/114603307?v=4&s=100" height="80" style="vertical-align: middle;" alt="Checkout Blocks" title="Checkout Blocks"/></a>
|
||||
<a href="https://github.com/Checkout-Blocks" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/114603307?v=4&s=100" height="80" style="vertical-align: middle;" alt="Checkout Blocks" title="Checkout Blocks"/></a>
|
||||
<a href="https://customer.io/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/1152079?v=4&s=100" height="80" style="vertical-align: middle;" alt="Customer IO" title="Customer IO"/></a>
|
||||
<a href="https://syntax.fm/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/130389858?v=4&s=100" height="80" style="vertical-align: middle;" alt="Syntax Podcast" title="Syntax Podcast"/></a>
|
||||
<br/>
|
||||
@@ -260,3 +251,4 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
[github]: https://github.com/harttle/liquidjs
|
||||
[oc]: https://opencollective.com/liquidjs/
|
||||
[contribution]: https://liquidjs.com/tutorials/contribution-guidelines.html
|
||||
[security-model]: https://liquidjs.com/tutorials/security-model.html
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const root = path.resolve(__dirname, '..')
|
||||
const readmePath = path.join(root, 'README.md')
|
||||
const dataPath = path.join(root, 'data/used-by.json')
|
||||
const outDir = path.join(root, 'docs/themes/navy/layout/partial')
|
||||
|
||||
const LINK_STYLE = 'display: inline-block; vertical-align: middle; margin: 8px;'
|
||||
const IMG_STYLE = 'vertical-align: middle;'
|
||||
const PER_ROW = 7
|
||||
|
||||
function escapeHtml (str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
function websiteFavicon (url) {
|
||||
return 'https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url='
|
||||
+ encodeURIComponent(url) + '&size=128'
|
||||
}
|
||||
|
||||
async function githubAvatar (url) {
|
||||
try {
|
||||
const { hostname, pathname } = new URL(url)
|
||||
if (hostname !== 'github.com') return
|
||||
const owner = pathname.split('/').filter(Boolean)[0]
|
||||
if (!owner) return
|
||||
const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
|
||||
headers: { 'User-Agent': 'liquidjs-build-used-by' }
|
||||
})
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
if (!data.avatar_url) return
|
||||
const base = data.avatar_url.split('?')[0]
|
||||
return `${base}?s=128`
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function resolveLogo (entry) {
|
||||
if (entry.logo) return entry.logo
|
||||
const gh = await githubAvatar(entry.url)
|
||||
if (gh) return gh
|
||||
return websiteFavicon(entry.url)
|
||||
}
|
||||
|
||||
function imgTag ({ name, src, width, height, imgStyle }) {
|
||||
const alt = escapeHtml(name)
|
||||
const href = escapeHtml(src)
|
||||
const dims = []
|
||||
if (width) dims.push(`width="${width}"`)
|
||||
if (height) dims.push(`height="${height}"`)
|
||||
else if (!width) dims.push('height="80"')
|
||||
const style = imgStyle ? `${IMG_STYLE}${imgStyle}` : IMG_STYLE
|
||||
return `<img src="${href}" ${dims.join(' ')} style="${style}" alt="${alt}" title="${alt}"/>`
|
||||
}
|
||||
|
||||
function linkTag (entry, logo) {
|
||||
const href = escapeHtml(entry.url)
|
||||
const img = imgTag({
|
||||
name: entry.name,
|
||||
src: logo,
|
||||
width: entry.width,
|
||||
height: entry.height,
|
||||
imgStyle: entry.imgStyle
|
||||
})
|
||||
return ` <a href="${href}" style="${LINK_STYLE}">${img}</a>`
|
||||
}
|
||||
|
||||
async function renderHtml (entries) {
|
||||
const logos = await Promise.all(entries.map(resolveLogo))
|
||||
const links = entries.map((entry, i) => linkTag(entry, logos[i]))
|
||||
const lines = ['<p align="center" style="line-height: 2.5;">']
|
||||
links.forEach((link, i) => {
|
||||
if (i > 0 && i % PER_ROW === 0 && links.length - i > 2) lines.push(' <br/>')
|
||||
lines.push(link)
|
||||
})
|
||||
lines.push('</p>')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function transformUsedBy (html) {
|
||||
return html
|
||||
.replace(/<br \/>.*?<\/td>/g, '</a></td>')
|
||||
.replace(/\n/g, '')
|
||||
.replace(/<\/tr>\s*<tr>/g, '')
|
||||
}
|
||||
|
||||
function patchReadme (html) {
|
||||
const readme = fs.readFileSync(readmePath, 'utf8').replace(/\r\n/g, '\n')
|
||||
const begin = '<!-- USED-BY-BEGIN -->'
|
||||
const end = '<!-- USED-BY-END -->'
|
||||
const re = new RegExp(`${begin}[\\s\\S]*?${end}`)
|
||||
if (!re.test(readme)) {
|
||||
throw new Error(`README.md is missing ${begin} … ${end}`)
|
||||
}
|
||||
const next = readme.replace(re, `${begin}\n${html}\n${end}`)
|
||||
fs.writeFileSync(readmePath, next)
|
||||
}
|
||||
|
||||
const entries = JSON.parse(fs.readFileSync(dataPath, 'utf8'))
|
||||
|
||||
async function main () {
|
||||
const html = await renderHtml(entries)
|
||||
patchReadme(html)
|
||||
fs.writeFileSync(path.join(outDir, 'used-by.swig'), transformUsedBy(html))
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,331 @@
|
||||
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, '.tmp/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((r) => setTimeout(r, 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, fullHtml }) {
|
||||
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,
|
||||
fullHtml,
|
||||
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, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
function highlightJson (text) {
|
||||
return escapeHtml(text)
|
||||
.replace(/"([^"]+)":/g, '<span class="json-key">"$1":</span>')
|
||||
.replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>')
|
||||
}
|
||||
|
||||
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 outputLineCount = Math.max(fullHtml.split('\n').length, 1)
|
||||
|
||||
const templateEditorH = paneHeight(templateLineCount)
|
||||
const contextEditorH = paneHeight(contextLineCount)
|
||||
const outputEditorH = paneHeight(outputLineCount)
|
||||
|
||||
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,
|
||||
fullHtml,
|
||||
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, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
window.__captureTpl.innerHTML =
|
||||
escapeHtml(tpl) + '<span class="capture-cursor">▎</span>'
|
||||
|
||||
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())
|
||||
const fullHtml = await page.evaluate(() => document.querySelector('#previewCode').textContent)
|
||||
|
||||
await prepareCapture(page, { fullTemplate, fullContext, fullHtml })
|
||||
|
||||
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, '.tmp'), { recursive: true })
|
||||
await copyFile(framePaths[framePaths.length - 1], join(root, '.tmp/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)
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
[
|
||||
{ "name": "Eleventy", "url": "https://www.11ty.dev/" },
|
||||
{ "name": "GitHub Docs", "url": "https://docs.github.com/" },
|
||||
{ "name": "Kibana", "url": "https://www.elastic.co/kibana" },
|
||||
{
|
||||
"name": "Power Pages, Azure API Management developer portal",
|
||||
"url": "https://learn.microsoft.com/en-us/power-pages/"
|
||||
},
|
||||
{
|
||||
"name": "Shopify CLI, Checkout Blocks",
|
||||
"url": "https://www.shopify.com/"
|
||||
},
|
||||
{ "name": "Directus", "url": "https://directus.io/" },
|
||||
{
|
||||
"name": "Builder.io, Mitosis",
|
||||
"url": "https://www.builder.io/"
|
||||
},
|
||||
{ "name": "Pattern Lab", "url": "https://patternlab.io/" },
|
||||
{ "name": "Rock RMS", "url": "https://www.rockrms.com/" },
|
||||
{ "name": "WISMOlabs", "url": "https://wismolabs.com/" },
|
||||
{ "name": "Dropkiq", "url": "https://www.dropkiq.com/" },
|
||||
{
|
||||
"name": "Freshet",
|
||||
"url": "https://chromewebstore.google.com/detail/freshet/mpclplhdencffbilobpcapccnihpelcg",
|
||||
"logo": "https://raw.githubusercontent.com/MattAltermatt/freshet/main/public/icon-128.png"
|
||||
}
|
||||
]
|
||||
@@ -6,6 +6,7 @@
|
||||
"version": "7.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "npm --prefix .. run build:contributors && npm --prefix .. run build:used-by",
|
||||
"build": "hexo generate",
|
||||
"start": "hexo serve",
|
||||
"lint": "eslint .",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Vendored
+4
-1
@@ -13,7 +13,10 @@ index:
|
||||
description: 'Thanks to these wonderful people! See <a href="tutorials/contribution-guidelines.html">contribution guidelines</a> if you'd like to help.'
|
||||
sponsors:
|
||||
title: Sponsors
|
||||
description: 'If you personally love LiquidJS or it's benefiting your business, please <a href="https://github.com/sponsors/harttle">sponsor us</a>!'
|
||||
description: 'Organizations and individuals who <a href="https://github.com/sponsors/harttle">sponsor LiquidJS</a>. Thank you!'
|
||||
used_by:
|
||||
title: Used by
|
||||
description: 'Products and projects running on LiquidJS. <a href="https://github.com/harttle/liquidjs/edit/master/data/used-by.json">Open a PR</a> to add yours.'
|
||||
|
||||
playground:
|
||||
title: Playground
|
||||
|
||||
Vendored
+37
-42
@@ -1,42 +1,37 @@
|
||||
<header id="banner" class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
<h2 id="banner-title">{{ page.subtitle }}</h2>
|
||||
<div id="banner-share">{{ partial('partial/share') }}</div>
|
||||
<div id="banner-start">
|
||||
<code id="banner-start-command">npm install liquidjs</code><a id="banner-start-link" href="./tutorials/setup.html"><i class="icon-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div id="content-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
{{ page.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contributors-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
<div class="section-header">
|
||||
<h3>{{__('index.contributors.title')}}</h3>
|
||||
<p class="description">{{__('index.contributors.description')}}</p>
|
||||
</div>
|
||||
<div class="contributors">
|
||||
{{ partial('partial/all-contributors') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sponsors-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
<div class="section-header">
|
||||
<h3>{{__('index.sponsors.title')}}</h3>
|
||||
<p class="description">{{__('index.sponsors.description')}}</p>
|
||||
</div>
|
||||
<div class="contributors">
|
||||
{{ partial('partial/financial-contributors') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header id="banner" class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
<h2 id="banner-title">{{ page.subtitle }}</h2>
|
||||
<div id="banner-share">{{ partial('partial/share') }}</div>
|
||||
<div id="banner-start">
|
||||
<code id="banner-start-command">npm install liquidjs</code><a id="banner-start-link" href="./tutorials/setup.html"><i class="icon-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div id="content-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
{{ page.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ partial('partial/home-section', {
|
||||
wrapId: 'used-by',
|
||||
variant: 'logos',
|
||||
title: __('index.used_by.title'),
|
||||
description: __('index.used_by.description'),
|
||||
contentPartial: 'used-by'
|
||||
}) }}
|
||||
{{ partial('partial/home-section', {
|
||||
wrapId: 'sponsors',
|
||||
variant: 'logos',
|
||||
title: __('index.sponsors.title'),
|
||||
description: __('index.sponsors.description'),
|
||||
contentPartial: 'financial-contributors'
|
||||
}) }}
|
||||
{{ partial('partial/home-section', {
|
||||
wrapId: 'contributors',
|
||||
variant: 'people',
|
||||
title: __('index.contributors.title'),
|
||||
description: __('index.contributors.description'),
|
||||
contentPartial: 'all-contributors'
|
||||
}) }}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{% if page.layout === 'playground' %}
|
||||
{{ js('js/liquid.browser.min.js') }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/src-min/ace.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/prism.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/components/prism-markup.min.js"></script>
|
||||
{% endif %}
|
||||
|
||||
{{ js('js/main') }}
|
||||
|
||||
+1
-5
@@ -1,7 +1,3 @@
|
||||
{
|
||||
"people": [
|
||||
"alice",
|
||||
"bob",
|
||||
"carol"
|
||||
]
|
||||
"name": "liquid"
|
||||
}
|
||||
|
||||
+1
-9
@@ -1,9 +1 @@
|
||||
<ul>
|
||||
{%- for person in people %}
|
||||
<li>
|
||||
<a href="{{person | prepend: "https://example.com/"}}">
|
||||
{{ person | capitalize }}
|
||||
</a>
|
||||
</li>
|
||||
{%- endfor%}
|
||||
</ul>
|
||||
<p>Hello, {{ name | capitalize }}!</p>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<div id="{{ wrapId }}-wrap" class="home-section home-section--{{ variant }}">
|
||||
<div class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
<div class="section-header">
|
||||
<h3>{{ title }}</h3>
|
||||
<p class="description">{{ description | safe }}</p>
|
||||
</div>
|
||||
<div class="contributors">
|
||||
{% if contentPartial == 'used-by' %}
|
||||
{{ partial('partial/used-by') }}
|
||||
{% elif contentPartial == 'financial-contributors' %}
|
||||
{{ partial('partial/financial-contributors') }}
|
||||
{% else %}
|
||||
{{ partial('partial/all-contributors') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+14
-6
@@ -4,16 +4,24 @@
|
||||
<div class="loader" role=status aria-busy=true></div>
|
||||
<div id="editors" class="inner hide" aria-hide=true>
|
||||
<div class="area-tpl editor-wrapper">
|
||||
<h2>Template</h2>
|
||||
<div class="editor" id="editorEl">{{ raw('partial/demo.liquid') }}</div>
|
||||
<div class="pane-window">
|
||||
<span class="pane-tab">Template</span>
|
||||
<div class="editor" id="editorEl">{{ raw('partial/demo.liquid') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="area-data editor-wrapper">
|
||||
<h2>Context</h2>
|
||||
<div class="editor" id="dataEl">{{ raw('partial/demo.json') }}</div>
|
||||
<div class="pane-window">
|
||||
<span class="pane-tab">Context</span>
|
||||
<div class="editor" id="dataEl">{{ raw('partial/demo.json') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="area-output editor-wrapper">
|
||||
<h2>Output</h2>
|
||||
<div class="editor" id="previewEl">{{__('playground.loading')}}</div>
|
||||
<div class="pane-window">
|
||||
<span class="pane-tab">Output</span>
|
||||
<div class="output-preview" id="previewEl">
|
||||
<pre class="highlight"><code class="language-markup" id="previewCode">{{__('playground.loading')}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="inner version"></p>
|
||||
|
||||
+23
-6
@@ -126,7 +126,8 @@
|
||||
background: var(--color-link-hover)
|
||||
color: #fff
|
||||
|
||||
#sponsors-wrap, #contributors-wrap
|
||||
#used-by-wrap, #sponsors-wrap, #contributors-wrap,
|
||||
.home-section
|
||||
background: var(--color-navy-lighter)
|
||||
border-top: 1px solid #161d24
|
||||
border-bottom: 1px solid #161d24
|
||||
@@ -166,24 +167,40 @@
|
||||
&:hover
|
||||
color: var(--color-link-hover)
|
||||
|
||||
#sponsors-wrap
|
||||
.home-section--logos, #sponsors-wrap, #used-by-wrap
|
||||
background: var(--color-content-bg)
|
||||
.inner
|
||||
h3
|
||||
color: var(--color-default)
|
||||
p
|
||||
color: var(--color-gray)
|
||||
.open-collective
|
||||
max-width: 100%
|
||||
@media (prefers-color-scheme: dark)
|
||||
p
|
||||
color: var(--color-default)
|
||||
|
||||
#contributors-wrap
|
||||
.home-section--people, #contributors-wrap
|
||||
border: none
|
||||
overflow: hidden;
|
||||
overflow: hidden
|
||||
|
||||
.contributors
|
||||
p
|
||||
margin: 0
|
||||
line-height: 2.5
|
||||
text-align: center
|
||||
a
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
margin: 8px
|
||||
a img
|
||||
display: inline-block
|
||||
height: 72px
|
||||
width: auto
|
||||
max-width: 160px
|
||||
vertical-align: middle
|
||||
object-fit: contain
|
||||
background: transparent
|
||||
border-radius: 0
|
||||
margin-bottom: 0
|
||||
tr
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
|
||||
+70
-23
@@ -13,14 +13,6 @@
|
||||
margin-bottom: 24px
|
||||
color: var(--color-default)
|
||||
|
||||
h2
|
||||
font-size: 0.8125rem
|
||||
font-weight: 600
|
||||
text-transform: uppercase
|
||||
letter-spacing: 0.04em
|
||||
color: var(--color-gray)
|
||||
margin: 0
|
||||
|
||||
#editors
|
||||
display: grid
|
||||
overflow: hidden
|
||||
@@ -56,25 +48,80 @@
|
||||
|
||||
.editor-wrapper
|
||||
display: flex
|
||||
gap: 8px
|
||||
flex-direction: column
|
||||
min-height: 0
|
||||
overflow: hidden
|
||||
.editor
|
||||
flex: 1 1 auto
|
||||
min-height: 0
|
||||
position: relative
|
||||
code-block-chrome()
|
||||
overflow: hidden
|
||||
@media mq-mobile
|
||||
min-height: 240px
|
||||
|
||||
.pane-window
|
||||
flex: 1 1 auto
|
||||
min-height: 0
|
||||
position: relative
|
||||
display: flex
|
||||
flex-direction: column
|
||||
playground-window-chrome()
|
||||
overflow: hidden
|
||||
@media mq-mobile
|
||||
min-height: 240px
|
||||
|
||||
.pane-tab
|
||||
position: absolute
|
||||
z-index: 5
|
||||
top: 0
|
||||
left: 0
|
||||
right: 0
|
||||
height: 36px
|
||||
line-height: 36px
|
||||
text-align: center
|
||||
font-size: 0.75rem
|
||||
font-weight: 600
|
||||
letter-spacing: 0.02em
|
||||
color: var(--color-gray)
|
||||
pointer-events: none
|
||||
user-select: none
|
||||
|
||||
.editor
|
||||
flex: 1 1 auto
|
||||
min-height: 0
|
||||
position: relative
|
||||
overflow: hidden
|
||||
.ace_editor
|
||||
font-family: font-mono
|
||||
font-size: 14px
|
||||
line-height: 1.5
|
||||
border-radius: 6px
|
||||
.ace_scrollbar
|
||||
z-index: 2
|
||||
top: 0
|
||||
bottom: 0
|
||||
height: auto !important
|
||||
|
||||
.output-preview
|
||||
flex: 1 1 auto
|
||||
min-height: 0
|
||||
cursor: default
|
||||
overflow: auto
|
||||
pre.highlight
|
||||
margin: 0
|
||||
min-height: 100%
|
||||
padding: 16px
|
||||
border: none
|
||||
box-shadow: none
|
||||
border-radius: 0 0 6px 6px
|
||||
background: var(--highlight-background)
|
||||
code
|
||||
display: block
|
||||
font-size: 14px
|
||||
line-height: 1.5
|
||||
background: transparent
|
||||
padding: 0
|
||||
white-space: pre
|
||||
|
||||
.ace_editor
|
||||
font-family: font-mono
|
||||
font-size: 14px
|
||||
line-height: 1.5
|
||||
border-radius: 6px
|
||||
.ace_gutter
|
||||
display: none !important
|
||||
width: 0 !important
|
||||
.ace_scroller
|
||||
left: 0 !important
|
||||
.ace_scrollbar
|
||||
z-index: 2
|
||||
|
||||
.version
|
||||
font-size: 0.8125rem
|
||||
|
||||
+17
@@ -102,3 +102,20 @@ code-block-chrome()
|
||||
border-radius: 6px
|
||||
border: 1px solid var(--code-border)
|
||||
box-shadow: var(--code-shadow)
|
||||
|
||||
playground-window-chrome()
|
||||
code-block-chrome()
|
||||
padding-top: 36px
|
||||
&:before
|
||||
content: ''
|
||||
position: absolute
|
||||
z-index: 4
|
||||
top: 0
|
||||
left: 0
|
||||
right: 0
|
||||
height: 36px
|
||||
background: var(--highlight-background)
|
||||
border-bottom: 1px solid var(--code-border)
|
||||
border-radius: 6px 6px 0 0
|
||||
pointer-events: none
|
||||
background-image: radial-gradient(circle at 12px 18px, #ff5f57 5px, transparent 5px), radial-gradient(circle at 28px 18px, #febc2e 5px, transparent 5px), radial-gradient(circle at 44px 18px, #28c840 5px, transparent 5px)
|
||||
|
||||
Vendored
+22
-10
@@ -48,20 +48,17 @@
|
||||
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const editor = createEditor('editorEl', 'liquid');
|
||||
const dataEditor = createEditor('dataEl', 'json');
|
||||
const preview = createEditor('previewEl', 'html');
|
||||
preview.setReadOnly(true);
|
||||
preview.renderer.setShowGutter(false);
|
||||
preview.renderer.setPadding(16);
|
||||
const previewCode = document.getElementById('previewCode');
|
||||
|
||||
const editors = [editor, dataEditor, preview];
|
||||
const editors = [editor, dataEditor];
|
||||
colorScheme.addEventListener('change', function() {
|
||||
editors.forEach(applyEditorTheme);
|
||||
});
|
||||
|
||||
const init = parseArgs(location.hash.slice(1));
|
||||
if (init) {
|
||||
editor.setValue(init.tpl, 1);
|
||||
dataEditor.setValue(init.data, 1);
|
||||
editor.setValue(init.tpl, -1);
|
||||
dataEditor.setValue(init.data, -1);
|
||||
}
|
||||
editor.on('change', update);
|
||||
dataEditor.on('change', update);
|
||||
@@ -76,7 +73,10 @@
|
||||
const editorsEl = document.querySelector('#editors');
|
||||
editorsEl.classList.remove('hide');
|
||||
editorsEl.setAttribute('aria-hide', false);
|
||||
editors.forEach(function(ed) { ed.resize(); });
|
||||
editors.forEach(function(ed) {
|
||||
ed.clearSelection();
|
||||
ed.resize();
|
||||
});
|
||||
}
|
||||
|
||||
function getEditorTheme() {
|
||||
@@ -96,11 +96,15 @@
|
||||
fontFamily: '"Source Code Pro", ui-monospace, Monaco, Menlo, Consolas, monospace',
|
||||
fontSize: '14px',
|
||||
showPrintMargin: false,
|
||||
showGutter: false,
|
||||
highlightActiveLine: false,
|
||||
highlightSelectedWord: false,
|
||||
tabSize: 2,
|
||||
useSoftTabs: true,
|
||||
scrollPastEnd: 0.25
|
||||
});
|
||||
editor.getSession().setMode('ace/mode/' + lang);
|
||||
editor.renderer.setShowGutter(false);
|
||||
editor.renderer.setScrollMargin(8, 8, 0, 0);
|
||||
return editor;
|
||||
}
|
||||
@@ -118,15 +122,23 @@
|
||||
return utoa(obj.tpl) + ',' + utoa(obj.data);
|
||||
}
|
||||
|
||||
function setPreview(value) {
|
||||
previewCode.textContent = value;
|
||||
if (window.Prism) {
|
||||
delete previewCode.dataset.highlighted;
|
||||
window.Prism.highlightElement(previewCode);
|
||||
}
|
||||
}
|
||||
|
||||
async function update() {
|
||||
const tpl = editor.getValue();
|
||||
const data = dataEditor.getValue();
|
||||
history.replaceState({}, '', '#' + serializeArgs({tpl, data}));
|
||||
try {
|
||||
const html = await engine.parseAndRender(tpl, JSON.parse(data));
|
||||
preview.setValue(html, 1);
|
||||
setPreview(html);
|
||||
} catch (err) {
|
||||
preview.setValue(err.stack, 1);
|
||||
setPreview(err.stack);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -29,12 +29,16 @@
|
||||
"build:min": "BUNDLES=min rollup -c rollup.config.mjs",
|
||||
"build:umd": "BUNDLES=umd rollup -c rollup.config.mjs",
|
||||
"build:charmap": "./bin/character-gen.js > src/util/character.ts",
|
||||
"build:docs": "run-s build:docs-liquid build:contributors build:apidoc build:changelog build:docs-hexo",
|
||||
"build:docs": "run-s build:docs-liquid build:contributors build:used-by build:apidoc build:changelog build:docs-hexo",
|
||||
"build:docs-liquid": "cross-env BUNDLES=min rollup -c rollup.config.mjs && shx cp dist/liquid.browser.min.js docs/themes/navy/source/js/",
|
||||
"build:contributors": "node bin/build-contributors.js",
|
||||
"build:used-by": "node bin/build-used-by.js",
|
||||
"build:apidoc": "shx rm -rf docs/source/api && typedoc --plugin typedoc-plugin-missing-exports ./src --gitRevision master --out docs/source/api",
|
||||
"build:changelog": "node bin/build-changelog.js",
|
||||
"build:docs-hexo": "cd docs && npm ci && npm run build && shx cp CNAME public/"
|
||||
"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"
|
||||
},
|
||||
"bin": {
|
||||
"liquidjs": "./bin/liquid.js",
|
||||
|
||||
Reference in New Issue
Block a user