mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-14 03:40:38 -07:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a7791be9e | ||
|
|
2858271c8f | ||
|
|
d22945ed5f | ||
|
|
4f7d2fd84a | ||
|
|
597ce7305f | ||
|
|
d7fa8ba5f1 | ||
|
|
1b356d350d | ||
|
|
e55128850e | ||
|
|
e8e502c585 | ||
|
|
b8bc4db46c | ||
|
|
68d500c18a | ||
|
|
de12359bfd | ||
|
|
025c40f0f2 | ||
|
|
2f414f8e40 | ||
|
|
40c52124d7 | ||
|
|
ae0c07e60b | ||
|
|
3b9202465e | ||
|
|
fc42ad7548 | ||
|
|
050a7fc68e | ||
|
|
0fdc5c79da | ||
|
|
90d2ecb107 | ||
|
|
0deb93eeae | ||
|
|
b0facc71f7 | ||
|
|
5cb843f162 | ||
|
|
38a0f510b0 | ||
|
|
1a893f8023 |
@@ -721,6 +721,42 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "edh649",
|
||||
"name": "Ed Hanton",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/527604?v=4",
|
||||
"profile": "https://github.com/edh649",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "gurdiga",
|
||||
"name": "Vlad GURDIGA",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53922?v=4",
|
||||
"profile": "https://gurdiga.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "StreakingMan",
|
||||
"name": "裸奔狂甩丁丁",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30397306?v=4",
|
||||
"profile": "https://www.streakingman.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "skynetigor",
|
||||
"name": "Ihor Panasiuk",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20903171?v=4",
|
||||
"profile": "https://github.com/skynetigor",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-${{ inputs.os }}
|
||||
path: dist
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
name: npm-logs-${{ inputs.os }}
|
||||
path: ~/.npm/_logs
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
branch: gh-pages
|
||||
folder: docs/public
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
|
||||
@@ -19,14 +19,14 @@ jobs:
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist-${{ inputs.os }}
|
||||
path: dist
|
||||
- name: Check Performance
|
||||
run: npm run perf:diff
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
|
||||
@@ -12,7 +12,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
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
npx semantic-release --dry-run
|
||||
fi
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
|
||||
+12
-12
@@ -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
|
||||
@@ -34,17 +34,17 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist-${{ matrix.os }}
|
||||
path: dist
|
||||
- name: Run Test
|
||||
run: TZ=${{ matrix.timezone }} npm test
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
name: test-npm-logs-${{ matrix.os }}-${{ matrix.node-version }}
|
||||
path: ~/.npm/_logs
|
||||
demo:
|
||||
name: Demo Check
|
||||
@@ -59,15 +59,15 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist-ubuntu-latest
|
||||
path: dist
|
||||
- name: Run Demo Test
|
||||
run: npm run test:demo
|
||||
- name: Archive npm failure logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: npm-logs
|
||||
name: demo-npm-logs
|
||||
path: ~/.npm/_logs
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
# [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* block.super with strictVariables, [#806](https://github.com/harttle/liquidjs/issues/806) ([#807](https://github.com/harttle/liquidjs/issues/807)) ([025c40f](https://github.com/harttle/liquidjs/commit/025c40f0f2f13efa62193c61d2fa56943917ac3c))
|
||||
|
||||
# [10.21.0](https://github.com/harttle/liquidjs/compare/v10.20.3...v10.21.0) (2025-02-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add find_index, has, and reject filters ([#799](https://github.com/harttle/liquidjs/issues/799)) ([0deb93e](https://github.com/harttle/liquidjs/commit/0deb93eeae4f530901e9a7d099bcc47207ad7385))
|
||||
|
||||
## [10.20.3](https://github.com/harttle/liquidjs/compare/v10.20.2...v10.20.3) (2025-02-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* empty tagToken.args since 10.20.0, fixes [#796](https://github.com/harttle/liquidjs/issues/796) ([38a0f51](https://github.com/harttle/liquidjs/commit/38a0f510b0a14baf35a368e9f07b536253394d06))
|
||||
|
||||
## [10.20.2](https://github.com/harttle/liquidjs/compare/v10.20.1...v10.20.2) (2025-01-19)
|
||||
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ For more details, refer to the [Setup Guide][setup].
|
||||
## Who's Using LiquidJS?
|
||||
|
||||
- [Eleventy](https://www.11ty.dev/): Eleventy, a simpler static site generator.
|
||||
- [Github Docs](https://github.com/github/docs): The open-source repo for docs.github.com.
|
||||
- [Opensense](https://www.opensense.com/): The smarter way to send email.
|
||||
- [Directus](https://docs.directus.io/): an instant REST+GraphQL API and intuitive no-code data collaboration app for any SQL database.
|
||||
- [Semgrep](https://github.com/returntocorp/semgrep): Lightweight static analysis for many languages.
|
||||
@@ -71,33 +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>
|
||||
</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.lambdatest.com/?utm_source=liquidjs&utm_medium=sponsor" style="display: inline-block; vertical-align: middle; margin: 8px;"><img src="https://www.lambdatest.com/blue-logo.png" width="240" style="vertical-align: middle;" alt="LambdaTest" title="LambdaTest"/></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 ✨
|
||||
@@ -210,6 +208,10 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://tovd.dev"><img src="https://avatars.githubusercontent.com/u/35376389?v=4?s=100" width="100px;" alt="Tim van Dam"/><br /><sub><b>Tim van Dam</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=timvandam" title="Code">💻</a></td>
|
||||
<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>
|
||||
</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
|
||||
|
||||
+2
-2
@@ -30,8 +30,8 @@ prismjs:
|
||||
tab_replace: ""
|
||||
|
||||
algolia:
|
||||
applicationID: 0X19J927JZ
|
||||
apiKey: 533161d821384919672e4ce8a39451b3
|
||||
applicationID: QJ35YOZTU4
|
||||
apiKey: 8c6cbb824b4c5023f0bb2ef29e228bef
|
||||
indexName: liquidjs
|
||||
twitter: harttleharttle
|
||||
github: harttle/liquidjs
|
||||
|
||||
@@ -51,10 +51,14 @@ filters:
|
||||
escape_once: escape_once.html
|
||||
find: find.html
|
||||
find_exp: find_exp.html
|
||||
find_index: find_index.html
|
||||
find_index_exp: find_index_exp.html
|
||||
first: first.html
|
||||
floor: floor.html
|
||||
group_by: group_by.html
|
||||
group_by_exp: group_by_exp.html
|
||||
has: has.html
|
||||
has_exp: has_exp.html
|
||||
inspect: inspect.html
|
||||
join: join.html
|
||||
json: json.html
|
||||
@@ -72,6 +76,8 @@ filters:
|
||||
push: push.html
|
||||
prepend: prepend.html
|
||||
raw: raw.html
|
||||
reject: reject.html
|
||||
reject_exp: reject_exp.html
|
||||
remove: remove.html
|
||||
remove_first: remove_first.html
|
||||
remove_last: remove_last.html
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: find_index
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return the 0-based index of the first object in an array for which the queried attribute has the given value or return `nil` if no item in the array satisfies the given criteria. For the following `members` array:
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | find_index: "graduation_year", 2014 | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: find_index_exp
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return the 0-based index of the first object in an array for which the given expression evaluates to true or return `nil` if no item in the array satisfies the evaluated expression.
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | find_index_exp: "item", "item.graduation_year == 2014" | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: has
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return `true` if the array includes an item for which the queried attribute has the given value or return `false` if no item in the array satisfies the given criteria. For the following `members` array:
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | has: "graduation_year", 2014 | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
true
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: has_exp
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return `true` if an item exists in an array for which the given expression evaluates to true or return `false` if no item in the array satisfies the evaluated expression.
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | has_exp: "item", "item.graduation_year == 2014" | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
true
|
||||
```
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: reject
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Creates an array excluding the objects with a given property value, or excluding [truthy][truthy] values by default when a property is not given.
|
||||
|
||||
In this example, assume you have a list of products and you want to filter out kitchen products. Using `reject`, you can create an array excluding only the products that have a `"type"` of `"kitchen"`.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
All products:
|
||||
{% for product in products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% assign non_kitchen_products = products | reject: "type", "kitchen" %}
|
||||
|
||||
Kitchen products:
|
||||
{% for product in non_kitchen_products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
All products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
|
||||
Kitchen products:
|
||||
- Vacuum
|
||||
- Television
|
||||
```
|
||||
|
||||
Say instead you have a list of products and you want to exclude taxable products. You can `reject` with a property name but no target value to reject all products with a [truthy][truthy] `"taxable"` value.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
All products:
|
||||
{% for product in products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% assign not_taxed_products = products | reject: "taxable" %}
|
||||
|
||||
Available products:
|
||||
{% for product in not_taxed_products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
All products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
|
||||
Available products:
|
||||
- Spatula
|
||||
- Television
|
||||
```
|
||||
|
||||
Additionally, `property` can be any valid Liquid variable expression as used in output syntax, except that the scope of this expression is within each item. For the following `products` array:
|
||||
|
||||
```javascript
|
||||
const products = [
|
||||
{ meta: { details: { class: 'A' } }, order: 1 },
|
||||
{ meta: { details: { class: 'B' } }, order: 2 },
|
||||
{ meta: { details: { class: 'B' } }, order: 3 }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign selected = products | reject: 'meta.details["class"]', "B" %}
|
||||
{% for item in selected -%}
|
||||
- {{ item.order }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
- 1
|
||||
```
|
||||
|
||||
## Jekyll style
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for excluding tags.
|
||||
|
||||
```javascript
|
||||
const pages = [
|
||||
{ tags: ["cat", "food"], title: 'Cat Food' },
|
||||
{ tags: ["dog", "food"], title: 'Dog Food' },
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign selected = pages | reject: 'tags', "cat" %}
|
||||
{% for item in selected -%}
|
||||
- {{ item.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Dog Food
|
||||
```
|
||||
|
||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: reject_exp
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Select all the objects in an array where the expression is false. In this example, assume you have a list of products and you want to hide your kitchen products. Using `reject_exp`, you can create an array that omits only the products that have a `"type"` of `"kitchen"`.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
All products:
|
||||
{% for product in products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% assign non_kitchen_products = products | reject_exp: "item", "item.type == 'kitchen'" %}
|
||||
|
||||
Kitchen products:
|
||||
{% for product in non_kitchen_products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
All products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
|
||||
Kitchen products:
|
||||
- Vacuum
|
||||
- Television
|
||||
```
|
||||
|
||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||
@@ -37,6 +37,7 @@ Kitchen products:
|
||||
```
|
||||
|
||||
Say instead you have a list of products and you only want to show those that are available to buy. You can `where` with a property name but no target value to include all products with a [truthy][truthy] `"available"` value.
|
||||
As a special case, the same will happen if the target value is given but evaluates to `undefined`.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
@@ -70,7 +71,6 @@ The `where` filter can also be used to find a single object in an array when com
|
||||
Input
|
||||
```liquid
|
||||
{% assign new_shirt = products | where: "type", "shirt" | first %}
|
||||
|
||||
Featured product: {{ new_shirt.title }}
|
||||
```
|
||||
|
||||
@@ -105,9 +105,11 @@ Output
|
||||
|
||||
## Jekyll style
|
||||
|
||||
{% since %}v10.19.0{% endsince %}
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for filtering tags.
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for filtering tags. Additionally, a target value of `undefined` is treated normally, entries matched are exactly those which are themselves `undefined`.
|
||||
|
||||
This option affects other array selection filters as well, such as `reject` and `find`.
|
||||
|
||||
```javascript
|
||||
const pages = [
|
||||
|
||||
@@ -10,6 +10,7 @@ const urlsToCache = [
|
||||
]
|
||||
const blackList = [
|
||||
/chrome-extension:/,
|
||||
/algolia.net/,
|
||||
/google-analytics.com.*collect/
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Echo
|
||||
|
||||
{% since %}v9.31.0{% endsince %}
|
||||
|
||||
Outputs an expression in the rendered HTML. This is identical to wrapping an expression in `{{` and `}}`, but works inside liquid tags and supports filters.
|
||||
Outputs an expression in the rendered HTML. This is identical to wrapping an expression in <code>{{</code> and <code>}}</code>, but works inside liquid tags and supports filters.
|
||||
|
||||
## echo
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ Getting started and building is described in [CONTRIBUTING.md](https://github.co
|
||||
|
||||
## Financial Support
|
||||
|
||||
LiquidJS is Open Source and Free. To help it live and thrive, especially when LiquidJS is benefiting your business, please consider contribute on [GitHub Sponsors](https://github.com/sponsors/harttle) or [Open Collective][oc]. If I'm missing anything, find me via Twitter (harttleharttle) or email (harttleharttle at gmail), to add you into [the contributors table](https://github.com/harttle/liquidjs#contributors-).
|
||||
LiquidJS is Open Source and Free. To help it live and thrive, especially when LiquidJS is benefiting your business, please consider contribute on [GitHub Sponsors](https://github.com/sponsors/harttle) or [Open Collective][oc].
|
||||
|
||||
I'll add all financial contributors into [README.md](https://github.com/harttle/liquidjs#financial-support) and it'll be also shown on https://liquidjs.com after next GitHub Actions build.
|
||||
|
||||
If I'm missing anything or you observed it not working, please don't hesitate to file an issue or find me via email (harttleharttle at gmail).
|
||||
|
||||
[oc]: https://opencollective.com/liquidjs/contribute/backer-10665/checkout
|
||||
[shopify/liquid]: https://shopify.github.io/liquid/
|
||||
|
||||
@@ -116,7 +116,7 @@ it defaults to false. For example, when set to true, a blank string would evalu
|
||||
|
||||
**preserveTimezones** is a boolean effects only literal timestamps. When set to `true`, all literal timestamps will remain the same when output. This is a parser option, so Date objects passed to LiquidJS as data will not be affected. Note that `preserveTimezones` has a higher priority than `timezoneOffset`.
|
||||
|
||||
**dateFormat** is used to specify a default format to output dates. `%A, %B %-e, %Y at %-l:%M %P %z` will be used if not specified. For example, set `dateFormat: %Y-%m-%dT%H:%M:%S:%LZ` to output all dates in [JavaScript Date.toJson()][https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON] format.
|
||||
**dateFormat** is used to specify a default format to output dates. `%A, %B %-e, %Y at %-l:%M %P %z` will be used if not specified. For example, set `dateFormat: %Y-%m-%dT%H:%M:%S:%LZ` to output all dates in [JavaScript Date.toJson()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON) format.
|
||||
|
||||
## Trimming
|
||||
|
||||
@@ -146,7 +146,7 @@ Nonexistent tags always throw errors during parsing and this behavior cannot be
|
||||
|
||||
## Parameter Order
|
||||
|
||||
Parameter orders are ignored by default, for ea `{% for i in (1..8) reversed limit:3 %}` will always perform `limit` before `reversed`, even if `reversed` occurs before `limit`. To make parameter order respected, set **orderedFilterParameters** to `true`. Its default value is `false`.
|
||||
Parameter orders are ignored by default, for example `{% for i in (1..8) reversed limit:3 %}` will always perform `limit` before `reversed`, even if `reversed` occurs before `limit`. To make parameter order respected, set **orderedFilterParameters** to `true`. Its default value is `false`.
|
||||
|
||||
[liquid]: /api/classes/Liquid.html
|
||||
[caching]: ./caching.html
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Value, TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
|
||||
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
this.value = new Value(token.args, liquid)
|
||||
this.value = new Value(tagToken.args, engine)
|
||||
},
|
||||
render: function*(ctx: Context) {
|
||||
const str = yield this.value.value(ctx); // 'alice'
|
||||
@@ -80,4 +80,4 @@ function disabledFilter(name) {
|
||||
}
|
||||
}
|
||||
engine.registerFilter('plus', disabledFilter('plus'));
|
||||
```
|
||||
```
|
||||
|
||||
@@ -16,6 +16,6 @@ LiquidJS 运算符非常简单也很特别,只支持两类运算符:
|
||||
|
||||
## 结合性
|
||||
|
||||
逻辑运算符是又结合的,所以连续的逻辑运算时计算顺序是从右向左,参考 [Shopify][operator-order] 的文档。
|
||||
逻辑运算符是右结合的,所以连续的逻辑运算时计算顺序是从右向左,参考 [Shopify][operator-order] 的文档。
|
||||
|
||||
[operator-order]: https://help.shopify.com/en/themes/liquid/basics/operators#order-of-operations
|
||||
|
||||
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>
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
<script>
|
||||
document.getElementById('search-input-wrap').classList.add('on');
|
||||
docsearch({
|
||||
apiKey: '01f36cc168657a26a385308b9e721bc6',
|
||||
appId: '{{ config.algolia.applicationID }}',
|
||||
apiKey: '{{ config.algolia.apiKey }}',
|
||||
indexName: 'liquidjs',
|
||||
inputSelector: '#search-input',
|
||||
debug: false
|
||||
|
||||
+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)
|
||||
|
||||
+21
-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
|
||||
@@ -240,3 +243,4 @@
|
||||
background: var(--color-gray)
|
||||
border-radius: 50%
|
||||
margin-bottom: 10px
|
||||
object-fit: contain
|
||||
|
||||
Generated
+8340
-4794
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "liquidjs",
|
||||
"version": "10.20.2",
|
||||
"version": "10.23.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\" .",
|
||||
|
||||
+14
-14
@@ -86,7 +86,7 @@ export class Context {
|
||||
public * _getFromScope (scope: unknown, paths: (PropertyKey | Drop)[] | string, strictVariables = this.strictVariables): IterableIterator<unknown> {
|
||||
if (isString(paths)) paths = paths.split('.')
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
scope = yield readProperty(scope as object, paths[i], this.ownPropertyOnly)
|
||||
scope = yield this.readProperty(scope as object, paths[i])
|
||||
if (strictVariables && isUndefined(scope)) {
|
||||
throw new InternalUndefinedVariableError((paths as string[]).slice(0, i + 1).join!('.'))
|
||||
}
|
||||
@@ -120,21 +120,21 @@ export class Context {
|
||||
if (key in this.environments) return this.environments
|
||||
return this.globals
|
||||
}
|
||||
readProperty (obj: Scope, key: (PropertyKey | Drop)) {
|
||||
obj = toLiquid(obj)
|
||||
key = toValue(key) as PropertyKey
|
||||
if (isNil(obj)) return obj
|
||||
if (isArray(obj) && (key as number) < 0) return obj[obj.length + +key]
|
||||
const value = readJSProperty(obj, key, this.ownPropertyOnly)
|
||||
if (value === undefined && obj instanceof Drop) return obj.liquidMethodMissing(key, this)
|
||||
if (isFunction(value)) return value.call(obj)
|
||||
if (key === 'size') return readSize(obj)
|
||||
else if (key === 'first') return readFirst(obj)
|
||||
else if (key === 'last') return readLast(obj)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export function readProperty (obj: Scope, key: (PropertyKey | Drop), ownPropertyOnly: boolean) {
|
||||
obj = toLiquid(obj)
|
||||
key = toValue(key) as PropertyKey
|
||||
if (isNil(obj)) return obj
|
||||
if (isArray(obj) && (key as number) < 0) return obj[obj.length + +key]
|
||||
const value = readJSProperty(obj, key, ownPropertyOnly)
|
||||
if (value === undefined && obj instanceof Drop) return obj.liquidMethodMissing(key)
|
||||
if (isFunction(value)) return value.call(obj)
|
||||
if (key === 'size') return readSize(obj)
|
||||
else if (key === 'first') return readFirst(obj)
|
||||
else if (key === 'last') return readLast(obj)
|
||||
return value
|
||||
}
|
||||
export function readJSProperty (obj: Scope, key: PropertyKey, ownPropertyOnly: boolean) {
|
||||
if (ownPropertyOnly && !hasOwnProperty.call(obj, key) && !(obj instanceof Drop)) return undefined
|
||||
return obj[key]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Emitter, SimpleEmitter } from '../emitters'
|
||||
import { Drop } from './drop'
|
||||
|
||||
export class BlockDrop extends Drop {
|
||||
constructor (
|
||||
// the block render from layout template
|
||||
private superBlockRender: () => Iterable<any> = () => ''
|
||||
private superBlockRender: (emitter: Emitter) => IterableIterator<unknown> | string = () => ''
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -11,7 +12,9 @@ export class BlockDrop extends Drop {
|
||||
* Provide parent access in child block by
|
||||
* {{ block.super }}
|
||||
*/
|
||||
public super () {
|
||||
return this.superBlockRender()
|
||||
public * super (): IterableIterator<unknown> {
|
||||
const emitter = new SimpleEmitter()
|
||||
yield this.superBlockRender(emitter)
|
||||
return emitter.buffer
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
import { Context } from '../context'
|
||||
|
||||
export abstract class Drop {
|
||||
public liquidMethodMissing (key: string | number): Promise<any> | any {
|
||||
public liquidMethodMissing (key: string | number, context: Context): Promise<any> | any {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
+70
-18
@@ -117,7 +117,17 @@ export function slice<T> (this: FilterImpl, v: T[] | string, begin: number, leng
|
||||
return v.slice(begin, begin + length)
|
||||
}
|
||||
|
||||
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
function expectedMatcher (this: FilterImpl, expected: any): (v: any) => boolean {
|
||||
if (this.context.opts.jekyllWhere) {
|
||||
return (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
||||
} else if (expected === undefined) {
|
||||
return (v: any) => isTruthy(v, this.context)
|
||||
} else {
|
||||
return (v: any) => equals(v, expected)
|
||||
}
|
||||
}
|
||||
|
||||
function * filter<T extends object> (this: FilterImpl, include: boolean, arr: T[], property: string, expected: any): IterableIterator<unknown> {
|
||||
const values: unknown[] = []
|
||||
arr = toArray(arr)
|
||||
this.context.memoryLimit.use(arr.length)
|
||||
@@ -125,16 +135,11 @@ export function * where<T extends object> (this: FilterImpl, arr: T[], property:
|
||||
for (const item of arr) {
|
||||
values.push(yield evalToken(token, this.context.spawn(item)))
|
||||
}
|
||||
const matcher = this.context.opts.jekyllWhere
|
||||
? (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
||||
: (v: any) => equals(v, expected)
|
||||
return arr.filter((_, i) => {
|
||||
if (expected === undefined) return isTruthy(values[i], this.context)
|
||||
return matcher(values[i])
|
||||
})
|
||||
const matcher = expectedMatcher.call(this, expected)
|
||||
return arr.filter((_, i) => matcher(values[i]) === include)
|
||||
}
|
||||
|
||||
export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
function * filter_exp<T extends object> (this: FilterImpl, include: boolean, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const filtered: unknown[] = []
|
||||
const keyTemplate = new Value(stringify(exp), this.liquid)
|
||||
const array = toArray(arr)
|
||||
@@ -143,11 +148,27 @@ export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemN
|
||||
this.context.push({ [itemName]: item })
|
||||
const value = yield keyTemplate.value(this.context)
|
||||
this.context.pop()
|
||||
if (value) filtered.push(item)
|
||||
if (value === include) filtered.push(item)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
return yield * filter.call(this, true, arr, property, expected)
|
||||
}
|
||||
|
||||
export function * reject<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
return yield * filter.call(this, false, arr, property, expected)
|
||||
}
|
||||
|
||||
export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
return yield * filter_exp.call(this, true, arr, itemName, exp)
|
||||
}
|
||||
|
||||
export function * reject_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
return yield * filter_exp.call(this, false, arr, itemName, exp)
|
||||
}
|
||||
|
||||
export function * group_by<T extends object> (this: FilterImpl, arr: T[], property: string): IterableIterator<unknown> {
|
||||
const map = new Map()
|
||||
arr = toEnumerable(arr)
|
||||
@@ -176,26 +197,57 @@ export function * group_by_exp<T extends object> (this: FilterImpl, arr: T[], it
|
||||
return [...map.entries()].map(([name, items]) => ({ name, items }))
|
||||
}
|
||||
|
||||
export function * find<T extends object> (this: FilterImpl, arr: T[], property: string, expected: string): IterableIterator<unknown> {
|
||||
function * search<T extends object> (this: FilterImpl, arr: T[], property: string, expected: string): IterableIterator<unknown> {
|
||||
const token = new Tokenizer(stringify(property)).readScopeValue()
|
||||
const array = toArray(arr)
|
||||
for (const item of array) {
|
||||
const value = yield evalToken(token, this.context.spawn(item))
|
||||
if (equals(value, expected)) return item
|
||||
const matcher = expectedMatcher.call(this, expected)
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
const value = yield evalToken(token, this.context.spawn(array[index]))
|
||||
if (matcher(value)) return [index, array[index]]
|
||||
}
|
||||
}
|
||||
|
||||
export function * find_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
function * search_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const predicate = new Value(stringify(exp), this.liquid)
|
||||
const array = toArray(arr)
|
||||
for (const item of array) {
|
||||
this.context.push({ [itemName]: item })
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
this.context.push({ [itemName]: array[index] })
|
||||
const value = yield predicate.value(this.context)
|
||||
this.context.pop()
|
||||
if (value) return item
|
||||
if (value) return [index, array[index]]
|
||||
}
|
||||
}
|
||||
|
||||
export function * has<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
const result = yield * search.call(this, arr, property, expected)
|
||||
return !!result
|
||||
}
|
||||
|
||||
export function * has_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const result = yield * search_exp.call(this, arr, itemName, exp)
|
||||
return !!result
|
||||
}
|
||||
|
||||
export function * find_index<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
const result = yield * search.call(this, arr, property, expected)
|
||||
return result ? result[0] : undefined
|
||||
}
|
||||
|
||||
export function * find_index_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const result = yield * search_exp.call(this, arr, itemName, exp)
|
||||
return result ? result[0] : undefined
|
||||
}
|
||||
|
||||
export function * find<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
const result = yield * search.call(this, arr, property, expected)
|
||||
return result ? result[1] : undefined
|
||||
}
|
||||
|
||||
export function * find_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const result = yield * search_exp.call(this, arr, itemName, exp)
|
||||
return result ? result[1] : undefined
|
||||
}
|
||||
|
||||
export function uniq<T> (this: FilterImpl, arr: T[]): T[] {
|
||||
arr = toArray(arr)
|
||||
this.context.memoryLimit.use(arr.length)
|
||||
|
||||
+13
-18
@@ -1,24 +1,19 @@
|
||||
import { toValue, argumentsToValue } from '../util/underscore'
|
||||
import { toNumber, argumentsToNumber } from '../util/underscore'
|
||||
|
||||
export const abs = argumentsToValue(Math.abs)
|
||||
export const at_least = argumentsToValue(Math.max)
|
||||
export const at_most = argumentsToValue(Math.min)
|
||||
export const ceil = argumentsToValue(Math.ceil)
|
||||
export const divided_by = argumentsToValue((dividend: number, divisor: number, integerArithmetic = false) => integerArithmetic ? Math.floor(dividend / divisor) : dividend / divisor)
|
||||
export const floor = argumentsToValue(Math.floor)
|
||||
export const minus = argumentsToValue((v: number, arg: number) => v - arg)
|
||||
export const modulo = argumentsToValue((v: number, arg: number) => v % arg)
|
||||
export const times = argumentsToValue((v: number, arg: number) => v * arg)
|
||||
export const abs = argumentsToNumber(Math.abs)
|
||||
export const at_least = argumentsToNumber(Math.max)
|
||||
export const at_most = argumentsToNumber(Math.min)
|
||||
export const ceil = argumentsToNumber(Math.ceil)
|
||||
export const divided_by = argumentsToNumber((dividend: number, divisor: number, integerArithmetic = false) => integerArithmetic ? Math.floor(dividend / divisor) : dividend / divisor)
|
||||
export const floor = argumentsToNumber(Math.floor)
|
||||
export const minus = argumentsToNumber((v: number, arg: number) => v - arg)
|
||||
export const plus = argumentsToNumber((lhs: number, rhs: number) => lhs + rhs)
|
||||
export const modulo = argumentsToNumber((v: number, arg: number) => v % arg)
|
||||
export const times = argumentsToNumber((v: number, arg: number) => v * arg)
|
||||
|
||||
export function round (v: number, arg = 0) {
|
||||
v = toValue(v)
|
||||
arg = toValue(arg)
|
||||
v = toNumber(v)
|
||||
arg = toNumber(arg)
|
||||
const amp = Math.pow(10, arg)
|
||||
return Math.round(v * amp) / amp
|
||||
}
|
||||
|
||||
export function plus (v: number, arg: number) {
|
||||
v = toValue(v)
|
||||
arg = toValue(arg)
|
||||
return Number(v) + Number(arg)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export { defaultOperators, Operators, evalToken, evalQuotedToken, Expression, is
|
||||
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 { 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'
|
||||
|
||||
+5
-1
@@ -39,7 +39,11 @@ export default class extends Tag {
|
||||
ctx.pop()
|
||||
}
|
||||
return renderChild
|
||||
? (superBlock: BlockDrop, emitter: Emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
|
||||
? (superBlock: BlockDrop, emitter: Emitter) => renderChild(
|
||||
new BlockDrop(
|
||||
(emitter: Emitter) => renderCurrent(superBlock, emitter)
|
||||
),
|
||||
emitter)
|
||||
: renderCurrent
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { NormalizedFullOptions } from '../liquid-options'
|
||||
export class TagToken extends DelimitedToken {
|
||||
public name: string
|
||||
public tokenizer: Tokenizer
|
||||
public readonly args: string;
|
||||
public constructor (
|
||||
input: string,
|
||||
begin: number,
|
||||
@@ -20,9 +21,6 @@ export class TagToken extends DelimitedToken {
|
||||
this.name = this.tokenizer.readTagName()
|
||||
this.tokenizer.assert(this.name, `illegal tag syntax, tag name expected`)
|
||||
this.tokenizer.skipBlank()
|
||||
}
|
||||
|
||||
get args (): string {
|
||||
return this.tokenizer.input.slice(this.tokenizer.p, this.contentRange[1])
|
||||
this.args = this.tokenizer.input.slice(this.tokenizer.p, this.contentRange[1])
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
import { assert } from './assert'
|
||||
import { toNumber } from './underscore'
|
||||
|
||||
export class Limiter {
|
||||
private message: string
|
||||
@@ -10,12 +9,12 @@ export class Limiter {
|
||||
this.limit = limit
|
||||
}
|
||||
use (count: number) {
|
||||
count = toNumber(count)
|
||||
count = +count || 0
|
||||
assert(this.base + count <= this.limit, this.message)
|
||||
this.base += count
|
||||
}
|
||||
check (count: number) {
|
||||
count = toNumber(count)
|
||||
count = +count || 0
|
||||
assert(count <= this.limit, this.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,7 @@ export function toValue (value: any): any {
|
||||
}
|
||||
|
||||
export function toNumber (value: any): number {
|
||||
value = Number(value)
|
||||
return isNaN(value) ? 0 : value
|
||||
return +toValue(value) || 0
|
||||
}
|
||||
|
||||
export function isNumber (value: any): value is number {
|
||||
@@ -191,6 +190,10 @@ export function argumentsToValue<F extends (...args: any) => any, T> (fn: F) {
|
||||
return function (this: T, ...args: Parameters<F>) { return fn.call(this, ...args.map(toValue)) }
|
||||
}
|
||||
|
||||
export function argumentsToNumber<F extends (...args: any) => any, T> (fn: F) {
|
||||
return function (this: T, ...args: Parameters<F>) { return fn.call(this, ...args.map(toNumber)) }
|
||||
}
|
||||
|
||||
export function escapeRegExp (text: string) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
||||
}
|
||||
|
||||
+28
-16
@@ -1,25 +1,37 @@
|
||||
import { Drop, Liquid } from '../..'
|
||||
|
||||
class SettingsDrop extends Drop {
|
||||
public foo = 'FOO'
|
||||
public bar () {
|
||||
return 'BAR'
|
||||
}
|
||||
public liquidMethodMissing (key: string) {
|
||||
return key.toUpperCase()
|
||||
}
|
||||
}
|
||||
import { Context, Drop, Liquid } from '../..'
|
||||
|
||||
describe('drop', function () {
|
||||
const settings = new SettingsDrop()
|
||||
let engine: Liquid
|
||||
beforeEach(function () {
|
||||
engine = new Liquid()
|
||||
})
|
||||
it('should support liquidMethodMissing', async function () {
|
||||
const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}`
|
||||
const html = await engine.parseAndRender(src, { settings })
|
||||
return expect(html).toBe('FOO,BAR,COO')
|
||||
|
||||
describe('liquidMethodMissing', () => {
|
||||
it('should support liquidMethodMissing', async function () {
|
||||
class SettingsDrop extends Drop {
|
||||
public foo = 'FOO'
|
||||
public bar () {
|
||||
return 'BAR'
|
||||
}
|
||||
public liquidMethodMissing (key: string) {
|
||||
return key.toUpperCase()
|
||||
}
|
||||
}
|
||||
const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}`
|
||||
const html = await engine.parseAndRender(src, { settings: new SettingsDrop() })
|
||||
return expect(html).toBe('FOO,BAR,COO')
|
||||
})
|
||||
|
||||
it('should expose context', async function () {
|
||||
class SettingsDrop extends Drop {
|
||||
public liquidMethodMissing (key: string, context: Context) {
|
||||
return key + ':' + context.getSync([key])
|
||||
}
|
||||
}
|
||||
const src = `{{settings.foo}}`
|
||||
const html = await engine.parseAndRender(src, { settings: new SettingsDrop(), foo: 'FOO' })
|
||||
return expect(html).toBe('foo:FOO')
|
||||
})
|
||||
})
|
||||
|
||||
describe('BlandDrop', function () {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync, LiquidError } from '../..'
|
||||
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync, LiquidError, IfTag } from '../..'
|
||||
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
|
||||
|
||||
describe('Issues', function () {
|
||||
@@ -558,4 +558,11 @@ describe('Issues', function () {
|
||||
CSharp <sup>2</sup> Posts:page2,page4,
|
||||
CPP <sup>1</sup> Posts:page0,`)
|
||||
})
|
||||
it('if tag condition, the token args is empty #796', () => {
|
||||
const tpl = 'Hello, {% if name %} {{ name }} {% else %} user {% endif %}'
|
||||
const engine = new Liquid()
|
||||
const parsed = engine.parse(tpl)
|
||||
const ifToken = parsed[1]
|
||||
expect((ifToken as IfTag).token.args).toEqual('name')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -417,6 +417,32 @@ describe('filters/array', function () {
|
||||
- Boring sneakers
|
||||
`)
|
||||
})
|
||||
it('should support filter with undefined target', function () {
|
||||
return test(`{% assign typed_products = products | where: "type", notdefined %}
|
||||
Typed products:
|
||||
{% for product in typed_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Typed products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
`)
|
||||
})
|
||||
it('should support no target', function () {
|
||||
return test(`{% assign typed_products = products | where: "type" %}
|
||||
Typed products:
|
||||
{% for product in typed_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Typed products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
`)
|
||||
})
|
||||
it('should support nested property', async function () {
|
||||
const authors = [
|
||||
{ name: 'Alice', books: { year: 2019 } },
|
||||
@@ -491,7 +517,7 @@ describe('filters/array', function () {
|
||||
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[]')
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should not match string with array', async () => {
|
||||
it('should filter arrays by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[{"foo":["FOO","bar"]}]', { jekyllWhere: true })
|
||||
})
|
||||
@@ -499,6 +525,14 @@ describe('filters/array', function () {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]', { jekyllWhere: true })
|
||||
})
|
||||
it('should filter by undefined when target is omitted', async () => {
|
||||
await test('{{products | where: "type" | map: "title" | join: ","}}', { products },
|
||||
'Coffee mug,Limited edition sneakers,Boring sneakers', { jekyllWhere: true })
|
||||
})
|
||||
it('should filter plainly when target is undefined', async () => {
|
||||
await test('{{products | where: "type", notdefined | map: "title" | join: ","}}', { products },
|
||||
'Coffee mug,Limited edition sneakers,Boring sneakers', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('where_exp', function () {
|
||||
@@ -537,6 +571,89 @@ describe('filters/array', function () {
|
||||
return test(tpl, scope, html)
|
||||
})
|
||||
})
|
||||
describe('reject', function () {
|
||||
const products = [
|
||||
{ title: 'Vacuum', type: 'living room' },
|
||||
{ title: 'Spatula', type: 'kitchen' },
|
||||
{ title: 'Television', type: 'living room' },
|
||||
{ title: 'Garlic press', type: 'kitchen' },
|
||||
{ title: 'Coffee mug', available: true },
|
||||
{ title: 'Limited edition sneakers', available: false },
|
||||
{ title: 'Boring sneakers', available: true }
|
||||
]
|
||||
it('should support reject by property value', function () {
|
||||
return test(`{% assign kitchen_products = products | reject: "type", "kitchen" %}
|
||||
Kitchen products:
|
||||
{% for product in kitchen_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Kitchen products:
|
||||
- Vacuum
|
||||
- Television
|
||||
- Coffee mug
|
||||
- Limited edition sneakers
|
||||
- Boring sneakers
|
||||
`)
|
||||
})
|
||||
it('should support reject truthy property', function () {
|
||||
return test(`{% assign unavailable_products = products | reject: "available" %}
|
||||
Unavailable products:
|
||||
{% for product in unavailable_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Unavailable products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
- Limited edition sneakers
|
||||
`)
|
||||
})
|
||||
it('should support reject by string property', function () {
|
||||
return test(`{% assign untyped_products = products | reject: "type" %}
|
||||
Untyped products:
|
||||
{% for product in untyped_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Untyped products:
|
||||
- Coffee mug
|
||||
- Limited edition sneakers
|
||||
- Boring sneakers
|
||||
`)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should filter arrays by exclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }, { foo: ['bar', 'baz'] }, { foo: ['FOO'] }] }
|
||||
await test('{{objs | reject: "foo", "FOO" | json}}', scope, '[{"foo":["bar","baz"]}]', { jekyllWhere: true })
|
||||
})
|
||||
it('should filter by undefined when target is omitted', async () => {
|
||||
await test('{{products | reject: "type" | map: "title" | join: ","}}', { products },
|
||||
'Vacuum,Spatula,Television,Garlic press', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('reject_exp', function () {
|
||||
const products = [
|
||||
{ title: 'Vacuum', type: 'living room' },
|
||||
{ title: 'Spatula', type: 'kitchen' },
|
||||
{ title: 'Television', type: 'living room' },
|
||||
{ title: 'Garlic press', type: 'kitchen' },
|
||||
{ title: 'Coffee mug', available: true },
|
||||
{ title: 'Limited edition sneakers', available: false },
|
||||
{ title: 'Boring sneakers', available: true }
|
||||
]
|
||||
it('should support reject by exp', function () {
|
||||
return test(`{% assign kitchen_products = products | reject_exp: "item", "item.type != 'kitchen'" %}
|
||||
Kitchen products:
|
||||
{% for product in kitchen_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Kitchen products:
|
||||
- Spatula
|
||||
- Garlic press
|
||||
`)
|
||||
})
|
||||
})
|
||||
describe('group_by', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2003, name: 'Jay' },
|
||||
@@ -616,12 +733,76 @@ describe('filters/array', function () {
|
||||
JSON.stringify(expected))
|
||||
})
|
||||
})
|
||||
describe('find', function () {
|
||||
describe('has', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack', age: 13 }
|
||||
]
|
||||
it('should support has with no value', function () {
|
||||
return test(
|
||||
`{{ members | has: "age" | json }}, {{ members | has: "height" | json }}`,
|
||||
{ members },
|
||||
`true, false`)
|
||||
})
|
||||
it('should support has by property', function () {
|
||||
return test(
|
||||
`{{ members | has: "graduation_year", 2014 | json }}`,
|
||||
{ members },
|
||||
`true`)
|
||||
})
|
||||
it('should return false if not found', function () {
|
||||
return test(
|
||||
`{{ members | has: "graduation_year", 2018 | json }}`,
|
||||
{ members },
|
||||
`false`)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should select array by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | has: "foo", "FOO" | json}}', scope, 'true', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | has: "tags", empty | json}}', scope, 'true', { jekyllWhere: true })
|
||||
})
|
||||
it('should search plainly when target is undefined', async () => {
|
||||
await test('{{members | has: "age", notdefined}}', { members }, 'true', { jekyllWhere: true })
|
||||
await test('{{members | has: "name", notdefined}}', { members }, 'false', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('has_exp', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
it('should support has by expression', function () {
|
||||
return test(
|
||||
`{{ members | has_exp: "item", "item.graduation_year == 2014" | json }}`,
|
||||
{ members },
|
||||
`true`)
|
||||
})
|
||||
it('should return false if not found', function () {
|
||||
return test(
|
||||
`{{ members | has_exp: "item", "item.graduation_year == 2018" | json }}`,
|
||||
{ members },
|
||||
`false`)
|
||||
})
|
||||
})
|
||||
describe('find', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack', age: 13 }
|
||||
]
|
||||
it('should support find with no value', function () {
|
||||
return test(
|
||||
`{{ members | find: "age" | json }}`,
|
||||
{ members },
|
||||
`{"graduation_year":2014,"name":"Jack","age":13}`)
|
||||
})
|
||||
it('should support find by property', function () {
|
||||
return test(
|
||||
`{{ members | find: "graduation_year", 2014 | json }}`,
|
||||
@@ -634,6 +815,24 @@ describe('filters/array', function () {
|
||||
{ members },
|
||||
``)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should select array by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | find: "foo", "FOO" | json}}', scope, '{"foo":["FOO","bar"]}', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | find: "tags", empty | json}}', scope, '{"tags":[]}', { jekyllWhere: true })
|
||||
})
|
||||
it('should search plainly when target is undefined', async () => {
|
||||
await test(
|
||||
'{{members | find: "age", notdefined | json}}',
|
||||
{ members },
|
||||
'{"graduation_year":2013,"name":"Jay"}',
|
||||
{ jekyllWhere: true })
|
||||
await test('{{members | find: "name", notdefined | json}}', { members }, '', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('find_exp', function () {
|
||||
const members = [
|
||||
@@ -654,4 +853,62 @@ describe('filters/array', function () {
|
||||
``)
|
||||
})
|
||||
})
|
||||
describe('find_index', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack', age: 13 }
|
||||
]
|
||||
it('should support find_index with no value', function () {
|
||||
return test(
|
||||
`{{ members | find_index: "age" | json }}`,
|
||||
{ members },
|
||||
`2`)
|
||||
})
|
||||
it('should support find_index by property', function () {
|
||||
return test(
|
||||
`{{ members | find_index: "graduation_year", 2014 | json }}`,
|
||||
{ members },
|
||||
`1`)
|
||||
})
|
||||
it('should render none if not found', function () {
|
||||
return test(
|
||||
`{{ members | find_index: "graduation_year", 2018 | json }}`,
|
||||
{ members },
|
||||
``)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should select array by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | find_index: "foo", "FOO" | json}}', scope, '0', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | find_index: "tags", empty | json}}', scope, '1', { jekyllWhere: true })
|
||||
})
|
||||
it('should search plainly when target is undefined', async () => {
|
||||
await test('{{members | find_index: "age", notdefined | json}}', { members }, '0', { jekyllWhere: true })
|
||||
await test('{{members | find_index: "name", notdefined | json}}', { members }, '', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('find_index_exp', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
it('should support find_index by expression', function () {
|
||||
return test(
|
||||
`{{ members | find_index_exp: "item", "item.graduation_year == 2014" | json }}`,
|
||||
{ members },
|
||||
`1`)
|
||||
})
|
||||
it('should render none if not found', function () {
|
||||
return test(
|
||||
`{{ members | find_index_exp: "item", "item.graduation_year == 2018" | json }}`,
|
||||
{ members },
|
||||
``)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -56,8 +56,9 @@ describe('filters/math', function () {
|
||||
it('should return "20" for 16,4', () => test('{{ 16 | plus: 4 }}', '20'))
|
||||
it('should return "195.357" for 183.357,12',
|
||||
() => test('{{ 183.357 | plus: 12 }}', '195.357'))
|
||||
it('should convert first arg as number', () => test('{{ "4" | plus: 2 }}', '6'))
|
||||
it('should convert both args as number', () => test('{{ "4" | plus: "2" }}', '6'))
|
||||
it('should convert first arg to number', () => test('{{ "4" | plus: 2 }}', '6'))
|
||||
it('should convert both args to numbers', () => test('{{ "4" | plus: "2" }}', '6'))
|
||||
it('should convert invalid string to 0', () => test('{{ "abc" | plus: "2" }}', '2'))
|
||||
it('should support variable', () => test('{{ 4 | plus: b }}', { b: 2 }, '6'))
|
||||
})
|
||||
|
||||
|
||||
@@ -101,6 +101,26 @@ describe('tags/layout', function () {
|
||||
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
|
||||
return expect(html).toBe(output)
|
||||
})
|
||||
it('should pass block.super as a string', async function () {
|
||||
mock({
|
||||
'/parent.html': '{% block css %}<link href="base.css" rel="stylesheet">{% endblock %}'
|
||||
})
|
||||
const src = '{% layout "parent.html" %}' +
|
||||
'{%block css%}{{block.super}}<meta basesize={{block.super | size}}>{%endblock%}'
|
||||
const html = await liquid.parseAndRender(src)
|
||||
const output = '<link href="base.css" rel="stylesheet"><meta basesize=39>'
|
||||
return expect(html).toBe(output)
|
||||
})
|
||||
it('should support block.super while strictVariables', async function () {
|
||||
mock({
|
||||
'/parent.html': '{% block css %}<link href="base.css" rel="stylesheet">{% endblock %}'
|
||||
})
|
||||
const src = '{% layout "parent.html" %}' +
|
||||
'{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
|
||||
const html = await liquid.parseAndRender(src, undefined, { strictVariables: true })
|
||||
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
|
||||
return expect(html).toBe(output)
|
||||
})
|
||||
it('should render block.super to empty if no parent exists', async function () {
|
||||
mock({
|
||||
'/parent.html': '{% block css %}{{block.super}}<link href="base.css" rel="stylesheet">{% endblock %}'
|
||||
|
||||
Reference in New Issue
Block a user