mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-13 19:30:39 -07:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40bc417cb6 | ||
|
|
eb4683ee3f | ||
|
|
f1fc573a65 | ||
|
|
524cd92cfe | ||
|
|
0d9e797889 | ||
|
|
3cd024d652 | ||
|
|
85233e0568 | ||
|
|
02403a1879 | ||
|
|
1c6316111d | ||
|
|
71aa1b1998 | ||
|
|
350f95c8f7 | ||
|
|
955b7971c0 | ||
|
|
8686876067 | ||
|
|
3a02eb12bf | ||
|
|
906707833e | ||
|
|
a2da822cb8 | ||
|
|
86fc135d9e | ||
|
|
5d953132e8 | ||
|
|
2858271c8f | ||
|
|
d22945ed5f | ||
|
|
4f7d2fd84a | ||
|
|
597ce7305f | ||
|
|
d7fa8ba5f1 | ||
|
|
1b356d350d |
@@ -748,6 +748,60 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "skynetigor",
|
||||
"name": "Ihor Panasiuk",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20903171?v=4",
|
||||
"profile": "https://github.com/skynetigor",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rosomri",
|
||||
"name": "Omri Rosner",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/68001413?v=4",
|
||||
"profile": "https://github.com/rosomri",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "immerrr",
|
||||
"name": "immerrr again",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/579798?v=4",
|
||||
"profile": "https://github.com/immerrr",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rongjiecomputer",
|
||||
"name": "Loo Rong Jie",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/13115060?v=4",
|
||||
"profile": "https://github.com/rongjiecomputer",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MorielHarush",
|
||||
"name": "MorielHarush",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/93482738?v=4",
|
||||
"profile": "https://github.com/MorielHarush",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "peaktwilight",
|
||||
"name": "Peak Twilight",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/77903714?v=4",
|
||||
"profile": "https://doruk.ch",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -4,6 +4,11 @@ jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -12,7 +17,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '22'
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
- name: Release
|
||||
|
||||
@@ -14,13 +14,13 @@ jobs:
|
||||
node-versoin: 22
|
||||
- os: ubuntu-latest
|
||||
timezone: Etc/GMT
|
||||
node-version: 20
|
||||
- os: ubuntu-latest
|
||||
timezone: Asia/Shanghai
|
||||
node-version: 18
|
||||
- os: ubuntu-latest
|
||||
timezone: Asia/Shanghai
|
||||
node-version: 16
|
||||
- os: ubuntu-latest
|
||||
timezone: Asia/Shanghai
|
||||
node-version: 15
|
||||
- os: ubuntu-latest
|
||||
timezone: Asia/Shanghai
|
||||
node-version: 14
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-npm-logs-${{ matrix.os }}-${{ replace(matrix.timezone, '/', '_') }}-${{ matrix.node-version }}
|
||||
name: test-npm-logs-${{ matrix.os }}-${{ matrix.node-version }}
|
||||
path: ~/.npm/_logs
|
||||
demo:
|
||||
name: Demo Check
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
# [10.24.0](https://github.com/harttle/liquidjs/compare/v10.23.0...v10.24.0) (2025-10-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **filters:** Add base64_encode and base64_decode filters for Shopify compatibility ([#828](https://github.com/harttle/liquidjs/issues/828)) ([86fc135](https://github.com/harttle/liquidjs/commit/86fc135d9ec0137689faf150535b9315e75ecc30))
|
||||
|
||||
# [10.23.0](https://github.com/harttle/liquidjs/compare/v10.22.0...v10.23.0) (2025-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Export specific tokens as types ([#824](https://github.com/harttle/liquidjs/issues/824)) ([4f7d2fd](https://github.com/harttle/liquidjs/commit/4f7d2fd84a8884e1009b13346d331a99b9721149))
|
||||
|
||||
# [10.22.0](https://github.com/harttle/liquidjs/compare/v10.21.1...v10.22.0) (2025-10-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* math filters coerce invalid string to 0, [#813](https://github.com/harttle/liquidjs/issues/813) ([#819](https://github.com/harttle/liquidjs/issues/819)) ([e8e502c](https://github.com/harttle/liquidjs/commit/e8e502c5854c9649bf7611a671a068dc260011d1))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow context access in liquidMethodMissing, [#808](https://github.com/harttle/liquidjs/issues/808) ([#820](https://github.com/harttle/liquidjs/issues/820)) ([e551288](https://github.com/harttle/liquidjs/commit/e55128850e507687f9d85a012fc3a72ac2550f3b))
|
||||
|
||||
## [10.21.1](https://github.com/harttle/liquidjs/compare/v10.21.0...v10.21.1) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
@@ -72,35 +72,30 @@ Feel free to create a PR or contact me to add your use case into this list!
|
||||
If you personally love LiquidJS or it's benefiting your business, please consider financially support us via [GitHub Sponsors](https://github.com/sponsors/harttle). Special thanks to our sponsors!
|
||||
|
||||
<!-- FINANCIAL-CONTRIBUTORS-BEGIN -->
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.opensense.com/"><img src="https://images.opencollective.com/opensense-inc/bf840ae/logo/256.png?height=100" width="100px;" alt="Opensense Inc."/><br /><sub><b>Opensense</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.11ty.dev/"><img src="https://avatars.githubusercontent.com/u/35147177?v=4&s=100" width="100px;" alt="Eleventy"/><br /><sub><b>Eleventy</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://about.me/peterdehaan"><img src="https://avatars2.githubusercontent.com/u/557895?v=4&s=100" width="100px;" alt="Peter deHaan"/><br /><sub><b>Peter deHaan</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://opencollective.com/touchless"><img src="https://images.opencollective.com/touchless/273bc74/logo/256.png?height=100" width="100px;" alt="Touchless"/><br /><sub><b>Touchless</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.dropkiq.com/"><img src="https://images.opencollective.com/1bertlol/43a8ea8/logo/256.png?height=100" width="100px;" alt="Adam Darrah"/><br /><sub><b>Dropkiq</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://dailycontributors.com/"><img src="https://images.opencollective.com/dailycontributors/3c2e057/logo/256.png?height=100&width=100" width="100px;" alt="Dailycontributors"/><br /><sub><b>Dailycontributors</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coni2k"><img src="https://avatars0.githubusercontent.com/u/1284601?v=4&s=100" width="100px;" alt="coni2k"/><br /><sub><b>Serkan Holat</b></sub></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/amit777"><img src="https://avatars0.githubusercontent.com/u/2703309?v=4&s=100" width="100px;" alt="amit777"/><br /><sub><b>amit777</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://opencollective.com/khaled-salem"><img src="https://images.opencollective.com/khaled-salem/avatar/256.png?height=256" width="100px;" alt="Khaled Salem"/><br /><sub><b>Khaled Salem</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://sentry.io/"><img src="https://avatars.githubusercontent.com/u/1396951?v=4&s=100" width="100px;" alt="Sentry"/><br /><sub><b>Sentry</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.checkoutblocks.com/"><img src="https://avatars.githubusercontent.com/u/114603307?v=4&s=100" width="100px;" alt="Checkout Blocks"/><br /><sub><b>Checkout Blocks</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://customer.io/"><img src="https://avatars.githubusercontent.com/u/1152079?v=4&s=100" width="100px;" alt="Customer IO"/><br /><sub><b>Customer IO</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15fathoms"><img src="https://avatars.githubusercontent.com/u/79156039?v=4&s=100" width="100px;" alt="Emmanuel Cartelli"/><br /><sub><b>Emmanuel Cartelli</b></sub></a><br /></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/microsoft"><img src="https://avatars.githubusercontent.com/u/6154722?v=4&s=100" width="100px;" alt="Microsoft"/><br /><sub><b>Microsoft</b></sub></a><br /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.pakstyle.pk/"><img src="https://images.opencollective.com/pakstyle/2b81605/logo/256.png?height=100" width="100px;" alt="PakStyle.pk"/><br /><sub><b>PakStyle.pk</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://syntax.fm/"><img src="https://avatars.githubusercontent.com/u/130389858?v=4&s=100" width="100px;" alt="Syntax Podcast"/><br /><sub><b>Syntax Podcast</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://opencollective.com/cartelli-emmanuel"><img src="https://images.opencollective.com/cartelli-emmanuel/avatar/256.png?height=100" width="100px;" alt="Cartelli Emmanuel"/><br /><sub><b>Cartelli Emmanuel</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.escorta.com/"><img src="https://images.opencollective.com/escortacom/avatar/256.png?height=100" width="100px;" alt="EscortA.com"/><br /><sub><b>EscortA.com</b></sub></a></td>
|
||||
<td align="center" valign="middle" width="14.28%"><a href="https://chudovo.com/"><img src="https://images.opencollective.com/Chudovo/avatar/256.png?height=100" style="max-width:100px;max-height:100px;" alt="Chudovo"/><br /><sub><b>Chudovo</b></sub></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<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://avatars.githubusercontent.com/u/35147177?v=4&s=100" height="80" style="vertical-align: middle;" alt="Eleventy" title="Eleventy"/></a>
|
||||
<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://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/>
|
||||
<a href="https://www.testmuai.com/?utm_medium=sponsor&utm_source=liquidjs" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/27130435?s=200&v=4" width="80" style="vertical-align: middle;" alt="TestMu AI" title="TestMu AI"/></a>
|
||||
<a href="https://chudovo.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/Chudovo/avatar/256.png?height=100" width="160" style="vertical-align: middle;background: white;padding: 8px 16px;" alt="Chudovo" title="Chudovo"/></a>
|
||||
<a href="https://dailycontributors.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/dailycontributors/3c2e057/logo/256.png?height=50&width=100" width="120" style="vertical-align: middle;" alt="Dailycontributors" title="Dailycontributors"/></a>
|
||||
<a href="https://www.pakstyle.pk/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/pakstyle/2b81605/logo/256.png?height=100" height="80" style="vertical-align: middle;" alt="PakStyle.pk" title="PakStyle.pk"/></a>
|
||||
<a href="https://www.escorta.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/escortacom/avatar/256.png?height=100" height="45" style="vertical-align: middle;" alt="EscortA.com" title="EscortA.com"/></a>
|
||||
<br/>
|
||||
<a href="https://opencollective.com/touchless" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/touchless/273bc74/logo/256.png?height=100" height="80" style="vertical-align: middle;" alt="Touchless" title="Touchless"/></a>
|
||||
<a href="https://www.dropkiq.com/" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/1bertlol/43a8ea8/logo/256.png?height=100" height="80" style="vertical-align: middle;" alt="Dropkiq" title="Dropkiq"/></a>
|
||||
<a href="https://about.me/peterdehaan" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars2.githubusercontent.com/u/557895?v=4&s=100" height="80" style="vertical-align: middle;" alt="Peter deHaan" title="Peter deHaan"/></a>
|
||||
<a href="https://github.com/coni2k" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars0.githubusercontent.com/u/1284601?v=4&s=100" height="80" style="vertical-align: middle;" alt="Serkan Holat" title="Serkan Holat"/></a>
|
||||
<a href="https://github.com/amit777" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars0.githubusercontent.com/u/2703309?v=4&s=100" height="80" style="vertical-align: middle;" alt="amit777" title="amit777"/></a>
|
||||
<a href="https://opencollective.com/khaled-salem" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/khaled-salem/avatar/256.png?height=256" height="80" style="vertical-align: middle;" alt="Khaled Salem" title="Khaled Salem"/></a>
|
||||
<a href="https://github.com/15fathoms" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://avatars.githubusercontent.com/u/79156039?v=4&s=100" height="80" style="vertical-align: middle;" alt="Emmanuel Cartelli" title="Emmanuel Cartelli"/></a>
|
||||
<a href="https://opencollective.com/cartelli-emmanuel" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://images.opencollective.com/cartelli-emmanuel/avatar/256.png?height=100" height="80" style="vertical-align: middle;" alt="Cartelli Emmanuel" title="Cartelli Emmanuel"/></a>
|
||||
</p>
|
||||
<!-- FINANCIAL-CONTRIBUTORS-END -->
|
||||
|
||||
## Contributors ✨
|
||||
@@ -216,6 +211,14 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/edh649"><img src="https://avatars.githubusercontent.com/u/527604?v=4?s=100" width="100px;" alt="Ed Hanton"/><br /><sub><b>Ed Hanton</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=edh649" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://gurdiga.com"><img src="https://avatars.githubusercontent.com/u/53922?v=4?s=100" width="100px;" alt="Vlad GURDIGA"/><br /><sub><b>Vlad GURDIGA</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=gurdiga" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.streakingman.com"><img src="https://avatars.githubusercontent.com/u/30397306?v=4?s=100" width="100px;" alt="裸奔狂甩丁丁"/><br /><sub><b>裸奔狂甩丁丁</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=StreakingMan" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/skynetigor"><img src="https://avatars.githubusercontent.com/u/20903171?v=4?s=100" width="100px;" alt="Ihor Panasiuk"/><br /><sub><b>Ihor Panasiuk</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=skynetigor" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rosomri"><img src="https://avatars.githubusercontent.com/u/68001413?v=4?s=100" width="100px;" alt="Omri Rosner"/><br /><sub><b>Omri Rosner</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=rosomri" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/immerrr"><img src="https://avatars.githubusercontent.com/u/579798?v=4?s=100" width="100px;" alt="immerrr again"/><br /><sub><b>immerrr again</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=immerrr" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rongjiecomputer"><img src="https://avatars.githubusercontent.com/u/13115060?v=4?s=100" width="100px;" alt="Loo Rong Jie"/><br /><sub><b>Loo Rong Jie</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=rongjiecomputer" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MorielHarush"><img src="https://avatars.githubusercontent.com/u/93482738?v=4?s=100" width="100px;" alt="MorielHarush"/><br /><sub><b>MorielHarush</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=MorielHarush" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://doruk.ch"><img src="https://avatars.githubusercontent.com/u/77903714?v=4?s=100" width="100px;" alt="Peak Twilight"/><br /><sub><b>Peak Twilight</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=peaktwilight" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -20,7 +20,6 @@ awk '/ALL-CONTRIBUTORS-LIST:START/{flag=1;next}/ALL-CONTRIBUTORS-LIST:END/{flag=
|
||||
# create docs/themes/navy/layout/partial/financial-contributors.swig
|
||||
awk '/FINANCIAL-CONTRIBUTORS-BEGIN/{flag=1;next}/FINANCIAL-CONTRIBUTORS-END/{flag=0}flag' README.md | \
|
||||
sed 's/<br \/>.*<\/td>/<\/a><\/td>/g' | \
|
||||
sed 's/width="[^"]*"//g' | \
|
||||
tr -d '\n' | \
|
||||
sed 's/<\/tr>\s*<tr>//g' \
|
||||
> docs/themes/navy/layout/partial/financial-contributors.swig
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ const engine = new Liquid({
|
||||
// layout files for `{% layout %}`
|
||||
layouts: process.cwd() + '/layouts',
|
||||
// partial files for `{% include %}` and `{% render %}`
|
||||
partials: process.cwd() + '/partials'
|
||||
partials: [process.cwd() + '/partials', 'node_modules']
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
set -ex
|
||||
set -e
|
||||
|
||||
npm start | grep 'LiquidJS Demo'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
set -x
|
||||
set -e
|
||||
|
||||
LOG_FILE=$(mktemp)
|
||||
npm start > $LOG_FILE 2>&1 &
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
set -ex
|
||||
set -e
|
||||
|
||||
npm start | grep 'NodeJS Demo for LiquidJS'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
set -ex
|
||||
set -e
|
||||
|
||||
npm start | grep '\[11:8] {{ todo }}'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
set -ex
|
||||
set -e
|
||||
|
||||
npm run build && npm start | grep 'TypeScript Demo for LiquidJS'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
set -ex
|
||||
set -e
|
||||
|
||||
npm run build
|
||||
npm start | grep 'Webpack Demo for LiquidJS'
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: base64_decode
|
||||
---
|
||||
|
||||
{% since %}v10.24.0{% endsince %}
|
||||
|
||||
Decodes a Base64-formatted string back to its original text.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ "b25lIHR3byB0aHJlZQ==" | base64_decode }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
one two three
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ "SGVsbG8sIFdvcmxkISBAIyQl" | base64_decode }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Hello, World! @#$%
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: base64_encode
|
||||
---
|
||||
|
||||
{% since %}v10.24.0{% endsince %}
|
||||
|
||||
Encodes a string into Base64 format.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ "one two three" | base64_encode }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
b25lIHR3byB0aHJlZQ==
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ "Hello, World! @#$%" | base64_encode }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
SGVsbG8sIFdvcmxkISBAIyQl
|
||||
```
|
||||
@@ -15,5 +15,6 @@ HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_b
|
||||
Array | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
|
||||
Date | date, date_to_xmlschema, date_to_rfc822, date_to_string, date_to_long_string
|
||||
Misc | default, json, jsonify, inspect, raw, to_integer
|
||||
Base64 | base64_encode, base64_decode
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -24,6 +24,7 @@ Though we're trying to be compatible with the Ruby version, there are still some
|
||||
|
||||
* Truthy and Falsy. All values except `undefined`, `null`, `false` are truthy, whereas in Ruby Liquid all except `nil` and `false` are truthy. See [#26][#26].
|
||||
* Number. In JavaScript we cannot distinguish or convert between `float` and `integer`, see [#59][#59]. And when applied `size` filter, numbers always return 0, which is 8 for integer in ruby, cause they do not have a `length` property.
|
||||
* Stringify: We've aligned string coercion for primitive types. While some differences remain; for example, in Shopify/liquid, `strip` returns the "inspected" string of an input array, whereas in LiquidJS, the `strip` filter simply stringifies the input array [#852][#852].
|
||||
* [.to_liquid()](https://github.com/Shopify/liquid/wiki/Introduction-to-Drops) is replaced by `.toLiquid()`
|
||||
* [.to_s()](https://www.rubydoc.info/gems/liquid/Liquid/Drop) is replaced by JavaScript `.toString()`
|
||||
* Iteration order for objects. The iteration order of JavaScript objects, and thus LiquidJS objects, is a combination of the insertion order for string keys, and ascending order for number-like keys, while the iteration order of Ruby Hash is simply the insertion order.
|
||||
@@ -47,6 +48,7 @@ Though we're trying to be compatible with the Ruby version, there are still some
|
||||
[#236]: https://github.com/harttle/liquidjs/issues/236
|
||||
[#414]: https://github.com/harttle/liquidjs/discussions/414
|
||||
[#485]: https://github.com/harttle/liquidjs/discussions/485
|
||||
[#852]: https://github.com/harttle/liquidjs/discussions/852
|
||||
[sort]: https://liquidjs.com/filters/sort.html
|
||||
[stable-sort]: https://v8.dev/features/stable-sort
|
||||
[plugins]: ./plugins.html#Plugin-List
|
||||
|
||||
@@ -5,17 +5,64 @@ title: Operators
|
||||
LiquidJS operators are very simple and different. There're 2 types of operators supported:
|
||||
|
||||
* Comparison operators: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
||||
* Logic operators: `or`, `and`, `contains`
|
||||
* Logic operators: `not`, `or`, `and`, `contains`
|
||||
|
||||
Thus numerical operators are not supported and you cannot even plus two numbers like this `{% raw %}{{a + b}}{% endraw %}`, instead we need a filter `{% raw %}{{ a | plus: b}}{% endraw %}`. Actually `+` is a valid variable name in LiquidJS.
|
||||
|
||||
## Logic Operators
|
||||
|
||||
### not
|
||||
|
||||
Negates a condition. Returns `true` if the condition is false, and `false` if the condition is true.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% if not user.active %}
|
||||
User is inactive
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### and
|
||||
|
||||
Returns `true` if both conditions are true.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% if user.age >= 18 and user.verified %}
|
||||
Access granted
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### or
|
||||
|
||||
Returns `true` if at least one condition is true.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% if user.isAdmin or user.isModerator %}
|
||||
You have elevated privileges
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### contains
|
||||
|
||||
Checks if a string contains a substring, or if an array contains an element.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% if product.title contains "Pack" %}
|
||||
This is a pack
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
## Precedence
|
||||
|
||||
1. Comparison operators. All comparison operations have the same precedence and higher than logic operators.
|
||||
2. Logic operators. All logic operators have the same precedence.
|
||||
1. Comparison operators, and `contains`. All comparison operators alongside `contains` have the same (highest) precedence.
|
||||
2. `not` operator. It has slightly more precedence than `or` and `and`.
|
||||
3. `or` and `and` operators. These logic operators have the same (lowest) precedence.
|
||||
|
||||
## Associativity
|
||||
|
||||
Logic operators are evaluated from right to left, see [shopify docs][operator-order].
|
||||
|
||||
[operator-order]: https://help.shopify.com/en/themes/liquid/basics/operators#order-of-operations
|
||||
[operator-order]: https://shopify.dev/docs/api/liquid/basics#order-of-operations
|
||||
|
||||
@@ -45,26 +45,20 @@ It can be a string-typed path (see above example), or a list of root directories
|
||||
|
||||
```javascript
|
||||
var engine = new Liquid({
|
||||
root: ['views/', 'views/partials/'],
|
||||
root: ['views/'],
|
||||
partials: ['views/partials/'],
|
||||
layouts: ['views/layouts/'],
|
||||
extname: '.liquid'
|
||||
});
|
||||
```
|
||||
|
||||
{% note tip Relative Paths %}Relative paths in <code>root</code> will be resolved against <code>cwd()</code>.{% endnote %}
|
||||
|
||||
When `{% raw %}{% render "foo" %}{% endraw %}` is rendered or `liquid.renderFile('foo')` is called, the following files will be looked up and the first existing file will be used:
|
||||
- When `parse()`, `render()` functions are called, for example `liquid.renderFile('foo')`, templates under `root` will be looked up.
|
||||
- When a partial is requested, for example `{% raw %}{% render "foo" %}{% endraw %}`, templates under `partials` will be looked up.
|
||||
- When a layout is requested, for example `{% raw %}{% layout "foo" %}{% endraw %}`, templates under `layouts` will be looked up.
|
||||
|
||||
- `cwd()`/views/foo.liquid
|
||||
- `cwd()`/views/partials/foo.liquid
|
||||
|
||||
If none of the above files exists, an `ENOENT` error will be thrown. Here's a demo for Node.js: [demo/nodejs](https://github.com/harttle/liquidjs/tree/master/demo/nodejs).
|
||||
|
||||
When LiquidJS is used in browser, say current location is <https://example.com/bar/index.html>, only the first `root` will be used and the file to be fetched is:
|
||||
|
||||
- <https://example.com/bar/foo.liquid>
|
||||
|
||||
If fetch fails, a 404/500 error or network failures for example, an `ENOENT` error will be thrown.
|
||||
Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/tree/master/demo/browser).
|
||||
When LiquidJS is used in browser, the paths will be resolved based on current location. Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/tree/master/demo/browser).
|
||||
|
||||
## Abstract File System
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ LiquidJS 一直很重视兼容于 Ruby 版本的 Liquid。Liquid 模板语言最
|
||||
|
||||
* 真和假。在 LiquidJS 中 `undefined`, `null`, `false` 是假,之外的都是真;在 Ruby 中 `nil` 和 `false` 是假,其他都是真。见 [#26][#26]。
|
||||
* 数字。JavaScript 不区分浮点数和整数,因此缺失一部分整数算术,见 [#59][#59]。此外 `size` 过滤器作用于数字时总是返回零,而不是 Ruby 中的浮点数或整数的内存大小。
|
||||
* 输出字符串。基本类型的输出已经和 Shopify/liquid 对齐,但是仍然存在一些区别。比如在 Shopify/liquid 中 `strip` 会返回 inspect 字符串,但 LiquidJS `strip` 只是简单地把输入转换为字符串 [#852][#852]。
|
||||
* Drop 中的 [.to_liquid()](https://github.com/Shopify/liquid/wiki/Introduction-to-Drops) 替换为 `.toLiquid()`。
|
||||
* 数据的 [.to_s()](https://www.rubydoc.info/gems/liquid/Liquid/Drop) 替换为 `.toString()`。
|
||||
* 对象的迭代顺序。JavaScript 对象的迭代顺序是插入顺序和数字键递增顺序的组合,但 Ruby Hash 中只是插入顺序(JavaScript 字面量 Object 和 Ruby 字面量 Hash 的插入顺序解释也不同)。
|
||||
@@ -46,6 +47,7 @@ LiquidJS 一直很重视兼容于 Ruby 版本的 Liquid。Liquid 模板语言最
|
||||
[#236]: https://github.com/harttle/liquidjs/issues/236
|
||||
[#414]: https://github.com/harttle/liquidjs/discussions/414
|
||||
[#485]: https://github.com/harttle/liquidjs/discussions/485
|
||||
[#852]: https://github.com/harttle/liquidjs/discussions/852
|
||||
[sort]: https://liquidjs.com/filters/sort.html
|
||||
[stable-sort]: https://v8.dev/features/stable-sort
|
||||
[plugins]: ./plugins.html#插件列表
|
||||
|
||||
@@ -5,14 +5,61 @@ title: 运算符
|
||||
LiquidJS 运算符非常简单也很特别,只支持两类运算符:
|
||||
|
||||
* 比较运算符:`==`, `!=`, `>`, `<`, `>=`, `<=`
|
||||
* 逻辑运算符:`or`, `and`, `contains`
|
||||
* 逻辑运算符:`not`, `or`, `and`, `contains`
|
||||
|
||||
因此普通的数学运算是不支持的,比如 `{% raw %}{{a + b}}{% endraw %}`。它的替代方案是过滤器 `{% raw %}{{ a | plus: b}}{% endraw %}`。事实上 `+` 在 LiquidJS 中是一个合法的变量名。
|
||||
|
||||
## 逻辑运算符
|
||||
|
||||
### not
|
||||
|
||||
对条件取反。如果条件为假则返回 `true`,如果条件为真则返回 `false`。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{% if not user.active %}
|
||||
用户未激活
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### and
|
||||
|
||||
当两个条件都为真时返回 `true`。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{% if user.age >= 18 and user.verified %}
|
||||
允许访问
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### or
|
||||
|
||||
当至少一个条件为真时返回 `true`。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{% if user.isAdmin or user.isModerator %}
|
||||
您拥有提升的权限
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### contains
|
||||
|
||||
检查字符串是否包含子字符串,或数组是否包含元素。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{% if product.title contains "Pack" %}
|
||||
这是一个套装
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
## 优先级
|
||||
|
||||
1. 比较运算符。所有比较运算符具有同样的优先级,且高于逻辑运算符。
|
||||
2. 逻辑运算符。所有逻辑运算符具有同样的有衔接。
|
||||
1. 比较运算符和 `contains`。所有比较运算符和 `contains` 具有同样的(最高)优先级。
|
||||
2. `not` 运算符。它的优先级略高于 `or` 和 `and`。
|
||||
3. `or` 和 `and` 运算符。这些逻辑运算符具有同样的(最低)优先级。
|
||||
|
||||
## 结合性
|
||||
|
||||
|
||||
Vendored
+15
-15
@@ -1,5 +1,5 @@
|
||||
<header id="banner" class="wrapper">
|
||||
<div class="inner">
|
||||
<div class="inner inner-content">
|
||||
<h2 id="banner-title">{{ page.subtitle }}</h2>
|
||||
<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>
|
||||
@@ -9,7 +9,7 @@
|
||||
</header>
|
||||
<div id="intro-news-list">
|
||||
<div class="wrapper">
|
||||
<div class="inner">
|
||||
<div class="inner inner-content">
|
||||
<div id="intro-news-flex">
|
||||
{% for news in site.data.news %}
|
||||
<a href="{{news.url}}" class="intro-news-wrap">
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<div id="content-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner">
|
||||
<div class="inner inner-content">
|
||||
{{ page.content }}
|
||||
<div id="intro-cmd-wrap" class="highlight typescript"><pre><span class="line"><span class="keyword">import</span> { Liquid } <span class="keyword">from</span> <span class="string">'liquidjs'</span></span><br><span class="line"><span class="keyword">const</span> engine = <span class="keyword">new</span> Liquid()</span><br><span class="line"><span class="keyword">const</span> tpl = engine.parse(<span class="string">'Welcome to {% raw %}{{v}}{% endraw %}!'</span>)</span><br><span class="line">engine.render(tpl, {<span class="attr">v</span>: <span class="string">"Liquid"</span>}).then(<span class="built_in">console</span>.log)</span><br><span class="line"><span class="comment">// Outputs "Welcome to Liquid!"</span></span><br></pre></div>
|
||||
<div id="intro-get-started-wrap">
|
||||
@@ -32,20 +32,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contributors-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner">
|
||||
<h3>{{__('index.contributors.title')}}</h3>
|
||||
<p class="description">{{__('index.contributors.description')}}</p>
|
||||
<div class="contributors">
|
||||
{{ partial('partial/all-contributors') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sponsors-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner">
|
||||
<div class="inner inner-content">
|
||||
<h3>{{__('index.sponsors.title')}}</h3>
|
||||
<p class="description">{{__('index.sponsors.description')}}</p>
|
||||
<div class="contributors">
|
||||
@@ -54,3 +43,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contributors-wrap">
|
||||
<div class="wrapper">
|
||||
<div class="inner inner-content">
|
||||
<h3>{{__('index.contributors.title')}}</h3>
|
||||
<p class="description">{{__('index.contributors.description')}}</p>
|
||||
<div class="contributors">
|
||||
{{ partial('partial/all-contributors') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+5
-1
@@ -54,7 +54,11 @@ a
|
||||
|
||||
.inner
|
||||
@media screen
|
||||
padding: 0 gutter-width
|
||||
margin: 0 gutter-width
|
||||
.inner.inner-content
|
||||
margin: 0 32px
|
||||
@media mq-tablet
|
||||
margin: 0 48px
|
||||
|
||||
#content-wrap
|
||||
background: var(--color-content-bg)
|
||||
|
||||
+20
-17
@@ -66,6 +66,10 @@
|
||||
.intro-news-wrap
|
||||
padding: 24px 30px
|
||||
text-decoration: none
|
||||
&:first-child
|
||||
padding-left: 0
|
||||
&:last-child
|
||||
padding-right: 0
|
||||
&:hover
|
||||
background-color: var(--color-link-hover)
|
||||
@media mq-normal
|
||||
@@ -105,7 +109,7 @@
|
||||
text-align: center
|
||||
@media mq-normal
|
||||
text-align: left
|
||||
padding-left: 70px
|
||||
padding-left: 48px
|
||||
|
||||
.intro-feature-icon
|
||||
color: var(--color-link)
|
||||
@@ -116,7 +120,7 @@
|
||||
margin-bottom: 26px
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 20px
|
||||
left: 0
|
||||
font-size: 24px
|
||||
width: @font-size
|
||||
|
||||
@@ -177,9 +181,11 @@
|
||||
border-bottom: 1px solid #161d24
|
||||
margin: -1px 0
|
||||
.inner
|
||||
margin: 48px 16px
|
||||
margin-top: 48px
|
||||
margin-bottom: 48px
|
||||
@media mq-tablet
|
||||
margin: 64px 32px
|
||||
margin-top: 64px
|
||||
margin-bottom: 64px
|
||||
h3
|
||||
color: #fff
|
||||
font-size: 32px
|
||||
@@ -192,20 +198,7 @@
|
||||
text-decoration: none
|
||||
|
||||
#contributors-wrap
|
||||
.description a
|
||||
color: #fff
|
||||
&:hover
|
||||
background: #fff
|
||||
color: var(--color-navy-lighter)
|
||||
|
||||
#sponsors-wrap
|
||||
border: none
|
||||
text-align: right
|
||||
background: var(--color-content-bg)
|
||||
overflow: hidden;
|
||||
.contributors
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
.inner
|
||||
h3
|
||||
color: var(--color-default)
|
||||
@@ -227,10 +220,20 @@
|
||||
background: #fff
|
||||
color: var(--color-navy-lighter)
|
||||
|
||||
#sponsors-wrap
|
||||
border: none
|
||||
overflow: hidden;
|
||||
.description a
|
||||
color: #fff
|
||||
&:hover
|
||||
background: #fff
|
||||
color: var(--color-navy-lighter)
|
||||
|
||||
.contributors
|
||||
tr
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: center;
|
||||
td
|
||||
margin: 10px 2px 0
|
||||
a img
|
||||
|
||||
Generated
+3319
-1902
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "liquidjs",
|
||||
"version": "10.21.1",
|
||||
"version": "10.24.0",
|
||||
"sideEffects": false,
|
||||
"description": "A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.",
|
||||
"main": "dist/liquid.node.js",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"types": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=16"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"**/*.mjs\" \"**/*.ts\" .",
|
||||
@@ -68,7 +68,7 @@
|
||||
"@semantic-release/changelog": "^6.0.2",
|
||||
"@semantic-release/commit-analyzer": "^9.0.2",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/npm": "^9.0.2",
|
||||
"@semantic-release/npm": "^13.1.5",
|
||||
"@semantic-release/release-notes-generator": "^10.0.3",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/express": "^4.17.2",
|
||||
@@ -100,7 +100,7 @@
|
||||
"rollup-plugin-typescript2": "^0.31.1",
|
||||
"rollup-plugin-uglify": "^6.0.4",
|
||||
"rollup-plugin-version-injector": "^1.3.3",
|
||||
"semantic-release": "^19.0.3",
|
||||
"semantic-release": "^25.0.3",
|
||||
"sinon": "^15.0.2",
|
||||
"supertest": "^3.4.2",
|
||||
"ts-jest": "^29.0.5",
|
||||
@@ -151,6 +151,9 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts"
|
||||
|
||||
@@ -45,6 +45,11 @@ const browserFS = {
|
||||
delimiters: ['', ''],
|
||||
'./fs/fs-impl': './build/fs-impl-browser'
|
||||
}
|
||||
const browserBase64 = {
|
||||
include: './src/filters/base64.ts',
|
||||
delimiters: ['', ''],
|
||||
'./base64-impl': '../build/base64-impl-browser'
|
||||
}
|
||||
const browserStream = {
|
||||
include: './src/emitters/index.ts',
|
||||
delimiters: ['', ''],
|
||||
@@ -94,6 +99,7 @@ const browserEsm = {
|
||||
plugins: [
|
||||
versionInjection,
|
||||
replace(browserFS),
|
||||
replace(browserBase64),
|
||||
replace(browserStream),
|
||||
typescript(tsconfig('es6'))
|
||||
],
|
||||
@@ -112,6 +118,7 @@ const browserUmd = {
|
||||
plugins: [
|
||||
versionInjection,
|
||||
replace(browserFS),
|
||||
replace(browserBase64),
|
||||
replace(browserStream),
|
||||
typescript(tsconfig('es5'))
|
||||
],
|
||||
@@ -130,6 +137,7 @@ const browserMin = {
|
||||
plugins: [
|
||||
versionInjection,
|
||||
replace(browserFS),
|
||||
replace(browserBase64),
|
||||
replace(browserStream),
|
||||
typescript(tsconfig('es5')),
|
||||
uglify()
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import * as base64 from './base64-impl-browser'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
describe('base64-impl/browser', function () {
|
||||
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
|
||||
console.info('jsdom not supported, skipping base64-impl-browser...')
|
||||
return
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
const dom = new JSDOM(``, {
|
||||
url: 'https://example.com/',
|
||||
contentType: 'text/html',
|
||||
includeNodeLocations: true
|
||||
})
|
||||
|
||||
// Mock btoa and atob on global object
|
||||
Object.defineProperty(global, 'btoa', {
|
||||
value: dom.window.btoa,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(global, 'atob', {
|
||||
value: dom.window.atob,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
delete (global as any).btoa
|
||||
delete (global as any).atob
|
||||
})
|
||||
|
||||
describe('#base64Encode()', function () {
|
||||
it('should encode a simple string', function () {
|
||||
expect(base64.base64Encode('one two three')).toBe('b25lIHR3byB0aHJlZQ==')
|
||||
})
|
||||
|
||||
it('should encode an empty string', function () {
|
||||
expect(base64.base64Encode('')).toBe('')
|
||||
})
|
||||
|
||||
it('should encode a string with special characters', function () {
|
||||
expect(base64.base64Encode('Hello, World! @#$%')).toBe('SGVsbG8sIFdvcmxkISBAIyQl')
|
||||
})
|
||||
|
||||
it('should encode numeric strings', function () {
|
||||
expect(base64.base64Encode('123')).toBe('MTIz')
|
||||
})
|
||||
|
||||
it('should encode boolean strings', function () {
|
||||
expect(base64.base64Encode('true')).toBe('dHJ1ZQ==')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#base64Decode()', function () {
|
||||
it('should decode a simple string', function () {
|
||||
expect(base64.base64Decode('b25lIHR3byB0aHJlZQ==')).toBe('one two three')
|
||||
})
|
||||
|
||||
it('should decode an empty string', function () {
|
||||
expect(base64.base64Decode('')).toBe('')
|
||||
})
|
||||
|
||||
it('should decode a string with special characters', function () {
|
||||
expect(base64.base64Decode('SGVsbG8sIFdvcmxkISBAIyQl')).toBe('Hello, World! @#$%')
|
||||
})
|
||||
|
||||
it('should decode numeric strings', function () {
|
||||
expect(base64.base64Decode('MTIz')).toBe('123')
|
||||
})
|
||||
|
||||
it('should decode boolean strings', function () {
|
||||
expect(base64.base64Decode('dHJ1ZQ==')).toBe('true')
|
||||
})
|
||||
})
|
||||
|
||||
describe('round-trip encoding/decoding', function () {
|
||||
it('should encode and decode back to original', function () {
|
||||
const original = 'Hello, World!'
|
||||
const encoded = base64.base64Encode(original)
|
||||
const decoded = base64.base64Decode(encoded)
|
||||
expect(decoded).toBe(original)
|
||||
})
|
||||
|
||||
it('should handle complex strings with special characters', function () {
|
||||
const original = 'Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||
const encoded = base64.base64Encode(original)
|
||||
const decoded = base64.base64Decode(encoded)
|
||||
expect(decoded).toBe(original)
|
||||
})
|
||||
|
||||
it('should handle mixed unicode and ASCII', function () {
|
||||
const original = 'Hello 🌍'
|
||||
const encoded = base64.base64Encode(original)
|
||||
const decoded = base64.base64Decode(encoded)
|
||||
expect(decoded).toBe(original)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
export function base64Encode (str: string): string {
|
||||
return btoa(String.fromCharCode(...new TextEncoder().encode(str)))
|
||||
}
|
||||
|
||||
export function base64Decode (str: string): string {
|
||||
return new TextDecoder().decode(
|
||||
Uint8Array.from(atob(str), c => c.charCodeAt(0))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export function base64Encode (str: string): string {
|
||||
return Buffer.from(str, 'utf8').toString('base64')
|
||||
}
|
||||
|
||||
export function base64Decode (str: string): string {
|
||||
return Buffer.from(str, 'base64').toString('utf8')
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Base64 related filters
|
||||
*
|
||||
* Implements base64_encode and base64_decode filters for Shopify compatibility
|
||||
*/
|
||||
|
||||
import { FilterImpl } from '../template'
|
||||
import { stringify } from '../util'
|
||||
import { base64Encode, base64Decode } from './base64-impl'
|
||||
|
||||
export function base64_encode (this: FilterImpl, value: string): string {
|
||||
const str = stringify(value)
|
||||
this.context.memoryLimit.use(str.length)
|
||||
return base64Encode(str)
|
||||
}
|
||||
|
||||
export function base64_decode (this: FilterImpl, value: string): string {
|
||||
const str = stringify(value)
|
||||
this.context.memoryLimit.use(str.length)
|
||||
return base64Decode(str)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import * as urlFilters from './url'
|
||||
import * as arrayFilters from './array'
|
||||
import * as dateFilters from './date'
|
||||
import * as stringFilters from './string'
|
||||
import * as base64Filters from './base64'
|
||||
import misc from './misc'
|
||||
import { FilterImplOptions } from '../template'
|
||||
|
||||
@@ -14,5 +15,6 @@ export const filters: Record<string, FilterImplOptions> = {
|
||||
...arrayFilters,
|
||||
...dateFilters,
|
||||
...stringFilters,
|
||||
...base64Filters,
|
||||
...misc
|
||||
}
|
||||
|
||||
+12
-11
@@ -43,25 +43,26 @@ export class Loader {
|
||||
|
||||
public * candidates (file: string, dirs: string[], currentFile?: string, enforceRoot?: boolean) {
|
||||
const { fs, extname } = this.options
|
||||
const isAllowed = (filepath: string) => {
|
||||
if (!enforceRoot) return true
|
||||
for (const dir of dirs) {
|
||||
if (this.contains(dir, filepath)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.shouldLoadRelative(file) && currentFile) {
|
||||
const referenced = fs.resolve(this.dirname(currentFile), file, extname)
|
||||
for (const dir of dirs) {
|
||||
if (!enforceRoot || this.contains(dir, referenced)) {
|
||||
// the relatively referenced file is within one of root dirs
|
||||
yield referenced
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isAllowed(referenced)) yield referenced
|
||||
}
|
||||
for (const dir of dirs) {
|
||||
const referenced = fs.resolve(dir, file, extname)
|
||||
if (!enforceRoot || this.contains(dir, referenced)) {
|
||||
yield referenced
|
||||
}
|
||||
if (isAllowed(referenced)) yield referenced
|
||||
}
|
||||
|
||||
if (fs.fallback !== undefined) {
|
||||
const filepath = fs.fallback(file)
|
||||
if (filepath !== undefined) yield filepath
|
||||
if (filepath !== undefined && isAllowed(filepath)) yield filepath
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -2,15 +2,20 @@
|
||||
export const version = '[VI]{version}[/VI]'
|
||||
export * as TypeGuards from './util/type-guards'
|
||||
export { toValue, createTrie, Trie, toPromise, toValueSync, assert, LiquidError, ParseError, RenderError, UndefinedVariableError, TokenizationError, AssertionError } from './util'
|
||||
export type { LiquidErrors } from './util/error'
|
||||
export { Drop } from './drop'
|
||||
export type { Comparable } from './drop'
|
||||
export { Emitter } from './emitters'
|
||||
export { defaultOperators, Operators, evalToken, evalQuotedToken, Expression, isFalsy, isTruthy } from './render'
|
||||
export { Context, Scope } from './context'
|
||||
export { Value, Hash, Template, FilterImplOptions, Tag, Filter, Output, Variable, VariableLocation, VariableSegments, Variables, StaticAnalysis, StaticAnalysisOptions, analyze, analyzeSync, Arguments, PartialScope } from './template'
|
||||
export type { TagRenderReturn } from './template'
|
||||
export { Token, TopLevelToken, TagToken, ValueToken } from './tokens'
|
||||
export type { RangeToken, LiteralToken, QuotedToken, PropertyAccessToken, NumberToken } from './tokens'
|
||||
export { TokenKind, Tokenizer, ParseStream, Parser } from './parser'
|
||||
export { filters } from './filters'
|
||||
export * from './tags'
|
||||
export { defaultOptions, LiquidOptions } from './liquid-options'
|
||||
export { FS } from './fs'
|
||||
export { defaultOptions } from './liquid-options'
|
||||
export type { LiquidOptions, RenderOptions, RenderFileOptions } from './liquid-options'
|
||||
export { FS, LookupType } from './fs'
|
||||
export { Liquid } from './liquid'
|
||||
|
||||
@@ -422,7 +422,7 @@ export class Tokenizer {
|
||||
|
||||
* readFileNameTemplate (options: NormalizedFullOptions): IterableIterator<TopLevelToken> {
|
||||
const { outputDelimiterLeft } = options
|
||||
const htmlStopStrings = [',', ' ', outputDelimiterLeft]
|
||||
const htmlStopStrings = [',', ' ', '\r', '\n', '\t', outputDelimiterLeft]
|
||||
const htmlStopStringSet = new Set(htmlStopStrings)
|
||||
// break on ',' and ' ', outputDelimiterLeft only stops HTML token
|
||||
while (this.p < this.N && !htmlStopStringSet.has(this.peek())) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync, LiquidError, IfTag } from '../..'
|
||||
import { spawnSync } from 'child_process'
|
||||
import { resolve as resolvePath } from 'path'
|
||||
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
|
||||
|
||||
describe('Issues', function () {
|
||||
@@ -173,6 +175,24 @@ describe('Issues', function () {
|
||||
const html = await engine.render(tpl, { my_variable: 'foo' })
|
||||
expect(html).toBe('CONTENT for /tmp/prefix/foo-bar/suffix')
|
||||
})
|
||||
it('should prevent path traversal in dynamic include with restricted root, #851', () => {
|
||||
const projectRoot = resolvePath(__dirname, '../..')
|
||||
const poc = `
|
||||
const { Liquid } = require('./dist/liquid.node.js');
|
||||
const e = new Liquid({ root: ['/tmp'], partials: ['/tmp'], dynamicPartials: true });
|
||||
e.parseAndRender('{% include page %}', { page: '../../../etc/passwd' })
|
||||
.then(() => { console.log('OK'); })
|
||||
.catch(err => { console.error('ERR:' + err.message); process.exit(1); });
|
||||
`
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
['-e', poc],
|
||||
{ cwd: projectRoot, encoding: 'utf8' }
|
||||
)
|
||||
|
||||
expect(result.status).not.toBe(0)
|
||||
expect(result.stderr).toContain('Failed to lookup')
|
||||
})
|
||||
it('Implement liquid/echo tags #428', () => {
|
||||
const template = `{%- liquid
|
||||
for value in array
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { test } from '../../stub/render'
|
||||
|
||||
describe('filters/base64', function () {
|
||||
describe('base64_encode', function () {
|
||||
it('should encode a simple string', () => {
|
||||
return test('{{ "one two three" | base64_encode }}', 'b25lIHR3byB0aHJlZQ==')
|
||||
})
|
||||
|
||||
it('should encode an empty string', () => {
|
||||
return test('{{ "" | base64_encode }}', '')
|
||||
})
|
||||
|
||||
it('should encode a string with special characters', () => {
|
||||
return test('{{ "Hello, World! @#$%" | base64_encode }}', 'SGVsbG8sIFdvcmxkISBAIyQl')
|
||||
})
|
||||
|
||||
it('should encode unicode characters', () => {
|
||||
return test('{{ "你好世界" | base64_encode }}', '5L2g5aW95LiW55WM')
|
||||
})
|
||||
|
||||
it('should handle undefined input', () => {
|
||||
return test('{{ foo | base64_encode }}', '')
|
||||
})
|
||||
|
||||
it('should handle null input', () => {
|
||||
return test('{{ null | base64_encode }}', '')
|
||||
})
|
||||
|
||||
it('should handle numeric input', () => {
|
||||
return test('{{ 123 | base64_encode }}', 'MTIz')
|
||||
})
|
||||
|
||||
it('should handle boolean input', () => {
|
||||
return test('{{ true | base64_encode }}', 'dHJ1ZQ==')
|
||||
})
|
||||
})
|
||||
|
||||
describe('base64_decode', function () {
|
||||
it('should decode a simple string', () => {
|
||||
return test('{{ "b25lIHR3byB0aHJlZQ==" | base64_decode }}', 'one two three')
|
||||
})
|
||||
|
||||
it('should decode an empty string', () => {
|
||||
return test('{{ "" | base64_decode }}', '')
|
||||
})
|
||||
|
||||
it('should decode a string with special characters', () => {
|
||||
return test('{{ "SGVsbG8sIFdvcmxkISBAIyQl" | base64_decode }}', 'Hello, World! @#$%')
|
||||
})
|
||||
|
||||
it('should handle undefined input', () => {
|
||||
return test('{{ foo | base64_decode }}', '')
|
||||
})
|
||||
|
||||
it('should handle null input', () => {
|
||||
return test('{{ null | base64_decode }}', '')
|
||||
})
|
||||
|
||||
it('should handle numeric input', () => {
|
||||
return test('{{ "MTIz" | base64_decode }}', '123')
|
||||
})
|
||||
|
||||
it('should handle boolean input', () => {
|
||||
return test('{{ "dHJ1ZQ==" | base64_decode }}', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
describe('base64 round-trip', function () {
|
||||
it('should encode and decode back to original', () => {
|
||||
return test('{{ "Hello, World!" | base64_encode | base64_decode }}', 'Hello, World!')
|
||||
})
|
||||
|
||||
it('should handle complex strings', () => {
|
||||
const complexString = 'Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||
return test(`{{ "${complexString}" | base64_encode | base64_decode }}`, complexString)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -265,6 +265,14 @@ describe('tags/include', function () {
|
||||
const html = liquid.renderFileSync('/current.html')
|
||||
return expect(html).toBe('FOO-')
|
||||
})
|
||||
it('should support Jekyll style include with other whitespace before filename', function () {
|
||||
mock({
|
||||
'/current.html': '{% include bar/foo.html\r\n\ntitle="TITLE"\tcontent="FOO" %}',
|
||||
'/bar/foo.html': '{{include.title}}={{include.content}}-{{content}}'
|
||||
})
|
||||
const html = liquid.renderFileSync('/current.html')
|
||||
return expect(html).toBe('TITLE=FOO-')
|
||||
})
|
||||
it('should support multiple parameters', function () {
|
||||
mock({
|
||||
'/current.html': '{% include bar/foo.html header="HEADER" content="CONTENT" %}',
|
||||
|
||||
+1
-2
@@ -10,8 +10,7 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"downlevelIteration": true,
|
||||
"strict": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
"strict": true
|
||||
},
|
||||
"all": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user