mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-12 19:00:39 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
753e8f94c0 | ||
|
|
0a1d60453e | ||
|
|
4ad383d9be | ||
|
|
66eb3b842a | ||
|
|
c41a5d5bab | ||
|
|
0aca2dd8c1 | ||
|
|
4289b4e804 | ||
|
|
c3e51caa70 | ||
|
|
a19feea7c4 | ||
|
|
e87f068067 | ||
|
|
392b859381 | ||
|
|
ed95d9a760 | ||
|
|
0f824355fa | ||
|
|
3dc4290b56 | ||
|
|
2f87708989 | ||
|
|
4a0186d62a | ||
|
|
c9e4220c88 | ||
|
|
bb8f8f569d | ||
|
|
2f6d84befc | ||
|
|
463a165e5e | ||
|
|
f88490cd3c | ||
|
|
e69a51025e | ||
|
|
ace5309c08 | ||
|
|
5a90c5bf74 | ||
|
|
f6e047867e | ||
|
|
c467630e0b | ||
|
|
b377dad9e3 | ||
|
|
ecc04c99c5 | ||
|
|
9e024ff2bc | ||
|
|
eec381ec72 | ||
|
|
c7216c6358 | ||
|
|
fe78890d35 | ||
|
|
d5771ba42c | ||
|
|
776d235fca | ||
|
|
a56af6bbfb | ||
|
|
1af8553651 | ||
|
|
049685b9a0 | ||
|
|
c503cb23df | ||
|
|
01014edc49 | ||
|
|
27e579e0e2 | ||
|
|
d3e3fecf53 | ||
|
|
33009bb988 | ||
|
|
aa955173d4 | ||
|
|
15b78ebd9d | ||
|
|
575f3e2338 | ||
|
|
7dfb620d30 | ||
|
|
21b78d9ba0 | ||
|
|
5d077f461a | ||
|
|
13343040c6 | ||
|
|
ca3240c2c4 | ||
|
|
2b0c5696bc | ||
|
|
dab8a29070 | ||
|
|
f395295721 | ||
|
|
200f6648aa | ||
|
|
31aa4ec318 | ||
|
|
231574903f | ||
|
|
dbc0497386 | ||
|
|
ab5e245fba | ||
|
|
78ce5662df | ||
|
|
8b880f65a2 | ||
|
|
d605158f96 | ||
|
|
1a44b908ce | ||
|
|
a0817e5dd7 | ||
|
|
703675e1e0 | ||
|
|
819d060a92 | ||
|
|
c3d7ecbde1 | ||
|
|
8bdb70878f | ||
|
|
2e1537d50c | ||
|
|
d4afbc038f | ||
|
|
58d03890b4 | ||
|
|
e3fb47a90b | ||
|
|
e4ad93ee8c | ||
|
|
0bdf6792d1 | ||
|
|
7e99efc513 | ||
|
|
527858fc14 | ||
|
|
9da41c8a37 | ||
|
|
2717dae127 | ||
|
|
8db6c7b28d | ||
|
|
298db97b84 | ||
|
|
bd8e2b0f96 | ||
|
|
117eaf57c3 | ||
|
|
1e69d86b0c | ||
|
|
cc6b66216d | ||
|
|
ca82416dea | ||
|
|
394d5a1ae2 | ||
|
|
371da3da74 | ||
|
|
7814285479 | ||
|
|
53824a2d00 | ||
|
|
9966d846b5 | ||
|
|
8c27a84059 | ||
|
|
388d0fbbc4 | ||
|
|
d71d0a0c20 | ||
|
|
191a0130d2 | ||
|
|
2c3ebaca0c | ||
|
|
7677f848c0 | ||
|
|
c7d2e1f1f4 | ||
|
|
c756191f49 | ||
|
|
e94e386c46 | ||
|
|
b7c782b10a | ||
|
|
49d3f64873 | ||
|
|
be3463c5f9 | ||
|
|
fde9924ee6 | ||
|
|
5b2ea63b14 | ||
|
|
23279a816a | ||
|
|
2c14a9541d | ||
|
|
c474649748 | ||
|
|
fad17cfbb3 | ||
|
|
6322acb88c | ||
|
|
95fc705b5d | ||
|
|
6801552fe6 | ||
|
|
6023d9ba10 | ||
|
|
cc4628d804 | ||
|
|
69286c6909 | ||
|
|
6c114267a5 | ||
|
|
e2ef236f68 | ||
|
|
80c2280ee6 | ||
|
|
44f6b520d5 | ||
|
|
3d71d9e35a | ||
|
|
124f4c4485 | ||
|
|
432d1c7ccb | ||
|
|
4b42e63601 | ||
|
|
f809c716eb | ||
|
|
aebeae9e1b | ||
|
|
564972ba74 | ||
|
|
d0c3cc8b47 | ||
|
|
b5b2a98d5e | ||
|
|
8894cbfe6e | ||
|
|
c58a116513 | ||
|
|
d4df3a0b53 | ||
|
|
9cfa43b8ae | ||
|
|
df6d62fd59 | ||
|
|
2438f4b018 | ||
|
|
8321a8e982 | ||
|
|
1de6adf308 | ||
|
|
a525f45b46 |
@@ -423,6 +423,78 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JaKXz",
|
||||
"name": "Jason Kurian",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2642545?v=4",
|
||||
"profile": "https://about.me/jasonkurian",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dphm",
|
||||
"name": "d pham (they/them)",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1707217?v=4",
|
||||
"profile": "https://github.com/dphm",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AleksandrHovhannisyan",
|
||||
"name": "Aleksandr Hovhannisyan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19352442?v=4",
|
||||
"profile": "https://www.aleksandrhovhannisyan.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jg-rp",
|
||||
"name": "jg-rp",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/72664870?v=4",
|
||||
"profile": "https://github.com/jg-rp",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ameyaapte1",
|
||||
"name": "Ameya Apte",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/16054747?v=4",
|
||||
"profile": "https://github.com/ameyaapte1",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tbdrz",
|
||||
"name": "tbdrz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/50599116?v=4",
|
||||
"profile": "https://github.com/tbdrz",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "santialbo",
|
||||
"name": "Santi Albo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1557563?v=4",
|
||||
"profile": "http://santialbo.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "YahangWu",
|
||||
"name": "Yahang Wu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12295975?v=4",
|
||||
"profile": "https://github.com/YahangWu",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -10,9 +10,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 10
|
||||
node-version: '14'
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:docs
|
||||
- name: Publish
|
||||
uses: JamesIves/[email protected]
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs/public
|
||||
@@ -10,13 +10,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: '14'
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
npm run build:dist
|
||||
- name: Test
|
||||
run: |
|
||||
npm run lint
|
||||
@@ -29,9 +29,4 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
||||
- name: Publish Docs
|
||||
uses: JamesIves/[email protected]
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs/public
|
||||
run: npx semantic-release
|
||||
+215
@@ -1,3 +1,218 @@
|
||||
## [9.39.2](https://github.com/harttle/liquidjs/compare/v9.39.1...v9.39.2) (2022-07-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* expression support Drop.valueOf, fixes [#522](https://github.com/harttle/liquidjs/issues/522) ([4ad383d](https://github.com/harttle/liquidjs/commit/4ad383d9beb57f5683805decc1851778db64aea4))
|
||||
|
||||
## [9.39.1](https://github.com/harttle/liquidjs/compare/v9.39.0...v9.39.1) (2022-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throw ParseError instead of RenderError for invalid assign expression, closes [#519](https://github.com/harttle/liquidjs/issues/519) ([c41a5d5](https://github.com/harttle/liquidjs/commit/c41a5d5babf85ccedbcb3b6f9a3cf5c326f72ae1))
|
||||
|
||||
# [9.39.0](https://github.com/harttle/liquidjs/compare/v9.38.0...v9.39.0) (2022-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* for tag not respecting Drop#valueOf(), fixes [#515](https://github.com/harttle/liquidjs/issues/515) ([c3e51ca](https://github.com/harttle/liquidjs/commit/c3e51caa701fd4449ed5257e23569a37ef12dea2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* iteration protocols ([a19feea](https://github.com/harttle/liquidjs/commit/a19feea7c46fc476139a150bda051f485328afe8))
|
||||
|
||||
# [9.38.0](https://github.com/harttle/liquidjs/compare/v9.37.0...v9.38.0) (2022-07-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* stack overflow on large number of templates, [#513](https://github.com/harttle/liquidjs/issues/513) ([3dc4290](https://github.com/harttle/liquidjs/commit/3dc4290b56265cfafbee8d9836e912d9b8492f90))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* inline comment tag ([#514](https://github.com/harttle/liquidjs/issues/514)) ([2f87708](https://github.com/harttle/liquidjs/commit/2f8770898963e35ac4491f6975a8abd03dc09067))
|
||||
|
||||
# [9.37.0](https://github.com/harttle/liquidjs/compare/v9.36.2...v9.37.0) (2022-04-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* support integer arithmetic for `divided_by`, closes [#465](https://github.com/harttle/liquidjs/issues/465) ([e69a510](https://github.com/harttle/liquidjs/commit/e69a51025efa7dec7d60d0067200a1466988ebbc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* automatic output escaping, closes [#500](https://github.com/harttle/liquidjs/issues/500) ([f88490c](https://github.com/harttle/liquidjs/commit/f88490cd3cd0b5316c43c3ca76837544d99ce0b0))
|
||||
|
||||
## [9.36.2](https://github.com/harttle/liquidjs/compare/v9.36.1...v9.36.2) (2022-04-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* lazy createRequire to allow exceptions being catched, fixes [#497](https://github.com/harttle/liquidjs/issues/497) ([b377dad](https://github.com/harttle/liquidjs/commit/b377dad9e34c3de3e0eedc1ee6f17a0e9d919669))
|
||||
|
||||
## [9.36.1](https://github.com/harttle/liquidjs/compare/v9.36.0...v9.36.1) (2022-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* contains operator does not support Drop, fixes [#492](https://github.com/harttle/liquidjs/issues/492) ([9e024ff](https://github.com/harttle/liquidjs/commit/9e024ff2bcf17e7ac19c718389d4cef39b8a51f7))
|
||||
* responsive header ([a56af6b](https://github.com/harttle/liquidjs/commit/a56af6bbfb5b698db55fbeb87e8e0872688fbe19))
|
||||
* use `createRequire` for ESM, fixes [#334](https://github.com/harttle/liquidjs/issues/334) ([eec381e](https://github.com/harttle/liquidjs/commit/eec381ec72db3858452799b7a3264e240be3044d))
|
||||
|
||||
# [9.36.0](https://github.com/harttle/liquidjs/compare/v9.35.2...v9.36.0) (2022-03-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Access array item by negative index, closes [#486](https://github.com/harttle/liquidjs/issues/486) ([049685b](https://github.com/harttle/liquidjs/commit/049685b9a0271ba03875e24ff2f6c7870cae62a7))
|
||||
* allow strip filter with specified char, closes [#390](https://github.com/harttle/liquidjs/issues/390) ([c503cb2](https://github.com/harttle/liquidjs/commit/c503cb23dfbdd6f146d6dea16a84eab3df1f7aa9))
|
||||
* appropriate error for malformed filters, fixes [#271](https://github.com/harttle/liquidjs/issues/271) ([01014ed](https://github.com/harttle/liquidjs/commit/01014edc491e12d38981045442da9faee598cdf7))
|
||||
|
||||
## [9.35.2](https://github.com/harttle/liquidjs/compare/v9.35.1...v9.35.2) (2022-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* corner case for concat filter without argument, [#481](https://github.com/harttle/liquidjs/issues/481) ([aa95517](https://github.com/harttle/liquidjs/commit/aa955173d4c7adc585e862934429f1f4c5f64969))
|
||||
* export all builtin tags from LiquidJS, [#464](https://github.com/harttle/liquidjs/issues/464) ([33009bb](https://github.com/harttle/liquidjs/commit/33009bb988eb74c58f390992750d91b967cb3428))
|
||||
|
||||
## [9.35.1](https://github.com/harttle/liquidjs/compare/v9.35.0...v9.35.1) (2022-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* some filters throw on nil input, see [#481](https://github.com/harttle/liquidjs/issues/481) ([7dfb620](https://github.com/harttle/liquidjs/commit/7dfb620d30f8818685e1cfb8e7492313a0d036ab))
|
||||
|
||||
# [9.35.0](https://github.com/harttle/liquidjs/compare/v9.34.1...v9.35.0) (2022-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `url_encode` throws on undefined value, fixes [#479](https://github.com/harttle/liquidjs/issues/479) ([ca3240c](https://github.com/harttle/liquidjs/commit/ca3240c2c4d157095d2ebe0024d0c71bc5e435f8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expose all tags/filters and TimezoneDate, closes [#464](https://github.com/harttle/liquidjs/issues/464) ([dab8a29](https://github.com/harttle/liquidjs/commit/dab8a29070b2508f2e6532717b7663966f610bec))
|
||||
|
||||
## [9.34.1](https://github.com/harttle/liquidjs/compare/v9.34.0...v9.34.1) (2022-02-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* array output now join with "" instead of "," ([ab5e245](https://github.com/harttle/liquidjs/commit/ab5e245fba9a0f6936275319d4e5e25aadb0f7c1))
|
||||
* sort filter unexpectedly modifies original array, [#475](https://github.com/harttle/liquidjs/issues/475) ([dbc0497](https://github.com/harttle/liquidjs/commit/dbc049738633b1b6f578d9d20f830b548ba67a22))
|
||||
|
||||
# [9.34.0](https://github.com/harttle/liquidjs/compare/v9.33.1...v9.34.0) (2022-01-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* where-filter null handling ([#457](https://github.com/harttle/liquidjs/issues/457)) ([9da41c8](https://github.com/harttle/liquidjs/commit/9da41c8a37d9c49ee4a16d5bd520fd6ae01c14ec))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `ownPropertyOnly` option to protect prototype, [#454](https://github.com/harttle/liquidjs/issues/454) ([7e99efc](https://github.com/harttle/liquidjs/commit/7e99efc5131e20cf3f59e1fc2c371a15aa4109db))
|
||||
|
||||
## [9.33.1](https://github.com/harttle/liquidjs/compare/v9.33.0...v9.33.1) (2022-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* liquidjs.version on npm package ([53824a2](https://github.com/harttle/liquidjs/commit/53824a2d00f978214fcd83a48118b7900cf97449))
|
||||
|
||||
# [9.33.0](https://github.com/harttle/liquidjs/compare/v9.32.1...v9.33.0) (2022-01-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support `offset:continue`, see [#439](https://github.com/harttle/liquidjs/issues/439) ([8c27a84](https://github.com/harttle/liquidjs/commit/8c27a84059384ae730eb0fa1524df04e122e27a0))
|
||||
* support Jekyll-like include syntax, see [#441](https://github.com/harttle/liquidjs/issues/441) ([388d0fb](https://github.com/harttle/liquidjs/commit/388d0fbbc42fe8cd69faba61c1dc29e9bb5ec2d0))
|
||||
|
||||
## [9.32.1](https://github.com/harttle/liquidjs/compare/v9.32.0...v9.32.1) (2022-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove limit on operator char length ([7677f84](https://github.com/harttle/liquidjs/commit/7677f848c0c57335dd1c4f1e3ec251fbf9b1663f))
|
||||
|
||||
# [9.32.0](https://github.com/harttle/liquidjs/compare/v9.31.0...v9.32.0) (2022-01-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support allow_false for `default` filter, see [#435](https://github.com/harttle/liquidjs/issues/435) ([c756191](https://github.com/harttle/liquidjs/commit/c756191f49f9c2b823048367abfdf0adf2bdb875))
|
||||
|
||||
# [9.31.0](https://github.com/harttle/liquidjs/compare/v9.30.0...v9.31.0) (2021-12-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement `liquid` and `echo` tags, see [#428](https://github.com/harttle/liquidjs/issues/428) ([fde9924](https://github.com/harttle/liquidjs/commit/fde9924ee622efae4c013d2aa01c6d705c8d5f46))
|
||||
|
||||
# [9.30.0](https://github.com/harttle/liquidjs/compare/v9.29.0...v9.30.0) (2021-12-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support jekyll-like include, see [#433](https://github.com/harttle/liquidjs/issues/433) ([23279a8](https://github.com/harttle/liquidjs/commit/23279a816a0582ade7f3b15c1c65c74bc147d134))
|
||||
|
||||
# [9.29.0](https://github.com/harttle/liquidjs/compare/v9.28.6...v9.29.0) (2021-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* customize globals & strictVariables when calling render, see [#432](https://github.com/harttle/liquidjs/issues/432) ([6801552](https://github.com/harttle/liquidjs/commit/6801552fe6829770cbbfdda051731c8b466ed9ec))
|
||||
|
||||
## [9.28.6](https://github.com/harttle/liquidjs/compare/v9.28.5...v9.28.6) (2021-12-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* size filter does not respect Objects, fixes [#385](https://github.com/harttle/liquidjs/issues/385) ([6c11426](https://github.com/harttle/liquidjs/commit/6c114267a526ef764dfd9bd94de199d2932ad91a))
|
||||
* throws when using `preserveTimezones` on Node.js, fixes [#431](https://github.com/harttle/liquidjs/issues/431) ([e2ef236](https://github.com/harttle/liquidjs/commit/e2ef236f68273b72a0b1293b0d13728cdb9aa4b8))
|
||||
|
||||
## [9.28.5](https://github.com/harttle/liquidjs/compare/v9.28.4...v9.28.5) (2021-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* always allow './' and '../' to be relative, even on windows ([44f6b52](https://github.com/harttle/liquidjs/commit/44f6b520d53ba984ecb5fc430d70f698837d1802))
|
||||
|
||||
## [9.28.4](https://github.com/harttle/liquidjs/compare/v9.28.3...v9.28.4) (2021-10-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow `{%render%}` to reassign argument, [#404](https://github.com/harttle/liquidjs/issues/404) ([124f4c4](https://github.com/harttle/liquidjs/commit/124f4c4485270a5fdfca610808a56ecd98d98417))
|
||||
|
||||
## [9.28.3](https://github.com/harttle/liquidjs/compare/v9.28.2...v9.28.3) (2021-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* relative root (by default) yields LookupError, fixes [#419](https://github.com/harttle/liquidjs/issues/419), [#424](https://github.com/harttle/liquidjs/issues/424), also related to [#395](https://github.com/harttle/liquidjs/issues/395) ([aebeae9](https://github.com/harttle/liquidjs/commit/aebeae9e1bbb8472af7788dfd09a08cb6de58e1c))
|
||||
|
||||
## [9.28.2](https://github.com/harttle/liquidjs/compare/v9.28.1...v9.28.2) (2021-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cache ongoing parseFile() calls, fixes [#416](https://github.com/harttle/liquidjs/issues/416) ([8894cbf](https://github.com/harttle/liquidjs/commit/8894cbfe6e0dbad4c07439adcefb6b3e2056be11))
|
||||
|
||||
## [9.28.1](https://github.com/harttle/liquidjs/compare/v9.28.0...v9.28.1) (2021-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hardcoded '/' in normalized `options.fs`, fixes [#412](https://github.com/harttle/liquidjs/issues/412), [#408](https://github.com/harttle/liquidjs/issues/408) ([9cfa43b](https://github.com/harttle/liquidjs/commit/9cfa43b8aee6a980f39d99f1cdb2be730ca21731))
|
||||
|
||||
# [9.28.0](https://github.com/harttle/liquidjs/compare/v9.27.1...v9.28.0) (2021-10-06)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://coveralls.io/github/harttle/liquidjs?branch=master)
|
||||
[](https://github.com/harttle/liquidjs/actions/workflows/check.yml?query=branch%3Amaster)
|
||||
[](https://david-dm.org/harttle/liquidjs)
|
||||
[](https://github.com/harttle/liquidjs/blob/master/LICENSE)
|
||||
[](https://github.com/harttle/liquidjs)
|
||||
|
||||
@@ -16,12 +15,25 @@ A simple, expressive and safe [Shopify][shopify/liquid] / Github Pages compatibl
|
||||
|
||||
<p align="center"><a href="https://liquidjs.com"><img height="155px" width="155px" src="https://liquidjs.com/icon/mstile-310x310.png" alt="logo"></a></p>
|
||||
|
||||
## What's it like?
|
||||
|
||||
Basically there're two types of Liquid syntax: tags enclosed by `{% %}` and outputs enclosed by `{{ }}`. A Liquid template looks like:
|
||||
|
||||
```liquid
|
||||
{% if username %}
|
||||
{{ username | append: ", welcome to LiquidJS!" | capitalize }}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
[A live demo](https://liquidjs.com/playground.html) is also available and here's a [quick tutorial](https://liquidjs.com/tutorials/intro-to-liquid.html) for Liquid syntax.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Install from npm in Node.js:
|
||||
|
||||
```bash
|
||||
npm install --save liquidjs
|
||||
npm install liquidjs
|
||||
```
|
||||
|
||||
Or use the UMD bundle from jsDelivr:
|
||||
@@ -39,12 +51,19 @@ More details, refer to [The Setup Guide][setup].
|
||||
* [react-liquid](https://github.com/aquibm/react-liquid#readme): Liquid templating language component for React
|
||||
* [@11ty/eleventy](https://www.npmjs.com/package/@11ty/eleventy): A simpler static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML.
|
||||
|
||||
## Backers
|
||||
## Financial Support
|
||||
|
||||
If you love LiquidJS or your company is using LiquidJS? Please consider [support us on Open Collective or Patreon][financial-support].
|
||||
|
||||

|
||||
### Backers
|
||||
If you personally like LiquidJS and find it's useful to you, you can become a backer!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/backers.svg?avatarHeight=72"></object>
|
||||
|
||||
### Sponsors
|
||||
If LiquidJS is benefiting your business/company, please sponsor us to make it better!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/sponsors.svg?avatarHeight=72"></object>
|
||||
## Contributors ✨
|
||||
|
||||
Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to these wonderful people:
|
||||
@@ -111,6 +130,16 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
<td align="center"><a href="https://n1ru4l.cloud/"><img src="https://avatars.githubusercontent.com/u/14338007?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Laurin Quast</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=n1ru4l" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mattvague"><img src="https://avatars.githubusercontent.com/u/64985?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Vague</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=mattvague" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bglw"><img src="https://avatars.githubusercontent.com/u/40188355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Liam Bigelow</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=bglw" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://about.me/jasonkurian"><img src="https://avatars.githubusercontent.com/u/2642545?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason Kurian</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=JaKXz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dphm"><img src="https://avatars.githubusercontent.com/u/1707217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>d pham (they/them)</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=dphm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.aleksandrhovhannisyan.com/"><img src="https://avatars.githubusercontent.com/u/19352442?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandr Hovhannisyan</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=AleksandrHovhannisyan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jg-rp"><img src="https://avatars.githubusercontent.com/u/72664870?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jg-rp</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=jg-rp" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ameyaapte1"><img src="https://avatars.githubusercontent.com/u/16054747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ameya Apte</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ameyaapte1" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tbdrz"><img src="https://avatars.githubusercontent.com/u/50599116?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tbdrz</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=tbdrz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://santialbo.com"><img src="https://avatars.githubusercontent.com/u/1557563?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Santi Albo</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=santialbo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/YahangWu"><img src="https://avatars.githubusercontent.com/u/12295975?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yahang Wu</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=YahangWu" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
+4
-4
@@ -6,8 +6,8 @@ Only the latest major version is supported with security updates. It can be chan
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please contact yangjvn@126.com to report a vulnerability.
|
||||
Please contact yangjvn@126.com to report a vulnerability or change request.
|
||||
|
||||
- It will be fixed very soon (maybe within 1 week or couple of days) if that vulnerability obviously affects common use cases.
|
||||
- Otherwise, it'll be scheduled with a priority just like requested features (which is lower than bugs).
|
||||
- If it's declined, you'll receive an email (more oftenly there will be a discuss).
|
||||
- If the vulnerability in question affects common use cases, it will be treated as a bug and fixed very soon (typically within 1 week).
|
||||
- Otherwise, it'll be scheduled in the same priority of feature request (which is lower than bugs).
|
||||
- If the request is declined, you'll receive a reply email anyway (most likely there will be a discussion).
|
||||
|
||||
+4
-6
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
rm -rf docs/source/api docs/source/zh-cn/api
|
||||
typedoc ./src --gitRevision master --out docs/source/api --plugin typedoc-plugin-markdown --theme vuepress
|
||||
typedoc ./src --gitRevision master --out docs/source/api --plugin typedoc-plugin-markdown --ignoreCompilerErrors true --theme vuepress
|
||||
rm docs/source/api/README.md
|
||||
|
||||
for file in $(find docs/source/api -name "*.md"); do
|
||||
@@ -9,8 +11,6 @@ for file in $(find docs/source/api -name "*.md"); do
|
||||
-e 's/\(\]([^_)]*\)\/_/\1\//g' \
|
||||
-e 's/\(\]([^)]*\.\)md/\1html/g' \
|
||||
-e 's/\](_/\](/g' \
|
||||
-e 's/{%/{% raw %}{%{% endraw %}/g' \
|
||||
-e 's/{{/{% raw %}{{{% endraw %}/g' \
|
||||
-e '1 s/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/g' \
|
||||
@@ -18,9 +18,7 @@ for file in $(find docs/source/api -name "*.md"); do
|
||||
-e '1 s/^# \(.*\)/---\ntitle: "\1"\nauto: true\n---/' \
|
||||
$file
|
||||
target=${file/\/_/\/}
|
||||
if [ "$file" != "$target" ]; then
|
||||
mv $file $target
|
||||
fi
|
||||
cat $file | ./bin/escape-nunjucks.js > $target
|
||||
done
|
||||
|
||||
cp -r docs/source/api docs/source/zh-cn/api
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd docs
|
||||
cp ../CHANGELOG.md source/tutorials/changelog.md
|
||||
sed -i \
|
||||
-e 's/{%/{% raw %}{%{% endraw %}/g' \
|
||||
-e 's/{{/{% raw %}{{{% endraw %}/g' \
|
||||
-e '1 s/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/g' \
|
||||
source/tutorials/changelog.md
|
||||
cp source/tutorials/changelog.md source/zh-cn/tutorials/changelog.md
|
||||
|
||||
sed -i '1i ---\ntitle: Changelog\nauto: true\n---\n' source/tutorials/changelog.md
|
||||
sed -i '1i ---\ntitle: 更新日志\nauto: true\n---\n' source/zh-cn/tutorials/changelog.md
|
||||
+3
-13
@@ -1,26 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
./bin/build-contributors.sh
|
||||
./bin/build-apidoc.sh
|
||||
./bin/build-changelog.sh
|
||||
|
||||
cd docs
|
||||
npm ci
|
||||
npm run build
|
||||
cp CNAME public/
|
||||
|
||||
cp ../CHANGELOG.md source/tutorials/changelog.md
|
||||
sed -i \
|
||||
-e 's/{%/{% raw %}{%{% endraw %}/g' \
|
||||
-e 's/{{/{% raw %}{{{% endraw %}/g' \
|
||||
-e '1 s/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/g' \
|
||||
source/tutorials/changelog.md
|
||||
cp source/tutorials/changelog.md source/zh-cn/tutorials/changelog.md
|
||||
|
||||
sed -i '1i ---\ntitle: Changelog\nauto: true\n---\n' source/tutorials/changelog.md
|
||||
sed -i '1i ---\ntitle: 更新日志\nauto: true\n---\n' source/zh-cn/tutorials/changelog.md
|
||||
|
||||
if [ "$HEXO_ALGOLIA_INDEXING_KEY" != "" ]; then
|
||||
npm run index
|
||||
fi
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// In Hexo, content inside markdown code/code fences,
|
||||
// will not go through nunjucks.
|
||||
// So we're escaping special chars outside code only.
|
||||
let backTick = 0
|
||||
process.stdin.on('data', (buffer) => {
|
||||
for (const c of buffer.toString()) {
|
||||
if (c === '`') {
|
||||
backTick++
|
||||
}
|
||||
const shouldEscape = '{%%}'.includes(c) && backTick % 2 === 0
|
||||
if (shouldEscape) {
|
||||
const code = c.charCodeAt(0).toString(16)
|
||||
process.stdout.write(`&#x${code};`)
|
||||
} else process.stdout.write(c)
|
||||
}
|
||||
})
|
||||
+3
-3
@@ -9,8 +9,8 @@ if [ ! -f $FILE_LATEST ]; then
|
||||
curl $URL_LATEST > $FILE_LATEST
|
||||
fi
|
||||
|
||||
if [ ! -f $FILE_LOCAL ]; then
|
||||
BUNDLES=cjs npm run build:dist
|
||||
fi
|
||||
# if [ ! -f $FILE_LOCAL ]; then
|
||||
BUNDLES=cjs npm run build:dist
|
||||
# fi
|
||||
|
||||
exec node benchmark/diff.js $FILE_LOCAL $FILE_LATEST
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# LiquidJS for Node.js
|
||||
|
||||
## Get Started
|
||||
|
||||
```bash
|
||||
cd demo/nodejs
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Liquid } from 'liquidjs'
|
||||
|
||||
const engine = new Liquid({
|
||||
extname: '.liquid',
|
||||
globals: { title: 'LiquidJS Demo' },
|
||||
// root files for `.render()` and `.parse()`
|
||||
root: process.cwd(),
|
||||
// layout files for `{% layout %}`
|
||||
layouts: process.cwd() + '/layouts',
|
||||
// partial files for `{% include %}` and `{% render %}`
|
||||
partials: process.cwd() + '/partials'
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request']
|
||||
}
|
||||
|
||||
async function main () {
|
||||
console.log('==========renderFile===========')
|
||||
const html = await engine.renderFile('todolist', ctx)
|
||||
console.log(html)
|
||||
|
||||
console.log('===========Streamed===========')
|
||||
const tpls = await engine.parseFile('todolist')
|
||||
engine.renderToNodeStream(tpls, ctx)
|
||||
.on('data', data => process.stdout.write(data))
|
||||
.on('end', () => console.log(''))
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head><meta name="title" content="{{title}}"></head>
|
||||
<body>
|
||||
{% block %}{% endblock %}
|
||||
{% render "footer.liquid" %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "liquidjs-demo-nodejs",
|
||||
"description": "Node.js demo for liquidjs",
|
||||
"private": true,
|
||||
"main": "index.mjs",
|
||||
"scripts": {
|
||||
"start": "node index.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"hello-liquid": "^1.0.0",
|
||||
"liquidjs": "^9.28.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<small>{{title}}</small>
|
||||
@@ -0,0 +1 @@
|
||||
<li>{{index}} - {{todo}}</li>
|
||||
@@ -0,0 +1,7 @@
|
||||
{% layout 'html.liquid' %}
|
||||
<h1>{% include "hello-liquid" %}</h1>
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
{% render 'todo.liquid' with todo, index: forloop.index%}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -7,6 +7,6 @@
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"liquidjs": "^9.27.0"
|
||||
"liquidjs": "^9.28.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,5 +21,4 @@ const ctx = {
|
||||
title: 'Welcome to liquidjs!'
|
||||
}
|
||||
|
||||
// console.log('isTruthy:', isTruthy('a string here'));
|
||||
engine.renderFile('todolist', ctx).then(console.log)
|
||||
|
||||
+8
-2
@@ -2,7 +2,9 @@ title: LiquidJS
|
||||
subtitle: "A simple, expressive and safe template engine."
|
||||
description: "LiquidJS is a simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript."
|
||||
author: Harttle
|
||||
language: en
|
||||
language:
|
||||
- en
|
||||
- zh-cn
|
||||
timezone: UTC
|
||||
|
||||
url: https://liquidjs.com
|
||||
@@ -19,8 +21,12 @@ pretty_urls:
|
||||
theme: navy
|
||||
|
||||
highlight:
|
||||
enable: false
|
||||
prismjs:
|
||||
enable: true
|
||||
line_number: false
|
||||
preprocess: true
|
||||
line_number: true
|
||||
tab_replace: ''
|
||||
|
||||
algolia:
|
||||
applicationID: 0X19J927JZ
|
||||
|
||||
Generated
+49
-42
@@ -213,15 +213,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
|
||||
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/algoliasearch": {
|
||||
@@ -346,9 +350,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1252,9 +1256,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -3424,9 +3428,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@@ -3889,9 +3893,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
|
||||
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
|
||||
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
@@ -4678,9 +4685,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/table/node_modules/ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -4728,9 +4735,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "4.6.7",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz",
|
||||
"integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==",
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
|
||||
"integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
|
||||
"dependencies": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
@@ -5293,9 +5300,9 @@
|
||||
"integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8="
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
|
||||
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
@@ -5412,9 +5419,9 @@
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
@@ -6097,9 +6104,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
@@ -7862,9 +7869,9 @@
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.28",
|
||||
@@ -8219,9 +8226,9 @@
|
||||
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE="
|
||||
},
|
||||
"prismjs": {
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
|
||||
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
|
||||
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
@@ -8885,9 +8892,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
@@ -8925,9 +8932,9 @@
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.6.7",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz",
|
||||
"integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==",
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
|
||||
"integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tutorials: /tutorials/setup.html
|
||||
tutorials: /tutorials/intro-to-liquid.html
|
||||
tags: /tags/overview.html
|
||||
filters: /filters/overview.html
|
||||
playground: /playground.html
|
||||
|
||||
@@ -9,6 +9,10 @@ tutorials:
|
||||
advanced:
|
||||
caching: caching.html
|
||||
registeration: register-filters-tags.html
|
||||
access_scope_in_filters: access-scope-in-filters.html
|
||||
parse_parameters: parse-parameters.html
|
||||
render_tag_content: render-tag-content.html
|
||||
sync_and_async: sync-and-async.html
|
||||
whitespace: whitespace-control.html
|
||||
plugins: plugins.html
|
||||
operators: operators.html
|
||||
@@ -28,6 +32,7 @@ filters:
|
||||
capitalize: capitalize.html
|
||||
ceil: ceil.html
|
||||
compact: compact.html
|
||||
concat: concat.html
|
||||
date: date.html
|
||||
default: default.html
|
||||
divided_by: divided_by.html
|
||||
@@ -46,6 +51,7 @@ filters:
|
||||
newline_to_br: newline_to_br.html
|
||||
plus: plus.html
|
||||
prepend: prepend.html
|
||||
raw: raw.html
|
||||
remove: remove.html
|
||||
remove_first: remove_first.html
|
||||
replace: replace.html
|
||||
@@ -65,7 +71,6 @@ filters:
|
||||
truncate: truncate.html
|
||||
truncatewords: truncatewords.html
|
||||
uniq: uniq.html
|
||||
concat: concat.html
|
||||
upcase: upcase.html
|
||||
url_decode: url_decode.html
|
||||
url_encode: url_encode.html
|
||||
@@ -73,21 +78,27 @@ filters:
|
||||
|
||||
tags:
|
||||
overview: overview.html
|
||||
"# (inline comment)": inline_comment.html
|
||||
assign: assign.html
|
||||
capture: capture.html
|
||||
case: case.html
|
||||
comment: comment.html
|
||||
cycle: cycle.html
|
||||
decrement: decrement.html
|
||||
echo: echo.html
|
||||
else: if.html
|
||||
elsif: if.html
|
||||
for: for.html
|
||||
if: if.html
|
||||
include: include.html
|
||||
increment: increment.html
|
||||
layout: layout.html
|
||||
liquid: liquid.html
|
||||
raw: raw.html
|
||||
render: render.html
|
||||
tablerow: tablerow.html
|
||||
unless: unless.html
|
||||
when: case.html
|
||||
|
||||
api:
|
||||
quick_entry:
|
||||
|
||||
@@ -44,4 +44,23 @@ Output
|
||||
2.99
|
||||
```
|
||||
|
||||
## Allowing `false`
|
||||
|
||||
{% since %}v9.32.0{% endsince %}
|
||||
|
||||
To allow variables to return `false` instead of the default value, you can use the `allow_false` parameter.
|
||||
|
||||
Input
|
||||
|
||||
```liquid
|
||||
{% assign display_price = false %}
|
||||
{{ display_price | default: true, allow_false: true }}
|
||||
```
|
||||
|
||||
Output
|
||||
|
||||
```text
|
||||
false
|
||||
```
|
||||
|
||||
[falsy]: ../tutorials/truthy-and-falsy.html
|
||||
|
||||
@@ -26,6 +26,23 @@ Output
|
||||
1.6666666666666667
|
||||
```
|
||||
|
||||
{% note info Integer Arithmetic %}Since JavaScript doesn't differentiate integers and floats, LiquidJS is not capable of integer arithmetic and the return type is always `number`, the string representation of which depends on its value.{% endnote %}
|
||||
In JavaScript, float and integer shares the same type `number` and we cannot tell the difference. For example:
|
||||
|
||||
```javascript
|
||||
// always true
|
||||
5.0 === 5
|
||||
```
|
||||
|
||||
You'll need to pass another `integerArithmetic` argument to enforce integer divide:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ 5 | divided_by: 3, true }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
|
||||
[floor]: ./floor.html
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Filters
|
||||
description: Description and demo for each Liquid filter
|
||||
---
|
||||
|
||||
LiquidJS implements business-logic independent filters that are typically implemented in [shopify/liquid][shopify/liquid]. This section contains the specification and demoes for all the filters implemented by LiquidJS.
|
||||
@@ -8,10 +9,11 @@ There's 40+ filters supported by LiquidJS. These filters can be categorized into
|
||||
|
||||
Categories | Filters
|
||||
--- | ---
|
||||
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs
|
||||
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
|
||||
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, remove, remove_first, truncate, truncatewords
|
||||
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
||||
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse, concat
|
||||
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse, concat, compact, size
|
||||
Date | date
|
||||
Misc | default, json, raw
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: raw
|
||||
---
|
||||
|
||||
{% since %}v9.37.0{% endsince %}
|
||||
|
||||
Liquid filter that directly returns the value of the variable. Useful when [outputEscape](/api/interfaces/liquid_options_.liquidoptions.html#Optional-outputEscape) is set.
|
||||
|
||||
{% note info Auto escape %}
|
||||
By default `outputEscape` is not set. That means LiquidJS output is not escaped by default, thus `raw` filter is not useful until `outputEscape` is set.
|
||||
{% endnote %}
|
||||
|
||||
Input (`outputEscape` not set)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
Input (`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
Input (`outputEscape="json"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
"<"
|
||||
```
|
||||
|
||||
Input (`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" | raw }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
<
|
||||
```
|
||||
@@ -14,5 +14,5 @@ Input
|
||||
|
||||
Output
|
||||
```text
|
||||
|
||||
ants, bugs, bees```
|
||||
ants, bugs, bees
|
||||
```
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"short_name": "LiquidJS",
|
||||
"name": "LiquidJS",
|
||||
"description": "A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon/apple-touch-icon-57x57.png",
|
||||
@@ -23,6 +24,33 @@
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"shortcuts" : [
|
||||
{
|
||||
"name": "Tutorials",
|
||||
"url": "/tutorials/intro-to-liquid.html",
|
||||
"description": "A set of articles describing how to use LiquidJS"
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"url": "/tags/overview.html",
|
||||
"description": "Description and demo for each Liquid tag"
|
||||
},
|
||||
{
|
||||
"name": "Filters",
|
||||
"url": "/filters/overview.html",
|
||||
"description": "Description and demo for each Liquid filter"
|
||||
},
|
||||
{
|
||||
"name": "Playground",
|
||||
"url": "/playground.html",
|
||||
"description": "An online code editor to try out and share Liquid templates"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"url": "/api/classes/liquid_.liquid.html",
|
||||
"description": "TypeScript doc for LiquidJS classes and interfaces"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0f83ce",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
---
|
||||
layout: playground
|
||||
title: Playground
|
||||
description: An online code editor to try out and share Liquid templates
|
||||
---
|
||||
|
||||
+9
-2
@@ -9,6 +9,7 @@ const urlsToCache = [
|
||||
'/fonts/icomoon.ttf?e8nnma'
|
||||
]
|
||||
const blackList = [
|
||||
/chrome-extension:/,
|
||||
/google-analytics.com.*collect/
|
||||
]
|
||||
|
||||
@@ -29,8 +30,14 @@ self.addEventListener('fetch', event => {
|
||||
if (
|
||||
event.request.method !== 'GET' || blackList.some(regex => regex.exec(event.request.url))
|
||||
) return network(event.request)
|
||||
const pn = networkAndSave(event.request)
|
||||
event.respondWith(cache(event.request).then(res => res || pn).catch(() => pn))
|
||||
if (event.request.url.match(/\.githubusercontent\.com/)) {
|
||||
// cache first
|
||||
const pn = networkAndSave(event.request)
|
||||
event.respondWith(cache(event.request).then(res => res || pn).catch(() => pn))
|
||||
} else {
|
||||
// network first
|
||||
event.respondWith(networkAndSave(event.request).catch(() => cache(event.request)))
|
||||
}
|
||||
})
|
||||
|
||||
function cache (req) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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.
|
||||
|
||||
## echo
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign username = 'Bob' %}
|
||||
{% echo username | append: ", welcome to LiquidJS!" | capitalize %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Bob, welcome to LiquidJS!
|
||||
```
|
||||
+62
-20
@@ -66,17 +66,17 @@ Causes the loop to stop iterating when it encounters the `break` tag.
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{% if i == 4 %}
|
||||
{%- if i == 4 -%}
|
||||
{% break %}
|
||||
{% else %}
|
||||
{%- else -%}
|
||||
{{ i }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1 2 3
|
||||
123
|
||||
```
|
||||
|
||||
### continue
|
||||
@@ -86,17 +86,17 @@ Causes the loop to skip the current iteration when it encounters the `continue`
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{% if i == 4 %}
|
||||
{% continue %}
|
||||
{% else %}
|
||||
{%- if i == 4 -%}
|
||||
{%- continue -%}
|
||||
{%- else -%}
|
||||
{{ i }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1 2 3 5
|
||||
1235
|
||||
```
|
||||
|
||||
### forloop
|
||||
@@ -152,15 +152,15 @@ Limits the loop to the specified number of iterations.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- if array = [1,2,3,4,5,6] -->
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{ item }}
|
||||
{{- item -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1 2
|
||||
12
|
||||
```
|
||||
|
||||
### offset
|
||||
@@ -169,15 +169,57 @@ Begins the loop at the specified index.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- if array = [1,2,3,4,5,6] -->
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array offset:2 %}
|
||||
{{ item }}
|
||||
{{- item -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
3 4 5 6
|
||||
3456
|
||||
```
|
||||
|
||||
#### offset:continue
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
Offset value can be `continue` to continue previous loop. For example:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
{% for item in array offset:continue %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
12
|
||||
3456
|
||||
```
|
||||
|
||||
For the same variable name (`"item"` in this case) and same collection (`"array"` in this case), there's one position record. That means you can start a new loop with a different variable name:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
{% for item2 in array offset:continue %}
|
||||
{{- item2 -}}
|
||||
{% endfor%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
12
|
||||
123456
|
||||
```
|
||||
|
||||
### range
|
||||
@@ -187,19 +229,19 @@ Defines a range of numbers to loop through. The range can be defined by both lit
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (3..5) %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
{{- i -}}
|
||||
{% endfor-%}
|
||||
|
||||
{% assign num = 4 %}
|
||||
{% for i in (1..num) %}
|
||||
{{ i }}
|
||||
{{- i -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
3 4 5
|
||||
1 2 3 4
|
||||
345
|
||||
1234
|
||||
```
|
||||
|
||||
### reversed
|
||||
|
||||
@@ -5,7 +5,7 @@ title: Include
|
||||
{% since %}v1.9.1{% endsince %}
|
||||
|
||||
{% note warn Deprecated %}
|
||||
This tag is deprecated, use <a href="./render.html">render</a> tag instead for better encapsulation.
|
||||
This tag is deprecated, use <a href="./render.html">render</a> tag instead, which contains all the features of `include` and provides better encapsulation.
|
||||
{% endnote %}
|
||||
|
||||
## Include a Template
|
||||
@@ -16,7 +16,7 @@ Renders a partial template from the template [roots][root].
|
||||
{% include 'footer.liquid' %}
|
||||
```
|
||||
|
||||
When the [extname][extname] option is set, the above `.liquid` extension can be omitted and writes:
|
||||
If [extname][extname] option is set, the above `.liquid` extension becomes optional:
|
||||
|
||||
```liquid
|
||||
{% include 'footer' %}
|
||||
@@ -44,5 +44,67 @@ A single object can be passed to a snippet by using the `with...as` syntax:
|
||||
|
||||
In the example above, the `product` variable in the partial template will hold the value of `featured_product` in the parent template.
|
||||
|
||||
## Outputs & Filters
|
||||
|
||||
When filename is specified as literal string, it supports Liquid output and filter syntax. Useful when concatenating strings for a complex filename.
|
||||
|
||||
```liquid
|
||||
{% include "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
{% note info Escaping %}
|
||||
In LiquidJS, `"` within quoted string literals need to be escaped by adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like filenames
|
||||
|
||||
Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, where file names are specified as literal string without surrounding quotes. Liquid outputs and filters are also supported within that, for example:
|
||||
|
||||
```liquid
|
||||
{% include prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
This way, you don't need to escape `"` in the filename expression.
|
||||
|
||||
```liquid
|
||||
{% include prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
## Jekyll include
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
[jekyllInclude][jekyllInclude] is used to enable Jekyll-like include syntax. Defaults to `false`, when set to `true`:
|
||||
|
||||
- Filename will be static: `dynamicPartials` now defaults to `false` (instead of `true`). And you can set `dynamicPartials` back to `true`.
|
||||
- Use `=` instead of `:` to separate parameter key-values.
|
||||
- Parameters are under `include` variable instead of current scope.
|
||||
|
||||
For example, the following template:
|
||||
|
||||
```liquid
|
||||
{% include article.html header="HEADER" content="CONTENT" %}
|
||||
```
|
||||
|
||||
`article.html` with following content:
|
||||
|
||||
```liquid
|
||||
<article>
|
||||
<header>{{include.header}}</header>
|
||||
{{include.content}}
|
||||
</article>
|
||||
```
|
||||
|
||||
Note that we're referencing the first parameter by `include.header` instead of `header`. Will output following:
|
||||
|
||||
```html
|
||||
<article>
|
||||
<header>HEADER</header>
|
||||
CONTENT
|
||||
</article>
|
||||
```
|
||||
|
||||
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-dynamicPartials
|
||||
[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "# (inline comment)"
|
||||
---
|
||||
|
||||
{% since %}v9.38.0{% endsince %}
|
||||
|
||||
Add comments to a Liquid template using an inline tag. Text enclosed in an inline comment tag will not be printed.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
Anything inside an inline comment tag will not be printed.
|
||||
{% # this is an inline comment %}
|
||||
But every line must start with a '#'.
|
||||
{%
|
||||
# this is a comment
|
||||
# that spans multiple lines
|
||||
%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Anything inside an inline comment tag will not be printed.
|
||||
But every line must start with a '#'.
|
||||
```
|
||||
|
||||
Inline comments are useful inside <a href="./liquid.html">`liquid`</a> tags too.
|
||||
|
||||
```liquid
|
||||
{% liquid
|
||||
# required args
|
||||
assign product = collection.products.first
|
||||
|
||||
# optional args
|
||||
assign should_show_border = should_show_border | default: true
|
||||
assign should_highlight = should_highlight | default: false
|
||||
%}
|
||||
```
|
||||
|
||||
But they don't work well for commenting out blocks of Liquid code. The <a href="./comment.html">`comment`</a> block tag is the better option when you need to temporarily stop other tags from being executed.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{%- # {% echo 'Welcome to LiquidJS!' %} -%}
|
||||
{% comment %}{% echo 'Welcome to LiquidJS!' %}{% endcomment %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
-%}
|
||||
```
|
||||
+74
-33
@@ -4,21 +4,67 @@ title: Layout
|
||||
|
||||
{% since %}v1.9.1{% endsince %}
|
||||
|
||||
## Using a Layout Template
|
||||
## Using a Layout
|
||||
|
||||
Renders current template inside a layout template from the template [roots][root].
|
||||
Introduce a layout template for the current template to render in. The directory for layout files are defined by [layouts][layouts] or [root][root].
|
||||
|
||||
```liquid
|
||||
{% layout 'footer.liquid' %}
|
||||
// default-layout.liquid
|
||||
Header
|
||||
{% block %}{% endblock %}
|
||||
Footer
|
||||
|
||||
// page.liquid
|
||||
{% layout "default-layout.liquid" %}
|
||||
{% block %}My page content{% endblock %}
|
||||
|
||||
// result
|
||||
Header
|
||||
My page content
|
||||
Footer
|
||||
```
|
||||
|
||||
When the [extname][extname] option is set, the above `.liquid` extension can be omitted and writes:
|
||||
If [extname][extname] option is set, the `.liquid` extension becomes optional:
|
||||
|
||||
```liquid
|
||||
{% layout 'footer' %}
|
||||
{% layout 'default-layout' %}
|
||||
```
|
||||
|
||||
When a partial template is rendered by `layout`, the code inside it can access its caller's variables but its parent cannot access variables defined inside a included template.
|
||||
{% note info Scoping %}
|
||||
When a partial template is rendered by <code>layout</code>, its template have access for its caller's variables but not vice versa. Variables defined in layout will be popped out before control returning to its caller.
|
||||
{% endnote %}
|
||||
|
||||
## Multiple Blocks
|
||||
|
||||
The layout file can contain multiple blocks, each with a specified name. The following snippets yield same result as in the above example.
|
||||
|
||||
```liquid
|
||||
// default-layout.liquid
|
||||
{% block header %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
|
||||
// page.liquid
|
||||
{% layout "default-layout.liquid" %}
|
||||
{% block header %}Header{% endblock %}
|
||||
{% block content %}My page content{% endblock %}
|
||||
{% block footer %}Footer{% endblock %}
|
||||
```
|
||||
|
||||
## Default Block Contents
|
||||
|
||||
In the above layout files, blocks has empty contents. But it's not necessarily be empty, in which case, the block contents in layout files will be used as default templates. The following snippets are also equivalent to the above examples:
|
||||
|
||||
```liquid
|
||||
// default-layout.liquid
|
||||
{% block header %}Header{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block footer %}Footer{% endblock %}
|
||||
|
||||
// page.liquid
|
||||
{% layout "default-layout.liquid" %}
|
||||
{% block content %}My page content{% endblock %}
|
||||
```
|
||||
|
||||
## Passing Variables
|
||||
|
||||
@@ -29,38 +75,33 @@ Variables defined in current template can be passed to a the layout template by
|
||||
{% layout 'name', my_variable: my_variable, my_other_variable: 'oranges' %}
|
||||
```
|
||||
|
||||
## Blocks
|
||||
## Outputs & Filters
|
||||
|
||||
The layout file can contain multiple `block`s which will be populated by the child template (the caller). For example we have a `default-layout.liquid` file with the following contents:
|
||||
When filename is specified as literal string, it supports Liquid output and filter syntax. Useful when concatenating strings for a complex filename.
|
||||
|
||||
```
|
||||
Header
|
||||
{% block content %}My default content{% endblock %}
|
||||
Footer
|
||||
```liquid
|
||||
{% layout "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
And it's called by a `page.liquid` file with `layout` tag:
|
||||
|
||||
```
|
||||
{% layout "default-layout" %}
|
||||
{% block content %}My page content{% endblock %}
|
||||
```
|
||||
|
||||
The render result of `page.liquid` will be :
|
||||
|
||||
```
|
||||
Header
|
||||
My page content
|
||||
Footer
|
||||
```
|
||||
|
||||
{% note tip Block %}
|
||||
<ul>
|
||||
<li>Multiple blocks can be defined within a layout template;</li>
|
||||
<li>The block name is optional when there's only one block.</li>
|
||||
<li>The block contents will fallback to parent's corresponding block if not provided by child template.</li>
|
||||
</ul>
|
||||
{% note info Escaping %}
|
||||
In LiquidJS, `"` within quoted string literals need to be escaped. Adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like Filenames
|
||||
|
||||
Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, file names are specified as literal string. And it also supports Liquid outputs and filters.
|
||||
|
||||
```liquid
|
||||
{% layout prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
This way, you don't need to escape `"` in the filename expression.
|
||||
|
||||
```liquid
|
||||
{% layout prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[layouts]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-layouts
|
||||
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#dynamicPartials
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Liquid
|
||||
---
|
||||
|
||||
{% since %}v9.31.0{% endsince %}
|
||||
|
||||
Encloses multiple tags within one set of delimiters, to allow writing Liquid logic more concisely.
|
||||
|
||||
## liquid
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% liquid
|
||||
assign names = 'Bob, Sally' | split: ', '
|
||||
|
||||
for name in names
|
||||
echo 'Hello, ' | append: name
|
||||
unless forloop.last
|
||||
echo ', '
|
||||
endunless
|
||||
endfor
|
||||
%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Hello, Bob, Hello Sally
|
||||
```
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Tags
|
||||
description: Description and demo for each Liquid tag
|
||||
---
|
||||
|
||||
LiquidJS implements business-logic independent tags that are typically implemented in [shopify/liquid][shopify/liquid]. This section contains the specification and demoes for all the tags implemented by LiquidJS.
|
||||
@@ -10,8 +11,8 @@ Category | Purpose | Tags
|
||||
--- | --- | ---
|
||||
Iteration | iterate over a collection | for, cycle, tablerow
|
||||
Control Flow | control the execution branch of template rendering | if, unless, elsif, else, case, when
|
||||
Variable | define and alter variables | assign, increment, decrement
|
||||
Variable | define and alter variables | assign, increment, decrement, capture, echo
|
||||
File | include another template or extend a layout template | render, include, layout
|
||||
Language | temporarily disable LiquidJS syntax | raw, comment
|
||||
Language | temporarily disable LiquidJS syntax | # (inline comment), raw, comment, liquid
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -4,26 +4,33 @@ title: Render
|
||||
|
||||
{% since %}v9.2.0{% endsince %}
|
||||
|
||||
## Basic Usage
|
||||
## Render a Template
|
||||
|
||||
### Render a Template
|
||||
|
||||
Renders a partial template from the template [root][root]s.
|
||||
Render a partial template from partials directory specified by [partials][partials] or [root][root].
|
||||
|
||||
```liquid
|
||||
// index.liquid
|
||||
Contents
|
||||
{% render 'footer.liquid' %}
|
||||
|
||||
// footer.liquid
|
||||
Footer
|
||||
|
||||
// result
|
||||
Contents
|
||||
Footer
|
||||
```
|
||||
|
||||
When the [extname][extname] option is set, the above `.liquid` extension can be omitted and writes:
|
||||
If [extname][extname] option is set, the above `.liquid` extension becomes optional:
|
||||
|
||||
```liquid
|
||||
{% render 'footer' %}
|
||||
```
|
||||
|
||||
{% note info Variable Scope %}
|
||||
When a partial template is rendered, the code inside it can't access its parent's variables and its variables won't be accessible by its parent. This encapsulation helps make theme code easier to understand and maintain.{% endnote %}
|
||||
When a partial template is rendered, the code inside it can't access its parent's variables and its variables won't be accessible by its parent. This encapsulation makes partials easier to understand and maintain.{% endnote %}
|
||||
|
||||
### Passing Variables
|
||||
## Passing Variables
|
||||
|
||||
Variables defined in parent's scope can be passed to a the partial template by listing them as parameters on the render tag:
|
||||
|
||||
@@ -34,6 +41,32 @@ Variables defined in parent's scope can be passed to a the partial template by l
|
||||
|
||||
[globals][globals] don't need to be passed down. They are accessible from all files.
|
||||
|
||||
## Outputs & Filters
|
||||
|
||||
When filename is specified as literal string, it supports Liquid output and filter syntax. Useful when concatenating strings for a complex filename.
|
||||
|
||||
```liquid
|
||||
{% render "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
{% note info Escaping %}
|
||||
In LiquidJS, `"` within quoted string literals need to be escaped. Adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like Filenames
|
||||
|
||||
Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, file names are specified as literal string. And it also supports Liquid outputs and filters.
|
||||
|
||||
```liquid
|
||||
{% render prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
This way, you don't need to escape `"` in the filename expression.
|
||||
|
||||
```liquid
|
||||
{% render prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### The `with` Parameter
|
||||
@@ -63,4 +96,6 @@ In the example above, the partial template will be rendered once for each `varia
|
||||
[forloop]: ./for.html
|
||||
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[partials]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-partials
|
||||
[globals]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-globals
|
||||
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#dynamicPartials
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Access Scope in Filters
|
||||
---
|
||||
|
||||
As covered in [Register Filters/Tags][register-filters], we can access filter arguments directly in filter function like:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ 1 | add: 2, 3 }}
|
||||
// Output: 6
|
||||
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
|
||||
```
|
||||
|
||||
When it comes to stateful filters, for example transform a URL path to full URL, we'll need to access a `origin` in current scope:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ '/index.html' | fullURL }}
|
||||
// Scope: { origin: "https://liquidjs.com" }
|
||||
// Output: https://liquidjs.com/index.html
|
||||
|
||||
engine.registerFilter('fullURL', function (path) {
|
||||
const origin = this.context.get(['origin'])
|
||||
return new URL(path, origin).toString()
|
||||
})
|
||||
```
|
||||
|
||||
See this JSFiddle: <http://jsfiddle.net/ctj364up/1/>
|
||||
|
||||
{% note warn Arrow Functions %}
|
||||
<code>this</code> in arrow functions is bound to current JavaScript context, you'll need to use <code>function(){}</code> instead of <code>()=>{}</code> syntax to access <code>this.context</code> correctly.
|
||||
{% endnote %}
|
||||
|
||||
[register-filters]: /tutorials/register-filters-tags.html
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Contribution Guideline
|
||||
---
|
||||
|
||||
## Star on Github 👉 [][liquidjs]
|
||||
## 👉👉👉 Star LiquidJS [][liquidjs]
|
||||
|
||||
Starring LiquidJS is the most important and easiest way to support us: boost its rank and expose it to more people, which in turn makes it better.
|
||||
|
||||
@@ -26,13 +26,19 @@ npm test
|
||||
|
||||
## Financial Support
|
||||
|
||||
LiquidJS is Open Source and Free and **without** capitalists support and **without** any ADs. To help it live and thrive, consider contribute on [Open Collective][oc] or [Patreon][pt]. To acknowledge your contribution, your name and avatar will be listed here and on [Github README][liquidjs].
|
||||
LiquidJS is Open Source and Free. To help it live and thrive, please consider contribute on [Open Collective][oc] or [Patreon][pt]. To acknowledge your contribution, your name and avatar will be listed here, on LiquidJS [homepage](https://liquidjs.com/) and on [Github README][liquidjs].
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/tiers/backer.svg?avatarHeight=72"></object>
|
||||
### Backers
|
||||
If you personally like LiquidJS and find it's useful to you, you can become a backer!
|
||||
|
||||
[](https://www.patreon.com/bePatron?u=32321060)
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/backers.svg?avatarHeight=72"></object>
|
||||
|
||||
[oc]: https://opencollective.com/liquidjs/
|
||||
### Sponsors
|
||||
If LiquidJS is benefiting your business/company, please sponsor us to make it better!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/sponsors.svg?avatarHeight=72"></object>
|
||||
|
||||
[oc]: https://opencollective.com/liquidjs/contribute/backer-ii-10666/checkout
|
||||
[pt]: https://www.patreon.com/harttle
|
||||
[shopify/liquid]: https://shopify.github.io/liquid/
|
||||
[caniuse-promises]: http://caniuse.com/#feat=promises
|
||||
|
||||
@@ -2,7 +2,25 @@
|
||||
title: Differences with Shopify/liquid
|
||||
---
|
||||
|
||||
All filters and tags in [shopify/liquid](https://github.com/Shopify/liquid) are supposed to be built in LiquidJS, but not those business-logic specific tags/filters which are typically from Shopify themes (see [Plugins List][plugins] in case you're looking for them and feel free to add yours to the list). Though being compatible the Ruby version is one of our priorities, there are still some differences:
|
||||
## Compatibility
|
||||
|
||||
Being compatible the Ruby version is one of our priorities. Liquid language is originally [implemented in Ruby][ruby-liquid] and used by Shopify, Jekyll and thus Github Pages, as you can see it's one of the most popular template engines in Ruby. There're lots of people using LiquidJS to serve their templates originally written for Shopify themes and Jekyll sites.
|
||||
|
||||
So "being compatible" means serve developers from Shopify and Jekyll well:
|
||||
|
||||
- **Well-formed Liquid template should work just fine in LiquidJS**. For example, `forloop.index` should be 1-indexed, `nil` should be rendered as empty string rather than `undefined`, etc. Although some features (e.g. [#236][#236]) are not feasible in JavaScript, at least we're trying to implement all the semantics of Liquid language.
|
||||
- **All filters and tags in [shopify/liquid][ruby-liquid] are supposed to be built in LiquidJS**. But not those business-logic specific tags/filters typically defined by Shopify platform. Those features should be maintained as [plugins][plugins]. For filters/tags that are not business-logic specific, like `{% layout %}`, and extremely useful, feel free to file an issue.
|
||||
|
||||
In the meantime, it's now implemented in JavaScript, that means it has to be more powerful:
|
||||
|
||||
* **Async as first-class citizen**. Filters and tags can be implemented asynchronously by return a `Promise`.
|
||||
* **Also can be sync**. For scenarios that are not I/O intensive, render synchronously can be much faster. You can call synchronous APIs like `.renderSync()` as long as all the filters and tags in template support to be rendered synchronously. All builtin filters/tags support both sync and async render.
|
||||
* **[Abstract file system][afs]**. Along with async feature, LiquidJS can be used to serve templates stored in Databases [#414][#414], on remote HTTP server [#485][#485], and so on.
|
||||
* **Additional tags and filters** like `layout` and `json`.
|
||||
|
||||
## Differences
|
||||
|
||||
Though we're trying to be compatible with the Ruby version, there are still some differences:
|
||||
|
||||
* 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.
|
||||
@@ -11,11 +29,20 @@ All filters and tags in [shopify/liquid](https://github.com/Shopify/liquid) are
|
||||
* 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.
|
||||
* Sort stability. The [sort][sort] stability is also not defined in both shopify/liquid and LiquidJS, but it's [considered stable][stable-sort] for LiquidJS in Node.js 12+ and Google Chrome 70+.
|
||||
* Trailing unmatched characters inside filters are allowed in shopify/liquid but not in LiquidJS. It means filter arguments without a colon like `{%raw%}{{ "a b" | split " "}}{%endraw%}` will throw an error in LiquidJS. This is intended to improve Liquid usability, see [#208][#208] and [#212][#212].
|
||||
* LiquidJS has additional tags: [layout][layout] and corresponding `block` tag.
|
||||
* LiquidJS has additional filters: [json][json].
|
||||
|
||||
[layout]: https://liquidjs.com/tags/layout.html
|
||||
[json]: https://liquidjs.com/filters/json.html
|
||||
[#26]: https://github.com/harttle/liquidjs/pull/26
|
||||
[#59]: https://github.com/harttle/liquidjs/issues/59
|
||||
[#208]: https://github.com/harttle/liquidjs/issues/208
|
||||
[#212]: https://github.com/harttle/liquidjs/issues/212
|
||||
[#236]: https://github.com/harttle/liquidjs/issues/236
|
||||
[#414]: https://github.com/harttle/liquidjs/discussions/414
|
||||
[#485]: https://github.com/harttle/liquidjs/discussions/485
|
||||
[sort]: https://liquidjs.com/filters/sort.html
|
||||
[stable-sort]: https://v8.dev/features/stable-sort
|
||||
[plugins]: ./plugins.html#Plugin-List
|
||||
[ruby-liquid]: https://github.com/Shopify/liquid
|
||||
[afs]: https://liquidjs.com/tutorials/render-file.html#Abstract-File-System
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Introduction to Liquid Template Language
|
||||
title: The Liquid Template Language
|
||||
describe: A short introduction to the Liquid template language and some simple demos.
|
||||
---
|
||||
|
||||
LiquidJS is a simple, expressive and safe [Shopify][shopify/liquid] / Github Pages compatible template engine in pure JavaScript. The purpose of this repo is to provide a standard Liquid implementation for the JavaScript community. Liquid is originally implemented in Ruby and used by Github Pages, Jekyll and Shopify, see [Differences with Shopify/liquid][diff].
|
||||
@@ -9,6 +10,10 @@ LiquidJS syntax is relatively simple. There're 2 types of markups in LiquidJS:
|
||||
- **Tags**. A tag consists of a tag name and optional arguments wrapped between `{%raw%}{%{%endraw%}` and `%}`.
|
||||
- **Outputs**. An output consists of a value and a list of filters, which is optional, wrapped between `{%raw%}{{{%endraw%}` and `}}`.
|
||||
|
||||
{% note info Live Demo %}
|
||||
Before going into the details, here's a live demo to play around: <https://liquidjs.com/playground.html>.
|
||||
{% endnote %}
|
||||
|
||||
## Outputs
|
||||
|
||||
**Outputs** are used to output variables, which can be transformed by filters, into HTML. The following template will insert the value of `username` into the input's value:
|
||||
|
||||
@@ -52,6 +52,29 @@ Setting `dynamicPartials: false`, LiquidJS will try to include the file named `f
|
||||
{% note warn Common Pitfall %}
|
||||
LiquidJS defaults this option to <code>true</code> to be compatible with shopify/liquid, but if you're from <a href="https://github.com/11ty/eleventy" target="_blank">eleventy</a> it's set to <code>false</code> by default (see <a href="https://www.11ty.dev/docs/languages/liquid/#quoted-include-paths" target="_blank">Quoted Include Paths</a>) which I believe is trying to be compatible with Jekyll.{% endnote %}
|
||||
|
||||
## Jekyll include
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
[jekyllInclude][jekyllInclude] is used to enable Jekyll-like include syntax. Defaults to `false`, when set to `true`:
|
||||
|
||||
- Filename will be static: `dynamicPartials` now defaults to `false` (instead of `true`). And you can set `dynamicPartials` back to `true`.
|
||||
- Use `=` instead of `:` to separate parameter key-values.
|
||||
- Parameters are under `include` variable instead of current scope.
|
||||
|
||||
For example in the following template, `name.html` is not quoted, `header` and `"HEADER"` are separated by `=`, and the `header` parameter is referenced by `include.header`. More details please check out [include][include].
|
||||
|
||||
```liquid
|
||||
// entry template
|
||||
{% include article.html header="HEADER" content="CONTENT" %}
|
||||
|
||||
// article.html
|
||||
<article>
|
||||
<header>{{include.header}}</header>
|
||||
{{include.content}}
|
||||
</article>
|
||||
```
|
||||
|
||||
## extname
|
||||
|
||||
**extname** defines the default extension name to be appended into filenames if the filename has no extension name. Defaults to `''` which means it's disabled by default. By setting it to `.liquid`:
|
||||
@@ -105,6 +128,8 @@ it defaults to false. For example, when set to true, a blank string would evalu
|
||||
|
||||
**lenientIf** modifies the behavior of `strictVariables` to allow handling optional variables. If set to `true`, an undefined variable will *not* cause an exception in the following two situations: a) it is the condition to an `if`, `elsif`, or `unless` tag; b) it occurs right before a `default` filter. Irrelevant if `strictVariables` is not set. Defaults to `false`.
|
||||
|
||||
**ownPropertyOnly** hides scope variables from prototypes, useful when you're passing a not sanitized object into LiquidJS or need to hide prototypes from templates. Defaults to `false`.
|
||||
|
||||
{% note info Non-existent Tags %}
|
||||
Non-existent tags always throw errors during parsing and this behavior can not be customized.
|
||||
{% endnote %}
|
||||
@@ -123,3 +148,4 @@ Parameter orders are ignored by default, for ea `{% for i in (1..8) reversed lim
|
||||
[layout]: ../tags/layout.html
|
||||
[wc]: ./whitespace-control.html
|
||||
[intro]: ./intro-to-liquid.html
|
||||
[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Parse Parameters
|
||||
---
|
||||
|
||||
## Access Raw Parameters
|
||||
|
||||
As covered in [Register Filters/Tags][register-tags], tag parameters is available on `tagToken.args` as a raw string. For example:
|
||||
|
||||
```javascript
|
||||
// Usage: {% random foo bar coo %}
|
||||
// Output: "foo", "bar" or "coo"
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// tagToken.args === "foo bar coo"
|
||||
this.items = tagToken.args.split(' ')
|
||||
},
|
||||
render(context, emitter) {
|
||||
// get a random index
|
||||
const index = Math.floor(this.items.length * Math.random())
|
||||
// output that item
|
||||
emitter.write(this.items[index])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Here's a JSFiddle version: <http://jsfiddle.net/ctj364up/2/>
|
||||
|
||||
## Parse Parameters as Values
|
||||
|
||||
Sometimes we need more dynamic tags and want to pass values to the custom tag instead of static strings. Variables in LiquidJS can be literal (string, number, etc.) or a variable from current context scope.
|
||||
|
||||
The following modified template also contains 3 values to random from, but they're values instead of static strings. The first one is string literal, second one is an identifier, third one is a property access sequence containing two identifiers.
|
||||
|
||||
```liquid
|
||||
{% random "foo" bar obj.coo %}
|
||||
```
|
||||
|
||||
It can be tricky to parse all these cases manually, but there's a [Tokenizer][Tokenizer] class in LiquidJS you can make use of.
|
||||
|
||||
```javascript
|
||||
const { Liquid, Tokenizer, evalToken } = require('liquidjs')
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
const tokenizer = new Tokenizer(tagToken.args)
|
||||
this.items = []
|
||||
while (!tokenizer.end()) {
|
||||
// here readValue() returns a LiteralToken or PropertyAccessToken
|
||||
this.items.push(tokenizer.readValue())
|
||||
}
|
||||
},
|
||||
* render(context, emitter) {
|
||||
const index = Math.floor(this.items.length * Math.random())
|
||||
const token = this.items[index]
|
||||
// in LiquidJS, we use yield to wait for async call
|
||||
const value = yield evalToken(token, context)
|
||||
emitter.write(value)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Calling this tag in scope `{ bar: "bar", obj: { coo: "coo" } }` yields exactly the same result as the first example. See this JSFiddle: <http://jsfiddle.net/ctj364up/3/>
|
||||
|
||||
{% note info Async ans Promises %}
|
||||
Async calls in LiquidJS are implemented by generators directly, for we can call generators in synchronous manner so this tag implementation is also valid for `renderSync()`, `parseAndRenderSync()`, `renderFileSync()`. If you need to await a promise in tag implementation, simply replace `await somePromise` with `yield somePromise` and keep `* render()` instead of `async render()` will do the trick. See <a href="/tutorials/sync-and-async.html">Sync and Async</a> for more details.
|
||||
{% endnote %}
|
||||
|
||||
## Parse Key-Value Pairs as Named Parameters
|
||||
|
||||
Named parameters become very handy when there're optional parameters or lots of parameters, in which case the order of parameters is not important. This is exactly what [Hash][Hash] class is invented for.
|
||||
|
||||
```liquid
|
||||
{% random from:2, to:max %}
|
||||
```
|
||||
|
||||
In the above example, we're trying to generate a random number in the range [2, max]. We'll use `Hash` to parse `from` and `to` parameters.
|
||||
|
||||
```javascript
|
||||
const { Liquid, Hash } = require('liquidjs')
|
||||
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// parse the parameters structure into `this.args`
|
||||
this.args = new Hash(tagToken.args)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
// evaluate the parameters in `context`
|
||||
const {from, to} = yield this.args.render(context)
|
||||
const length = to - from + 1
|
||||
const value = from + Math.floor(length * Math.random())
|
||||
emitter.write(value)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Rendering `{% random from:2, to:max %}` in scope `{ max: 10 }` will generate a random number in the range [2, 10]. See this JSFiddle: <http://jsfiddle.net/ctj364up/4/>
|
||||
|
||||
|
||||
[register-tags]: /tutorials/register-filters-tags.html
|
||||
[Tokenizer]: /api/classes/parser_tokenizer_.tokenizer.html
|
||||
[Hash]: /api/classes/template_tag_hash_.hash.html
|
||||
@@ -28,7 +28,7 @@ color: 'yellow' shape: 'square'
|
||||
More details please refer to the [render](../tags/render.html) tag.
|
||||
|
||||
{% note tip The ".liquid" Extension %}
|
||||
The ".liquid" extension in <code>layout</code>, <code>render</code> and <code>include</code> an be omitted if Liquid instance is created using `extname: ".liquid"` option. See <a href="./options.html#extname">the extname option</a> for details.
|
||||
The ".liquid" extension in <code>layout</code>, <code>render</code> and <code>include</code> can be omitted if Liquid instance is created using `extname: ".liquid"` option. See <a href="./options.html#extname">the extname option</a> for details.
|
||||
{% endnote %}
|
||||
|
||||
## Layout Templates (Extends)
|
||||
|
||||
@@ -61,4 +61,4 @@ function disabledFilter(name) {
|
||||
}
|
||||
}
|
||||
engine.registerFilter('plus', disabledFilter('plus'));
|
||||
```
|
||||
```
|
||||
@@ -68,7 +68,7 @@ Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/t
|
||||
|
||||
## Abstract File System
|
||||
|
||||
LiquidJS defines an abstract file system interface in [src/fs/ifs.ts][ifs] and the default implementation is [src/fs/node.ts][fs-node] for Node.js and [src/fs/browser.ts][fs-browser] for the browser bundle.
|
||||
LiquidJS defines an abstract file system interface in [src/fs/fs.ts][ifs] and the default implementation is [src/fs/node.ts][fs-node] for Node.js and [src/fs/browser.ts][fs-browser] for the browser bundle.
|
||||
|
||||
The `Liquid` constructor provides a [fs][fs] option to specify the file system implementation. It's supposed to be used to define customized template fetching logic, i.e. fetch template from a database table, like:
|
||||
|
||||
@@ -78,14 +78,17 @@ var engine = new Liquid({
|
||||
readFileSync (file) {
|
||||
return db.model('Template').findByIdSync(file).text
|
||||
},
|
||||
await readFile (file) {
|
||||
async readFile (file) {
|
||||
const template = await db.model('Template').findById(file)
|
||||
return template.text
|
||||
},
|
||||
existsSync () {
|
||||
return true
|
||||
},
|
||||
await exists () {
|
||||
exists () {
|
||||
return true
|
||||
},
|
||||
contains () {
|
||||
return true
|
||||
},
|
||||
resolve(root, file, ext) {
|
||||
@@ -95,8 +98,10 @@ var engine = new Liquid({
|
||||
});
|
||||
```
|
||||
|
||||
{% note warn Path Traversal Vulnerability %}The default value of <code>contains()</code> always returns true. That means when specifying an abstract file system, you'll need to provide a proper <code>contains()</code> to avoid expose such vulnerabilities.{% endnote %}
|
||||
|
||||
[fs]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-fs
|
||||
[ifs]: https://github.com/harttle/liquidjs/blob/master/src/fs/ifs.ts
|
||||
[ifs]: https://github.com/harttle/liquidjs/blob/master/src/fs/fs.ts
|
||||
[fs-node]: https://github.com/harttle/liquidjs/blob/master/src/fs/node.ts
|
||||
[fs-browser]: https://github.com/harttle/liquidjs/blob/master/src/fs/browser.ts
|
||||
[layout]: https://help.shopify.com/en/themes/liquid/tags/theme-tags#layout
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: Render Tag Content
|
||||
---
|
||||
|
||||
Custom tags can have content template and can be nested. This article describes how to implement custom tags that consists of a *begin tag*, an *end tag*, and template content between them.
|
||||
|
||||
## Render Tag Content
|
||||
|
||||
We'll start with a simple tag `wrap` which wraps its content into a `<div class="wrapper"></div>` element:
|
||||
|
||||
```liquid
|
||||
{% wrap %}
|
||||
{{ "hello world!" | capitalize }}
|
||||
{% endwrap %}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```html
|
||||
<div class='wrapper'>
|
||||
Hello world!
|
||||
</div>
|
||||
```
|
||||
|
||||
Firstly, [register][register-tags] a tag with name `wrap` and parse the content into `this.tpls`. Here in `parse(tagToken, remainTokens)`,
|
||||
|
||||
- `tagToken` is current token `{% wrap %}`, and
|
||||
- `remainTokens` is an array of all tokens following `{% wrap %}` until the end of this template file.
|
||||
|
||||
Basically, what we need to do is take/`.shift()` enough tags from `remainTokens` until we got a `endwrap` token (the name can be arbitrary, but in convention, we need it to be `endwrap`). And if there's no `endwrap` until the end of template file, we need to throw an tag-not-closed `Error`.
|
||||
|
||||
```javascript
|
||||
engine.registerTag('wrap', {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
let closed = false
|
||||
while(remainTokens.length) {
|
||||
let token = remainTokens.shift()
|
||||
// we got the end tag! stop taking tokens
|
||||
if (token.name === 'endwrap') {
|
||||
closed = true
|
||||
break
|
||||
}
|
||||
// parse token into template
|
||||
// parseToken() may consume more than 1 tokens
|
||||
// e.g. {% if %}...{% endif %}
|
||||
let tpl = this.liquid.parser.parseToken(token, remainTokens)
|
||||
this.tpls.push(tpl)
|
||||
}
|
||||
if (!closed) throw new Error(`tag ${tagToken.getText()} not closed`)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
emitter.write("<div class='wrapper'>")
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
emitter.write("</div>")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`.renderTemplates()` can be async, we need `yield` to wait it complete. More details on async in LiquidJS, please refer to [Sync and Async][async]. Other parts of `render()` method is quite straightforward. Here's a JSFiddle version: <http://jsfiddle.net/por0zcn1/3/>
|
||||
|
||||
## Using ParseStream
|
||||
|
||||
When it comes to complex tags like [for][for] and [if][if], the `parse()` can be very complicated. There's a [ParseStream][ParseStream] utility to organize the `parse()` in event-based style. Following is a re-written `parse()` using `ParseStream` and does exactly the same as above example.
|
||||
|
||||
```javascript
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
this.liquid.parser.parseStream(remainTokens)
|
||||
.on('template', tpl => this.tpls.push(tpl))
|
||||
// note that we cannot use arrow function because we need `this`
|
||||
.on('tag:endwrap', function () { this.stop() })
|
||||
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||
.start()
|
||||
}
|
||||
```
|
||||
|
||||
Here's a JSFiddle version: <http://jsfiddle.net/por0zcn1/4/>. For simplicity, the following examples are implemented using `ParseStream`.
|
||||
|
||||
## Manipulate the Context
|
||||
|
||||
The `wrap` tag above doesn't seem to be very useful, without using that tag we can render the content anyway. Now we're going to implement a `repeat` tag to render the content 2 times (we can also add a [parameter][parameter] to render arbitrary times).
|
||||
|
||||
```liquid
|
||||
{% repeat %}
|
||||
{{ repeat.i }}. {{ "hello world!" | capitalize }}
|
||||
{% endrepeat %}`
|
||||
```
|
||||
|
||||
Expected outputs:
|
||||
|
||||
```html
|
||||
1. Hello world!
|
||||
2. Hello world!
|
||||
```
|
||||
|
||||
As you've noticed, there's an additional `repeat.i` in the context of `repeat`. That is implemented by manipulating the *Context*.
|
||||
|
||||
{% note info Context %}
|
||||
<em>Context</em> defines the value of each variable in Liquid template. In LiquidJS, a `Context` consists of a stack of `Scope`s. A *Scope* is a plain object like the one specified in `engine.render(tpl, scope)`.
|
||||
{% endnote %}
|
||||
|
||||
Each time we enter a new *Context*, we need to push a new *Scope*. And when we finish rendering and exit the *Context*, we pop the *Scope* from the *Context*. As you can see in the following implementation:
|
||||
|
||||
```javascript
|
||||
engine.registerTag('repeat', {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
this.liquid.parser.parseStream(remainTokens)
|
||||
.on('template', tpl => this.tpls.push(tpl))
|
||||
.on('tag:endrepeat', function () { this.stop() })
|
||||
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||
.start()
|
||||
},
|
||||
* render(context, emitter) {
|
||||
const repeat = { i: 1 }
|
||||
context.push({ repeat })
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
repeat.i++
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
context.pop()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `parse()` is exactly the same as `wrap` tag, we repeat the content simply by calling `.renderTemplates(this.tpls)` twice during `render()`. Here's the JSFiddle: <http://jsfiddle.net/por0zcn1/2/>
|
||||
|
||||
{% note warn Use Push & Pop in Pairs %}
|
||||
`context.push()` and `context.pop()` have to be used in pairs. Failing to `pop()` the *Scope* you pushed will leak the *Scope* to latter templates and may corrupt the *Context* stack.
|
||||
{% endnote %}
|
||||
|
||||
[register-tags]: ./register-filters-tags.html
|
||||
[async]: ./sync-and-async.html
|
||||
[for]: ../tags/for.html
|
||||
[if]: ../tags/if.html
|
||||
[ParseStream]: ../api/classes/parser_parse_stream_.parsestream.html
|
||||
[parameter]: ./parse-parameters.html
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Sync and Async
|
||||
---
|
||||
|
||||
LiquidJS supports both sync and async evaluate, and can be used with Promises. To reuse the same set of tag/filter implementations in both sync and async, LiquidJS tags are implemented as generators.
|
||||
|
||||
## Sync and Async API
|
||||
|
||||
All major methods on [Liquid][Liquid] supports both sync and async. These methods return Promises:
|
||||
|
||||
- `render()`
|
||||
- `renderFile()`
|
||||
- `parseFile()`
|
||||
- `parseAndRender()`
|
||||
- `evalValue()`
|
||||
|
||||
The synchronous version of methods contains a `Sync` suffix:
|
||||
|
||||
- `renderSync()`
|
||||
- `renderFileSync()`
|
||||
- `parseFileSync()`
|
||||
- `parseAndRenderSync()`
|
||||
- `evalValueSync()`
|
||||
|
||||
## Implement Sync-Compatible Tags
|
||||
|
||||
### Requirements
|
||||
|
||||
All builtin tags are *sync-compatible* and safe to use for both sync and async APIs. To make your custom tag *sync-compatible*, you'll need to avoid return a `Promise`. That means the `render(context, emitter)`:
|
||||
|
||||
- Should not directly `return <Promise>`, and
|
||||
- Should not be declared as `async`.
|
||||
|
||||
{% note info Non Sync-Compatible Tags %}
|
||||
Non <em>sync-compatible</em> tags are also valid tags, will work just fine for asynchronous API calls. When called synchronously, tags that return a <code>Promise</code> will be rendered as <code>[object Promise]</code>.
|
||||
{% endnote %}
|
||||
|
||||
### Await Promises
|
||||
But LiquidJS is Promise-friendly, right? You can still call Promise-based functions and wait for that Promise within tag implementations. Just replace `await` with `yield` and keep `* render()` instead of `async render()`. e.g.
|
||||
|
||||
```typescript
|
||||
import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
|
||||
|
||||
// Usage: {% upper "alice" %}
|
||||
// Output: ALICE
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
this.str = tagToken.args
|
||||
},
|
||||
* render: function(ctx: Context) {
|
||||
// _evalValue will behave synchronously when called by synchronous API
|
||||
// in which case `ctx.sync == true`
|
||||
var str = yield this.liquid._evalValue(this.str, ctx)
|
||||
return str.toUpperCase()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See this JSFiddle: <http://jsfiddle.net/ctj364up/6/>.
|
||||
|
||||
## Async-only Tags
|
||||
|
||||
For tags that intended to be used only by async API, or those cannot be implemented synchronously, there's no difference between using generator-base syntax or async syntax. I'll call them *async-only tags*.
|
||||
|
||||
For example, if the above `this.liquid._evalValue()` doesn't respect `ctx.sync` and always returns a Promise, even if the tag is implemented using `* render()` and `yield this.liquid._evalValue()`, it will be rendered as `<object Promise>` anyway.
|
||||
|
||||
For *async-only tags*, you can use async syntax at will. Be careful some APIs in LiquidJS return Promises and others return Generators. You'll need [toPromise][toPromise] API to convert a Generator to a Promise, for example:
|
||||
|
||||
```typescript
|
||||
import { TagToken, Context, Emitter, TopLevelToken, toPromise } from 'liquidjs'
|
||||
|
||||
// Usage: {% upper "alice" %}
|
||||
// Output: ALICE
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
this.str = tagToken.args; // name
|
||||
},
|
||||
render: async function(ctx: Context) {
|
||||
var str = await toPromise(this.liquid._evalValue(this.str, ctx));
|
||||
// Or use the alternate API that returns a Promise
|
||||
// var str = await this.liquid.evalValue(this.str, ctx);
|
||||
return str.toUpperCase()
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
See this JSFiddle: <http://jsfiddle.net/ctj364up/5/>.
|
||||
|
||||
[Liquid]: /api/classes/liquid_.liquid.html
|
||||
[toPromise]: /api/modules/liquid_.html#toPromise
|
||||
@@ -41,7 +41,7 @@ Alternatively, LiquidJS provides these per engine options to enable whitespace c
|
||||
|
||||
* `trimTagLeft`
|
||||
* `trimTagRight`
|
||||
* `trimValueRight`
|
||||
* `trimValueLeft`
|
||||
* `trimValueRight`
|
||||
|
||||
[LiquidJS][liquidjs] will **NOT** trim any whitespace by default, aka. above options all default to `false`. For details of these options, see the [options][options].
|
||||
@@ -53,4 +53,4 @@ In greedy mode (enabled by the [greedy option][greedy]), all consecutive whitesp
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
[liquidjs]: https://github.com/harttle/liquidjs
|
||||
[options]: ../api/interfaces/liquid_options_.liquidoptions.html
|
||||
[greedy]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-greedy
|
||||
[greedy]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-greedy
|
||||
|
||||
@@ -42,4 +42,23 @@ title: default
|
||||
2.99
|
||||
```
|
||||
|
||||
## 允许 `false`
|
||||
|
||||
{% since %}v9.32.0{% endsince %}
|
||||
|
||||
为了允许让 `false` 直接输出而不是用默认值,可以用 `allow_false` 参数。
|
||||
|
||||
输入
|
||||
|
||||
```liquid
|
||||
{% assign display_price = false %}
|
||||
{{ display_price | default: true, allow_false: true }}
|
||||
```
|
||||
|
||||
输出
|
||||
|
||||
```text
|
||||
false
|
||||
```
|
||||
|
||||
[falsy]: ../tutorials/truthy-and-falsy.html
|
||||
|
||||
@@ -26,6 +26,23 @@ title: divided_by
|
||||
1.6666666666666667
|
||||
```
|
||||
|
||||
{% note info Integer Arithmetic %}Since JavaScript doesn't differentiate integers and floats, LiquidJS is not capable of integer arithmetic and the return type is always `number`, the string representation of which depends on its value.{% endnote %}
|
||||
在 JavaScript 里数字没有浮点和整数的区分,它们的类型都是 `number`:
|
||||
|
||||
```javascript
|
||||
// always true
|
||||
5.0 === 5
|
||||
```
|
||||
|
||||
因此如果需要做整数运算,需要传入额外的 `integerArithmetic` 参数:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ 5 | divided_by: 3, true }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
|
||||
[floor]: ./floor.html
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 过滤器
|
||||
description: 每个 Liquid 过滤器的描述和示例
|
||||
---
|
||||
|
||||
LiquidJS 支持 Liquid 语法中具体业务无关的过滤器,基本上 [shopify/liquid 核心][shopify/liquid] 支持的 LiquidJS 都支持。这部分包含了所有 LiquidJS 支持的过滤器的文档和使用示例。
|
||||
@@ -8,10 +9,11 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类:
|
||||
|
||||
类别 | 过滤器
|
||||
--- | ---
|
||||
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs
|
||||
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
|
||||
字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, remove, remove_first, truncate, truncatewords
|
||||
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
||||
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse
|
||||
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse, concat, compact, size
|
||||
日期 | date
|
||||
其他 | default, json
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: raw
|
||||
---
|
||||
|
||||
{% since %}v9.37.0{% endsince %}
|
||||
|
||||
直接返回变量的值。配合 [outputEscape](/api/interfaces/liquid_options_.liquidoptions.html#Optional-outputEscape) 参数使用。
|
||||
|
||||
{% note info 自动转义 %}
|
||||
默认情况下 `outputEscape` 为 `undefined`,这意味着 LiquidJS 输出不会默认转义,因此这时使用 `raw` 没有意义。
|
||||
{% endnote %}
|
||||
|
||||
输入(未设置 `outputEscape`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
输入(`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
输入(`outputEscape="json"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
"<"
|
||||
```
|
||||
|
||||
输入(`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" | raw }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"short_name": "LiquidJS",
|
||||
"name": "LiquidJS",
|
||||
"description": "LiquidJS 是一个简单的、安全的、兼容 Shopify 的、纯 JavaScript 编写的模板引擎。",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon/apple-touch-icon-57x57.png",
|
||||
"type": "image/png",
|
||||
"sizes": "57x57"
|
||||
},
|
||||
{
|
||||
"src": "/icon/favicon-96x96.png",
|
||||
"type": "image/png",
|
||||
"sizes": "96x96"
|
||||
},
|
||||
{
|
||||
"src": "/icon/favicon-196x196.png",
|
||||
"type": "image/png",
|
||||
"sizes": "196x196"
|
||||
},
|
||||
{
|
||||
"src": "/icon/apple-touch-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"shortcuts" : [
|
||||
{
|
||||
"name": "教程",
|
||||
"url": "/zh-cn/tutorials/intro-to-liquid.html",
|
||||
"description": "一系列描述如何使用 LiquidJS 的文章"
|
||||
},
|
||||
{
|
||||
"name": "标签",
|
||||
"url": "/zh-cn/tags/overview.html",
|
||||
"description": "每个 Liquid 标签的描述和示例"
|
||||
},
|
||||
{
|
||||
"name": "过滤器",
|
||||
"url": "/zh-cn/filters/overview.html",
|
||||
"description": "每个 Liquid 过滤器的描述和示例"
|
||||
},
|
||||
{
|
||||
"name": "演示",
|
||||
"url": "/zh-cn/playground.html",
|
||||
"description": "一个用来尝试和分享 Liquid 模板的在线编辑器"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"url": "/zh-cn/api/classes/liquid_.liquid.html",
|
||||
"description": "LiquidJS 类和接口的 TypeScript 文档"
|
||||
}
|
||||
],
|
||||
"start_url": "/zh-cn",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0f83ce",
|
||||
"background_color": "#0f83ce"
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
---
|
||||
layout: playground
|
||||
title: 演示
|
||||
description: 一个用来尝试和分享 Liquid 模板的在线编辑器
|
||||
---
|
||||
|
||||
@@ -49,17 +49,17 @@ The collection is empty.
|
||||
输入
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{% if i == 4 %}
|
||||
{%- if i == 4 -%}
|
||||
{% break %}
|
||||
{% else %}
|
||||
{%- else -%}
|
||||
{{ i }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
1 2 3
|
||||
123
|
||||
```
|
||||
|
||||
### continue
|
||||
@@ -69,17 +69,17 @@ The collection is empty.
|
||||
输入
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{% if i == 4 %}
|
||||
{% continue %}
|
||||
{% else %}
|
||||
{%- if i == 4 -%}
|
||||
{%- continue -%}
|
||||
{%- else -%}
|
||||
{{ i }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
1 2 3 5
|
||||
1235
|
||||
```
|
||||
|
||||
### forloop
|
||||
@@ -137,13 +137,13 @@ index index0 rindex rindex0
|
||||
```liquid
|
||||
<!-- if array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{ item }}
|
||||
{{- item -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
1 2
|
||||
12
|
||||
```
|
||||
|
||||
### offset
|
||||
@@ -152,15 +152,57 @@ index index0 rindex rindex0
|
||||
|
||||
输入
|
||||
```liquid
|
||||
<!-- if array = [1,2,3,4,5,6] -->
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array offset:2 %}
|
||||
{{ item }}
|
||||
{{- item -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
3 4 5 6
|
||||
3456
|
||||
```
|
||||
|
||||
#### offset:continue
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
`offset` 的值可以是 `continue`,用来继续上一次循环。例如:
|
||||
|
||||
输入
|
||||
```liquid
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
{% for item in array offset:continue %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
12
|
||||
3456
|
||||
```
|
||||
|
||||
对同样的变量名和集合名(这个例子中是 `"item-array"`),存在唯一的位置记录。也就是说用新的变量名就可以开启一个新的循环:
|
||||
|
||||
输入
|
||||
```liquid
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
{% for item2 in array offset:continue %}
|
||||
{{- item2 -}}
|
||||
{% endfor%}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
12
|
||||
123456
|
||||
```
|
||||
|
||||
### range
|
||||
|
||||
@@ -44,5 +44,67 @@ title: Include
|
||||
|
||||
上面的例子中,子模板中 `product` 会保有父模板中的 `featured_product` 变量的值。
|
||||
|
||||
## 输出和过滤器
|
||||
|
||||
文件名为字符串字面量时,支持 Liquid 输出和过滤器。在拼接文件名时很方便:
|
||||
|
||||
```liquid
|
||||
{% include "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
{% note info 转义 %}
|
||||
字符串字面量里的 `"` 需要转义为 `\"`,使用静态文件名可以避免这个问题,见下面的 Jekyll-like 文件名。
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like 文件名
|
||||
|
||||
设置 [dynamicPartials][dynamicPartials] 为 `false` 来启用 Jekyll-like 文件名,这时文件名不需要用引号包含,会被当作字面量处理。 这样的字符串里面仍然支持 Liquid 输出和过滤器,例如:
|
||||
|
||||
```liquid
|
||||
{% include prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
这样文件名里的 `"` 就不用转义了。
|
||||
|
||||
```liquid
|
||||
{% include prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
## Jekyll include
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
[jekyllInclude][jekyllInclude] 用来启用 Jekyll-like include 语法。默认为 `false`,当设置为 `true` 时:
|
||||
|
||||
- 默认启用静态文件名:`dynamicPartials` 的默认值变为 `false`(而非 `true`)。但你也可以把它设置回 `true`。
|
||||
- 参数的键和值之间由 `=` 分隔(本来是 `:`)。
|
||||
- 参数放到了 `include` 变量下,而非当前作用域。
|
||||
|
||||
例如下面的模板:
|
||||
|
||||
```liquid
|
||||
{% include article.html header="HEADER" content="CONTENT" %}
|
||||
```
|
||||
|
||||
其中 `article.html` 的内容是:
|
||||
|
||||
```liquid
|
||||
<article>
|
||||
<header>{{include.header}}</header>
|
||||
{{include.content}}
|
||||
</article>
|
||||
```
|
||||
|
||||
注意我们通过 `include.header` 引用第一个参数,而不是 `header`。输出如下:
|
||||
|
||||
```html
|
||||
<article>
|
||||
<header>HEADER</header>
|
||||
CONTENT
|
||||
</article>
|
||||
```
|
||||
|
||||
[extname]: ../../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[dynamicPartials]: ../../api/interfaces/liquid_options_.liquidoptions.html#Optional-dynamicPartials
|
||||
[jekyllInclude]: ../../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "#(单行注释)"
|
||||
---
|
||||
|
||||
{% since %}v9.38.0{% endsince %}
|
||||
|
||||
在 Liquid 模板中添加注释,注释标签内的文字不会被输出。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
注释标签内的东西都不会输出。
|
||||
{% # this is an inline comment %}
|
||||
但每行都必须以 '#' 开头。
|
||||
{%
|
||||
# this is a comment
|
||||
# that spans multiple lines
|
||||
%}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
注释标签内的东西都不会输出。
|
||||
但每行都必须以 '#' 开头。
|
||||
```
|
||||
|
||||
在 <a href="./liquid.html">`liquid`</a> 标签里也可以使用注释标签。
|
||||
|
||||
```liquid
|
||||
{% liquid
|
||||
# required args
|
||||
assign product = collection.products.first
|
||||
|
||||
# optional args
|
||||
assign should_show_border = should_show_border | default: true
|
||||
assign should_highlight = should_highlight | default: false
|
||||
%}
|
||||
```
|
||||
|
||||
但注释标签不能用于把其他标签注释掉。这时应该使用 <a href="./comment.html">`comment`</a> 标签来临时禁用其他标签。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{%- # {% echo 'Welcome to LiquidJS!' %} -%}
|
||||
{% comment %}{% echo 'Welcome to LiquidJS!' %}{% endcomment %}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
-%}
|
||||
```
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 标签
|
||||
description: 每个 Liquid 标签的描述和示例
|
||||
---
|
||||
|
||||
LiquidJS 支持 Liquid 语法中具体业务无关的标签,包含 [shopify/liquid 核心][shopify/liquid] 里的所有标签。这部分包含了所有 LiquidJS 支持的标签的文档和使用示例。
|
||||
@@ -10,8 +11,8 @@ LiquidJS 支持十几个过滤器,可以分为如下几类:
|
||||
--- | --- | ---
|
||||
迭代 | 遍历一个集合 | for, cycle, tablerow
|
||||
控制流 | 控制模板渲染的执行分支 | if, unless, elif, else, case, when
|
||||
变量 | 定义和修改变量 | assign, increment, decrement
|
||||
变量 | 定义和修改变量 | assign, increment, decrement, capture, echo
|
||||
文件 | 引入或继承其他模板 | render, include, layout
|
||||
语言 | 暂时禁用 Liquid 语法 | raw, comment
|
||||
语言 | 暂时禁用 Liquid 语法 | # (单行注释), raw, comment, liquid
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: 过滤器里访问上下文
|
||||
---
|
||||
|
||||
在 [注册过滤器和标签][register-filters] 里介绍过,可以在函数参数里直接获得过滤器的参数:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ 1 | add: 2, 3 }}
|
||||
// Output: 6
|
||||
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
|
||||
```
|
||||
|
||||
但有些过滤器还需要访问当前上下文的变量,比如把 URL 路径转换为完整的 URL 时,需要访问上下文的 `origin` 变量:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ '/index.html' | fullURL }}
|
||||
// Scope: { origin: "https://liquidjs.com" }
|
||||
// Output: https://liquidjs.com/index.html
|
||||
|
||||
engine.registerFilter('fullURL', function (path) {
|
||||
const origin = this.context.get(['origin'])
|
||||
return new URL(path, origin).toString()
|
||||
})
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/1/>。
|
||||
|
||||
{% note warn 箭头函数 %}
|
||||
在箭头函数里 `this` 会绑定到当前 JavaScript 上下文,你需要用 `function(){}` 来替代 `()=>{}` 语法,才能正确地访问 `this.context`。
|
||||
{% endnote %}
|
||||
|
||||
[register-filters]: /tutorials/register-filters-tags.html
|
||||
@@ -2,7 +2,7 @@
|
||||
title: 贡献指南
|
||||
---
|
||||
|
||||
## Star on Github 👉 [][liquidjs]
|
||||
## Star LiquidJS 👉👉👉 [][liquidjs]
|
||||
|
||||
Star 是支持 LiquidJS 最重要的方式,也是最简单的方式:通过提升排名来让更多人了解 LiquidJS,让它得到更好的改进。
|
||||
|
||||
@@ -24,13 +24,19 @@ npm test
|
||||
|
||||
**向后兼容**:请考虑向后(之前的旧的版本)兼容。LiquidJS 被用于很多层的软件,包括底层库、编译器、站点生成器、 Web 服务器。对多数最终用户来说,驱动或请求整个系统做一次主版本升级是很难办到的。
|
||||
|
||||
## 成为赞助者!
|
||||
## 资金支持
|
||||
|
||||
LiquidJS 是开源的、免费的,并且 **没有** 商业支持,也 **没有** 任何广告。如果你喜欢 LiquidJS 或你的公司在使用 LiquidJS,请考虑通过 [Open Collective][oc] 或 [Patreon][pt] 赞助,作为感谢你的名字和头像(或 Logo)会展示在这里和 [Github README][liquidjs]。
|
||||
LiquidJS 是开源和免费的,但支持 [Open Collective][oc] 或 [Patreon][pt] 赞助,作为感谢你的名字和头像(或 Logo)会展示在这里,[LiquidJS 首页](https://liquidjs.com/)和 [Github README][liquidjs]。
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/tiers/backer.svg?avatarHeight=72"></object>
|
||||
### 支持者
|
||||
如果你喜欢 LiquidJS,希望支持我们,你可以成为 Open Collective Backer!
|
||||
|
||||
[](https://www.patreon.com/bePatron?u=32321060)
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/backers.svg?avatarHeight=72"></object>
|
||||
|
||||
### 赞助商
|
||||
如果你的公司/业务在使用 LiquidJS,请考虑赞助我们!让 LiquidJS 变得更好,以及保持开放和免费!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/sponsors.svg?avatarHeight=72"></object>
|
||||
|
||||
[oc]: https://opencollective.com/liquidjs/
|
||||
[pt]: https://www.patreon.com/harttle
|
||||
|
||||
@@ -2,7 +2,25 @@
|
||||
title: 和 Shopify/liquid 的区别
|
||||
---
|
||||
|
||||
[Shopify/liquid](https://github.com/Shopify/liquid) 中的所有标签和过滤器 LiquidJS 都支持,但不包括 Shopify 主题中业务逻辑相关的标签和过滤器(如果你在找这些标签可以参考 [插件列表][plugins],也欢迎把你的插件添加到列表中)。尽管原则上我们尽力兼容于 Shopify/liquid,但仍然存在一些区别:
|
||||
## 兼容性
|
||||
|
||||
LiquidJS 一直很重视兼容于 Ruby 版本的 Liquid。Liquid 模板语言最初由 [Ruby 实现][ruby-liquid],用于 Shopify,Jekyll 以及 Github Pages,它是 Ruby 里最流行的模板引擎之一。因此由很多人用 LiquidJS 来渲染他们的 Shopify 主题和 Jekyll 站点。
|
||||
|
||||
所以“兼容”意味着让这些开发者有很好的使用体验:
|
||||
|
||||
- **LiquidJS 应当能够渲染语法正确的 Liquid 模板**。例如 `forloop.index` 应该是 1 开始的下表,`nil` 应该渲染成空字符串而不是 `undefined` 等。即使有些功能(例如 [#236][#236])用 JavaScript 很难实现,但至少 LiquidJS 会尝试实现所有的 Liquid 语义。
|
||||
- **所有 [shopify/liquid][ruby-liquid] 里的标签和过滤器 LiquidJS 都要实现**。这之外有些业务逻辑相关的标签/过滤器尤其是 Shopify 平台上的那些,应该维护在 [插件][plugins] 里。但是这其中有一些很有用的标签(比如 `{% layout %}`)LiquidJS 也会考虑实现,可以去开个 Issue 讨论一下。
|
||||
|
||||
同时,既然现在用 JavaScript 实现了,那 Liquid 应该有更强的功能:
|
||||
|
||||
* **完全支持异步**。所有过滤器和标签都可以实现为异步,只需要返回 `Promise` 即可。
|
||||
* **同时支持同步**。对一些非 I/O 密集的场景,同步渲染会更快。只要模板包含的标签和过滤器都支持同步,你就可以调用类似 `.renderSync()` 这样的 API。所有内置标签和过滤器都同时支持同步和异步。
|
||||
* **[抽象文件系统][afs]**。和异步功能一起使用,LiquidJS 可以实现渲染数据库里的模板 [#414][#414],远程 HTTP 服务器上的模板 [#485][#485],等等。
|
||||
* **额外的标签和过滤器**。比如 `layout` 和 `json`。
|
||||
|
||||
## 区别
|
||||
|
||||
[Shopify/liquid][ruby-liquid] 中的所有标签和过滤器 LiquidJS 都支持,但不包括 Shopify 主题中业务逻辑相关的标签和过滤器(如果你在找这些标签可以参考 [插件列表][plugins],也欢迎把你的插件添加到列表中)。尽管原则上我们尽力兼容于 Shopify/liquid,但仍然存在一些区别:
|
||||
|
||||
* 真和假。在 LiquidJS 中 `undefined`, `null`, `false` 是假,之外的都是真;在 Ruby 中 `nil` 和 `false` 是假,其他都是真。见 [#26][#26]。
|
||||
* 数字。JavaScript 不区分浮点数和整数,因此缺失一部分整数算术,见 [#59][#59]。此外 `size` 过滤器作用于数字时总是返回零,而不是 Ruby 中的浮点数或整数的内存大小。
|
||||
@@ -11,11 +29,20 @@ title: 和 Shopify/liquid 的区别
|
||||
* 对象的迭代顺序。JavaScript 对象的迭代顺序是插入顺序和数字键递增顺序的组合,但 Ruby Hash 中只是插入顺序(JavaScript 字面量 Object 和 Ruby 字面量 Hash 的插入顺序解释也不同)。
|
||||
* 排序稳定性。shopify/liquid 和 LiquidJS 都没有定义 [sort][sort] 过滤器的稳定性在,它取决于 Ruby/JavaScript 内置的排序算法,在 Node.js 12+ 和 Google Chrome 70+ LiquidJS 的排序是 [稳定的][stable-sort]。
|
||||
* shopify/liquid 允许过滤器尾部的未匹配字符,但 LiquidJS 不允许。这就是说如果过滤器参数前忘记写冒号比如 `{%raw%}{{ "a b" | split " "}}{%endraw%}` LiquidJS 会抛出异常。这是为了提升 Liquid 模板的易用性,参考 [#208][#208] 和 [#212][#212]。
|
||||
* LiquidJS 有额外的标签:[layout][layout] 和相应的 `block`。
|
||||
* LiquidJS 有额外的过滤器:[json][json]。
|
||||
|
||||
[layout]: https://liquidjs.com/tags/layout.html
|
||||
[json]: https://liquidjs.com/filters/json.html
|
||||
[#26]: https://github.com/harttle/liquidjs/pull/26
|
||||
[#59]: https://github.com/harttle/liquidjs/issues/59
|
||||
[#208]: https://github.com/harttle/liquidjs/issues/208
|
||||
[#212]: https://github.com/harttle/liquidjs/issues/212
|
||||
[#236]: https://github.com/harttle/liquidjs/issues/236
|
||||
[#414]: https://github.com/harttle/liquidjs/discussions/414
|
||||
[#485]: https://github.com/harttle/liquidjs/discussions/485
|
||||
[sort]: https://liquidjs.com/filters/sort.html
|
||||
[stable-sort]: https://v8.dev/features/stable-sort
|
||||
[plugins]: ./plugins.html#插件列表
|
||||
[ruby-liquid]: https://github.com/Shopify/liquid
|
||||
[afs]: https://liquidjs.com/tutorials/render-file.html#Abstract-File-System
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Liquid 模板语言简介
|
||||
title: Liquid 模板语言
|
||||
description: Liquid 模板语言的介绍,和一些示例代码
|
||||
---
|
||||
|
||||
LiquidJS 是一个简单的、安全的、兼容 Shopify 的、纯 JavaScript 编写的模板引擎。这个项目的目的是为 JavaScript 社区提供一个 Liquid 模板引擎的实现。Liquid 最初用 Ruby 实现并用于 Github Pages, Jekyll 和 Shopify,参考 [和 Shopify/liquid 的区别][diff]。
|
||||
@@ -9,6 +10,10 @@ LiquidJS 语法相对简单。LiquidJS 中有两种标记:
|
||||
- **标签**。标签由标签名和参数构成,由 `{%raw%}{%{%endraw%}` 和 `%}` 包裹。
|
||||
- **输出**。输出由一个值和一组可选的过滤器构成,由 `{%raw%}{{{%endraw%}` 和 `}}` 包裹。
|
||||
|
||||
{% note info 在线示例 %}
|
||||
在进一步了解细节之前,这里有一个在线示例:<https://liquidjs.com/playground.html>。
|
||||
{% endnote %}
|
||||
|
||||
## 输出
|
||||
|
||||
**输出** 用于转换和输出变量到 HTMl。下面的模板将会把 `username` 的值插入到 input 的 `value`:
|
||||
|
||||
@@ -47,9 +47,32 @@ const engine = new Liquid({
|
||||
{% liquid foo.html %}
|
||||
```
|
||||
|
||||
{% note warn Common Pitfall %}
|
||||
{% note warn 常见陷阱 %}
|
||||
LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/liquid,但如果你在使用 <a href="https://github.com/11ty/eleventy" target="_blank">eleventy</a> 它会设置默认值 <code>false</code> (参考 <a href="https://www.11ty.dev/docs/languages/liquid/#quoted-include-paths" target="_blank">Quoted Include Paths</a>)以兼容于 Jekyll。{% endnote %}
|
||||
|
||||
## Jekyll include
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
[jekyllInclude][jekyllInclude] 用来启用 Jekyll-like include 语法。默认为 `false`,当设置为 `true` 时:
|
||||
|
||||
- 默认启用静态文件名:`dynamicPartials` 的默认值变为 `false`(而非 `true`)。但你也可以把它设置回 `true`。
|
||||
- 参数的键和值之间由 `=` 分隔(本来是 `:`)。
|
||||
- 参数放到了 `include` 变量下,而非当前作用域。
|
||||
|
||||
例如下面的模板中,`name.html` 没有带引号,`header` 和 `"HEADER"` 以 `=` 分隔,`header` 参数通过 `include.header` 来引用。更多详情请参考 [include][include]。
|
||||
|
||||
```liquid
|
||||
// entry template
|
||||
{% include article.html header="HEADER" content="CONTENT" %}
|
||||
|
||||
// article.html
|
||||
<article>
|
||||
<header>{{include.header}}</header>
|
||||
{{include.content}}
|
||||
</article>
|
||||
```
|
||||
|
||||
## extname
|
||||
|
||||
**extname** 定义了默认的文件后缀,当传入文件名不包含后缀时自动追加。默认值是 `''` 也就是说默认是禁用的。如果设置为 `.liquid`:
|
||||
@@ -102,6 +125,8 @@ LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/l
|
||||
|
||||
**strictVariables** 用来启用变量严格模式。如果设置为 `true` 变量不存在时渲染会抛出异常,默认为 `false` 这时不存在的变量会被渲染为空字符串。
|
||||
|
||||
**ownPropertyOnly** 用来隐藏原型上的变量,如果你需要把未经处理过的对象传递给模板时,可以设置 `ownPropertyOnly` 为 `true`,默认为 `false`。
|
||||
|
||||
{% note info 不存在的标签 %}
|
||||
不存在的标签总是会抛出一个解析异常,这一行为无法自定义。
|
||||
{% endnote %}
|
||||
@@ -120,3 +145,4 @@ LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/l
|
||||
[layout]: ../tags/layout.html
|
||||
[wc]: ./whitespace-control.html
|
||||
[intro]: ./intro-to-liquid.html
|
||||
[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: 参数解析
|
||||
---
|
||||
|
||||
## 访问原始参数
|
||||
|
||||
在 [注册过滤器和标签][register-tags] 中提到,可以通过 `tagToken.args` 来得到标签的原始参数字符串。例如:
|
||||
|
||||
```javascript
|
||||
// Usage: {% random foo bar coo %}
|
||||
// Output: "foo", "bar" or "coo"
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// tagToken.args === "foo bar coo"
|
||||
this.items = tagToken.args.split(' ')
|
||||
},
|
||||
render(context, emitter) {
|
||||
// get a random index
|
||||
const index = Math.floor(this.items.length * Math.random())
|
||||
// output that item
|
||||
emitter.write(this.items[index])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/2/>。
|
||||
|
||||
## 解析参数的值
|
||||
|
||||
除了静态的参数字符串之外,我们更希望把动态的值传递给标签。LiquidJS 中的值可以是字面量(字符串、数字等,也可以是当前上下文的变量。
|
||||
|
||||
下面是修改过的模板,也包含三个值用来随机。但它们表示的是值而不是静态的字符串。第一个是字符串字面量,第二个是标识符(表示变量),第三个是属性访问表达式,包含两个标识符。
|
||||
|
||||
```liquid
|
||||
{% random "foo" bar obj.coo %}
|
||||
```
|
||||
|
||||
解析这么多种情况会很麻烦,但 LiquidJS 提供了 [Tokenizer][Tokenizer] 类来处理这种情况。
|
||||
|
||||
```javascript
|
||||
const { Liquid, Tokenizer, evalToken } = require('liquidjs')
|
||||
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
const tokenizer = new Tokenizer(tagToken.args)
|
||||
this.items = []
|
||||
while (!tokenizer.end()) {
|
||||
// here readValue() returns a LiteralToken or PropertyAccessToken
|
||||
this.items.push(tokenizer.readValue())
|
||||
}
|
||||
},
|
||||
* render(context, emitter) {
|
||||
const index = Math.floor(this.items.length * Math.random())
|
||||
const token = this.items[index]
|
||||
// in LiquidJS, we use yield to wait for async call
|
||||
const value = yield evalToken(token, context)
|
||||
emitter.write(value)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
用上下文 `{ bar: "bar", obj: { coo: "coo" } }` 来调用这个标签可以得到上第一个例子一样的效果。见这个 JSFiddle:<http://jsfiddle.net/ctj364up/3/>.
|
||||
|
||||
{% note info 异步和 Promise %}
|
||||
在 LiquidJS 里异步用生成器实现,这样同样一份标签的实现也可以用于同步的 API 比如 `renderSync()`,`parseAndRenderSync()`,`renderFileSync()`。如果要在标签实现里等待 Promise,只需要把 `await somePromise` 换成 `yield somePromise`,并保留 `* render()` 不要改成 `async render()`。更多细节请参考 <a href="/tutorials/sync-and-async.html">Sync and Async</a>。
|
||||
{% endnote %}
|
||||
|
||||
## 把键值对解析为命名参数
|
||||
|
||||
当参数很多时或者有可选参数时,使用命名参数语法会很方便。这时参数由无序的键值对构成,LiquidJS 中的 [Hash][Hash] 类就是来处理这种情况的。
|
||||
|
||||
```liquid
|
||||
{% random from:2, to:max %}
|
||||
```
|
||||
|
||||
上面的例子用来产生 [2, max] 范围内的随机数。我们要用 `Hash` 来解析 `from` 和 `to` 参数。
|
||||
|
||||
```javascript
|
||||
const { Liquid, Hash } = require('liquidjs')
|
||||
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// 解析参数结果,存到 `this.args` 里
|
||||
this.args = new Hash(tagToken.args)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
// 在当前 `context` 下计算参数的值
|
||||
const {from, to} = yield this.args.render(context)
|
||||
const length = to - from + 1
|
||||
const value = from + Math.floor(length * Math.random())
|
||||
emitter.write(value)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
在 `{ max: 10 }` 上下文上渲染 `{% random from:2, to:max %}` 将会得到 [2, 10] 范围内的随机数。见这个 JSFiddle:<http://jsfiddle.net/ctj364up/4/>。
|
||||
|
||||
[register-tags]: /tutorials/register-filters-tags.html
|
||||
[Tokenizer]: /api/classes/parser_tokenizer_.tokenizer.html
|
||||
[Hash]: /api/classes/template_tag_hash_.hash.html
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: 渲染标签内容
|
||||
---
|
||||
|
||||
自定义标签可以有内容,也可以嵌套使用。本文描述了如何实现一个由*开始标签*,*结束标签*和之间的*标签内容*的自定义标签。
|
||||
|
||||
## 渲染标签内容
|
||||
|
||||
我们先实现一个简单的 `wrap` 标签,它会把内容包装在 `<div class="wrapper"></div>` 元素里:
|
||||
|
||||
```liquid
|
||||
{% wrap %}
|
||||
{{ "hello world!" | capitalize }}
|
||||
{% endwrap %}
|
||||
```
|
||||
|
||||
期望输出:
|
||||
|
||||
```html
|
||||
<div class='wrapper'>
|
||||
Hello world!
|
||||
</div>
|
||||
```
|
||||
|
||||
首先 [注册][register-tags] 一个名为 `wrap` 的标签,把内容解析到 `this.tpls` 数组里。`parse(tagToken, remainTokens)` 中,
|
||||
|
||||
- `tagToken` 是当前 *Token* `{% wrap %}`,
|
||||
- `remainTokens` 是当前模板中后续所有 *Token* 的数组。
|
||||
|
||||
我们要做的是从 `remainTokens` 里拿出来/`.shift()` 足够的标签直到遇到 `endwrap`(其实可以是任意名字,但按照惯例应该叫 `endwrap`)。如果到模板结尾都没遇到 `endwrap`,需要抛出一个标签未关闭的 `Error`。
|
||||
|
||||
```javascript
|
||||
engine.registerTag('wrap', {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
let closed = false
|
||||
while(remainTokens.length) {
|
||||
let token = remainTokens.shift()
|
||||
// 得到了结束标签,停止解析
|
||||
if (token.name === 'endwrap') {
|
||||
closed = true
|
||||
break
|
||||
}
|
||||
// 把 Token 解析成 Template
|
||||
// parseToken() 可能会消耗多个 Token
|
||||
// 例如 {% if %}...{% endif %}
|
||||
let tpl = this.liquid.parser.parseToken(token, remainTokens)
|
||||
this.tpls.push(tpl)
|
||||
}
|
||||
if (!closed) throw new Error(`tag ${tagToken.getText()} not closed`)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
emitter.write("<div class='wrapper'>")
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
emitter.write("</div>")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`.renderTemplates()` 可能是异步的,因此需要 `yield` 来等它完成。更多关于 LiquidJS 异步的信息可以参考 [同步和异步][async]。`render()` 的其他部分比较直观,这是 JSFiddle 版本:<http://jsfiddle.net/por0zcn1/3/>。
|
||||
|
||||
## 使用 ParseStream
|
||||
|
||||
对于像 [for][for] 和 [if][if] 这样的复杂标签,`parse()` 会变得很复杂。使用 [ParseStream][ParseStream] 工具可以按事件风格来组织 `parse()` 的逻辑。下面是用 `ParseStream` 重写过的 `parse()`,实现了和上面例子中完全一样的功能。
|
||||
|
||||
```javascript
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
this.liquid.parser.parseStream(remainTokens)
|
||||
.on('template', tpl => this.tpls.push(tpl))
|
||||
// 注意这里不能用箭头函数,因为我们需要 `this`
|
||||
.on('tag:endwrap', function () { this.stop() })
|
||||
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||
.start()
|
||||
}
|
||||
```
|
||||
|
||||
这是 JSFiddle 链接:<http://jsfiddle.net/por0zcn1/4/>。简单起见,下面的例子都借助 `ParseStream` 来实现。
|
||||
|
||||
## 操作上下文
|
||||
|
||||
上面的 `wrap` 标签看起来没什么用,反正没它也可以很容易地渲染那部分内容。我们现在来实现一个 `repeat` 标签,把内容渲染两次(还可以[加个参数][parameter]让它渲染任意次):
|
||||
|
||||
```liquid
|
||||
{% repeat %}
|
||||
{{ repeat.i }}. {{ "hello world!" | capitalize }}
|
||||
{% endrepeat %}`
|
||||
```
|
||||
|
||||
期望输出:
|
||||
|
||||
```html
|
||||
1. Hello world!
|
||||
2. Hello world!
|
||||
```
|
||||
|
||||
你可能注意到了在 `repeat` 上下文里有个额外的变量 `repeat.i`,这就需要我们操作 *上下文*。
|
||||
|
||||
{% note info 上下文 %}
|
||||
<em>上下文</em> 定义了 Liquid 模板中每个变量的值。在 LiquidJS 中,`Context` 由一个 `Scope` 的栈组成。*Scope* 就是一个普通对象,就像传给 `engine.render(tpl, scope)` 的 `scope` 一样。
|
||||
{% endnote %}
|
||||
|
||||
每次进入新的 *上下文* 时,我们需要 `push` 一个新的 `Scope`。当结束渲染并退出 *上下文* 时,再把 `Scope` 从 *上下文* `pop` 出来。见下面的实现:
|
||||
|
||||
```javascript
|
||||
engine.registerTag('repeat', {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
this.liquid.parser.parseStream(remainTokens)
|
||||
.on('template', tpl => this.tpls.push(tpl))
|
||||
.on('tag:endrepeat', function () { this.stop() })
|
||||
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||
.start()
|
||||
},
|
||||
* render(context, emitter) {
|
||||
const repeat = { i: 1 }
|
||||
context.push({ repeat })
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
repeat.i++
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
context.pop()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`parse()` 部分和 `wrap` 标签完全相同,在 `render()` 部分我们通过调用两次 `.renderTemplates(this.tpls)` 来重复渲染内容。这是 JSFiddle 链接:<http://jsfiddle.net/por0zcn1/2/>。
|
||||
|
||||
{% note warn 成对使用 Push 和 Pop %}
|
||||
必须成对地使用 `context.push()` 和 `context.pop()`。如果忘记 `pop()` 会导致 `Scope` 泄露给后面的模板内容,也可能损坏 *上下文* 栈。.
|
||||
{% endnote %}
|
||||
|
||||
[register-tags]: ./register-filters-tags.html
|
||||
[async]: ./sync-and-async.html
|
||||
[for]: ../tags/for.html
|
||||
[if]: ../tags/if.html
|
||||
[ParseStream]: ../api/classes/parser_parse_stream_.parsestream.html
|
||||
[parameter]: ./parse-parameters.html
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: 同步和异步
|
||||
---
|
||||
|
||||
LiquidJS 支持同步调用也支持异步调用,支持 Promise。为了同异步复用一套标签和过滤器,LiquidJS 标签用生成器来实现。
|
||||
|
||||
## 同异步 API
|
||||
|
||||
[Liquid][Liquid] 上主要的方法都支持同步和异步,下面这些方法返回 `Promise`:
|
||||
|
||||
- `render()`
|
||||
- `renderFile()`
|
||||
- `parseFile()`
|
||||
- `parseAndRender()`
|
||||
- `evalValue()`
|
||||
|
||||
它们的同步版本带一个 `Sync` 后缀:
|
||||
|
||||
- `renderSync()`
|
||||
- `renderFileSync()`
|
||||
- `parseFileSync()`
|
||||
- `parseAndRenderSync()`
|
||||
- `evalValueSync()`
|
||||
|
||||
## 如何实现兼容同步的标签
|
||||
|
||||
### 要求
|
||||
|
||||
所有内置标签都兼容同步,可以安全地用于同步或异步 API。为了让你的自定义标签页支持同步,你的标签不能返回 `Promise`,这意味着你的 `render(context, emitter)` 函数:
|
||||
|
||||
- 不能直接 `return <Promise>`,
|
||||
- 也不能声明为 `async`。
|
||||
|
||||
{% note info 不兼容同步的标签 %}
|
||||
不兼容同步的标签也仍然是合法标签,在异步 API 下也会正常运行。被同步调用时,返回 <code>Promise</code> 的标签会被渲染成 <code>[object Promise]</code>。
|
||||
{% endnote %}
|
||||
|
||||
### 等待 Promise
|
||||
但 LiquidJS 是支持 `Promise` 的,你仍然可以调用返回 `Promise` 的方法并等它 resolve。只需要把 `await` 换成 `yield` 并保留 `* render()` 不要改成 `async render()`。例如:
|
||||
|
||||
```typescript
|
||||
import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
|
||||
|
||||
// Usage: {% upper "alice" %}
|
||||
// Output: ALICE
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
this.str = tagToken.args
|
||||
},
|
||||
* render: function(ctx: Context) {
|
||||
// 同步调用时 `ctx.sync == true`,`_evalValue()` 会同步地执行
|
||||
var str = yield this.liquid._evalValue(this.str, ctx)
|
||||
return str.toUpperCase()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/6/>。
|
||||
|
||||
## 只支持异步的标签
|
||||
|
||||
对于只用于异步 API 的标签,或者只能实现为异步的标签,使用生成器语法和 async 语法并没有区别。
|
||||
|
||||
例如,如果上面的 `this.liquid._evalValue()` 不会检查 `ctx.sync` 而且总是返回一个 `Promise`,那么即使这个标签用 `* render()` 和 `yield this.liquid._evalValue()` 实现,最终也会渲染成 `<object Promise>`。
|
||||
|
||||
这时可以直接使用 async 语法。注意有些 LiquidJS API 会返回 `Promise`,有些会返回生成器。你需要用 [toPromise][toPromise] API 来把生成器转换为 `Promise`,比如:
|
||||
|
||||
```typescript
|
||||
import { TagToken, Context, Emitter, TopLevelToken, toPromise } from 'liquidjs'
|
||||
|
||||
// Usage: {% upper "alice" %}
|
||||
// Output: ALICE
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
this.str = tagToken.args; // name
|
||||
},
|
||||
render: async function(ctx: Context) {
|
||||
var str = await toPromise(this.liquid._evalValue(this.str, ctx));
|
||||
// Or use the alternate API that returns a Promise
|
||||
// var str = await this.liquid.evalValue(this.str, ctx);
|
||||
return str.toUpperCase()
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/5/>。
|
||||
|
||||
[Liquid]: /api/classes/liquid_.liquid.html
|
||||
[toPromise]: /api/modules/liquid_.html#toPromise
|
||||
Vendored
+4
@@ -41,6 +41,10 @@ sidebar:
|
||||
advanced: Advanced
|
||||
caching: Caching
|
||||
registeration: Register Filters/Tags
|
||||
access_scope_in_filters: Access Scope in Filters
|
||||
parse_parameters: Parse Parameters
|
||||
render_tag_content: Render Tag Content
|
||||
sync_and_async: Sync and Async
|
||||
whitespace: Whitespace Control
|
||||
plugins: Plugins
|
||||
operators: Operators
|
||||
|
||||
Vendored
+5
@@ -41,6 +41,10 @@ sidebar:
|
||||
advanced: 高级主题
|
||||
caching: 缓存
|
||||
registeration: 注册标签/过滤器
|
||||
access_scope_in_filters: 过滤器里访问上下文
|
||||
parse_parameters: 参数解析
|
||||
render_tag_content: 渲染标签内容
|
||||
sync_and_async: 同步和异步
|
||||
whitespace: 换行和缩进
|
||||
plugins: 插件
|
||||
operators: 运算符
|
||||
@@ -55,6 +59,7 @@ sidebar:
|
||||
overview: 概述
|
||||
tags:
|
||||
overview: 概述
|
||||
'# (inline comment)': '#(单行注释)'
|
||||
api:
|
||||
quick_entry: 快速入口
|
||||
classes: 类
|
||||
|
||||
Vendored
+1
-7
@@ -25,13 +25,7 @@
|
||||
<div class="wrapper">
|
||||
<div class="inner">
|
||||
{{ page.content }}
|
||||
<ul id="intro-cmd-wrap">
|
||||
<li class="intro-code-item"><span class="hl">import</span> { Liquid } <span class="hl">from</span> 'liquidjs'</li>
|
||||
<li class="intro-code-item"><span class="hl">const</span> engine = <span class="hl">new</span> Liquid()</li>
|
||||
<li class="intro-code-item"><span class="hl">const</span> tpl = engine.parse(<span class="hl-str">'Welcome to {{v}}!'</span>)</li>
|
||||
<li class="intro-code-item">engine.render(tpl, {v: "Liquid"}).then(console.log)</li>
|
||||
<li class="intro-code-item"><span class="hl-comment">// Outputs "Welcome to Liquid!"</span></li>
|
||||
</ul>
|
||||
<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">
|
||||
<a href="tutorials/setup.html" id="intro-get-started-link">{{ __('index.get_started') }}</a>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +48,14 @@
|
||||
<td align="center"><a href="https://n1ru4l.cloud/"><img src="https://avatars.githubusercontent.com/u/14338007?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/mattvague"><img src="https://avatars.githubusercontent.com/u/64985?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/bglw"><img src="https://avatars.githubusercontent.com/u/40188355?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://about.me/jasonkurian"><img src="https://avatars.githubusercontent.com/u/2642545?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/dphm"><img src="https://avatars.githubusercontent.com/u/1707217?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://www.aleksandrhovhannisyan.com/"><img src="https://avatars.githubusercontent.com/u/19352442?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/jg-rp"><img src="https://avatars.githubusercontent.com/u/72664870?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/ameyaapte1"><img src="https://avatars.githubusercontent.com/u/16054747?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/tbdrz"><img src="https://avatars.githubusercontent.com/u/50599116?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="http://santialbo.com"><img src="https://avatars.githubusercontent.com/u/1557563?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
<td align="center"><a href="https://github.com/YahangWu"><img src="https://avatars.githubusercontent.com/u/12295975?v=4?s=100" width="100px;" alt=""/></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
+2
-1
@@ -3,8 +3,9 @@
|
||||
<title>{% if page.title %}{{ page.title }} | {% endif %}{{ config.title }}</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ page.description | default(config.description) }}">
|
||||
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net/">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="manifest" href="{% if page.lang != "en" %}/{{page.lang}}{% endif %}/manifest.json">
|
||||
<!-- Canonical links -->
|
||||
<link rel="canonical" href="{{ url }}">
|
||||
<!-- Alternative links -->
|
||||
|
||||
+4
-3
@@ -10,15 +10,16 @@
|
||||
<input type="search" id="search-input" placeholder="{{__('menu.search')}}...">
|
||||
</div>
|
||||
</nav>
|
||||
<div id="lang-select-wrap" class="main-nav-link">
|
||||
<label><i class="icon-network"></i>{{ lang_name(page.lang) }}</label>
|
||||
<div class="main-nav-link icon-nav-link">
|
||||
<label><i class="icon-network"></i> <span class="icon-nav-title">{{ lang_name(page.lang) }}</span></label>
|
||||
<select id="lang-select" data-canonical="{{ canonical_path_for_nav() }}">
|
||||
{% for lang in site.data.languages %}
|
||||
<option value="{{ loop.key }}"{% if page.lang === loop.key %} selected{% endif %}>{{ lang_name(loop.key) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<a href="https://github.com/{{ config.github }}" id="github-link-wrap" class="main-nav-link"><i class="icon-github"></i>Github</a>
|
||||
<a href="https://github.com/{{ config.github }}" class="main-nav-link icon-nav-link"><i class="icon-github"></i> <span class="icon-nav-title">Github</span></a>
|
||||
<a href="https://opencollective.com/liquidjs" class="main-nav-link icon-nav-link"><i class="icon-opencollective"></i> <span class="icon-nav-title">Support</span></a>
|
||||
<a id="mobile-nav-toggle">
|
||||
<span class="mobile-nav-toggle-bar"></span>
|
||||
<span class="mobile-nav-toggle-bar"></span>
|
||||
|
||||
+11
-10
@@ -2,9 +2,6 @@
|
||||
<div id="mobile-nav-inner">
|
||||
<ul id="mobile-nav-list">
|
||||
{{ header_menu('mobile-nav') }}
|
||||
<li class="mobile-nav-item">
|
||||
<a href="https://github.com/{{ config.github }}" class="mobile-nav-link" rel="external" target="_blank">GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% if page.layout == 'page' %}
|
||||
<div class="mobile-sidebar-list">
|
||||
@@ -12,12 +9,16 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="mobile-lang-select-wrap">
|
||||
<span id="mobile-lang-select-label"><i class="icon-network"></i><span>{{ lang_name(page.lang) }}</span></span>
|
||||
<select id="mobile-lang-select" data-canonical="{{ canonical_path_for_nav() }}">
|
||||
{% for lang in site.data.languages %}
|
||||
<option value="{{ loop.key }}"{% if page.lang === loop.key %} selected{% endif %}>{{ lang_name(loop.key) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div id="mobile-button-list">
|
||||
<a href="https://github.com/{{ config.github }}" class="mobile-nav-link" rel="external" target="_blank"><i class="icon-github"></i></a>
|
||||
<a href="https://opencollective.com/liquidjs" class="mobile-nav-link" rel="external" target="_blank"><i class="icon-opencollective"></i></a>
|
||||
<div id="mobile-lang-select-wrap" class="mobile-nav-link">
|
||||
<label for="mobile-lang-select"><i class="icon-network"></i></label>
|
||||
<select id="mobile-lang-select" data-canonical="{{ canonical_path_for_nav() }}">
|
||||
{% for lang in site.data.languages %}
|
||||
<option value="{{ loop.key }}"{% if page.lang === loop.key %} selected{% endif %}>{{ lang_name(loop.key) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
+3
-1
@@ -1,12 +1,14 @@
|
||||
<div id="playground" role="main">
|
||||
<div class="wrapper">
|
||||
<h1 class="inner">{{__('playground.title')}}</h1>
|
||||
<div class="row inner">
|
||||
<div class="loader" role=status aria-busy=true></div>
|
||||
<div id="editors" class="row inner hide" aria-hide=true>
|
||||
<div class="col">
|
||||
<div id="editorEl">{{ raw('partial/demo.liquid') }}</div>
|
||||
<div id="dataEl">{}</div>
|
||||
</div>
|
||||
<div class="col" id="previewEl">{{__('playground.loading')}}</div>
|
||||
</div>
|
||||
<p class="inner version"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+17
-6
@@ -21,15 +21,28 @@ input, button, select
|
||||
border: 0
|
||||
|
||||
body
|
||||
background: color-navy
|
||||
background: var(--color-navy)
|
||||
font-size: font-size
|
||||
font-family: font-sans
|
||||
color: color-default
|
||||
color: var(--color-default)
|
||||
text-rendering: optimizeLegibility
|
||||
-webkit-font-smoothing: antialiased
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
overflow-x: hidden
|
||||
|
||||
a
|
||||
color: var(--color-link)
|
||||
text-decoration: none
|
||||
&:hover
|
||||
color: var(--color-link-hover)
|
||||
text-decoration: underline
|
||||
@media print
|
||||
color: var(--color-default)
|
||||
text-decoration: underline
|
||||
&:after
|
||||
content: " (" attr(href) ")"
|
||||
font-size: 80%
|
||||
|
||||
@media screen and (max-device-width: 480px)
|
||||
body
|
||||
-webkit-text-size-adjust: none;
|
||||
@@ -44,10 +57,8 @@ body
|
||||
padding: 0 gutter-width
|
||||
|
||||
#content-wrap
|
||||
background: #fff
|
||||
border-top: 1px solid darken(color-navy, 5%)
|
||||
border-bottom: 1px solid darken(color-navy, 5%)
|
||||
margin: -1px 0
|
||||
background: var(--color-content-bg)
|
||||
overflow: hidden
|
||||
|
||||
.video-container
|
||||
text-align: center
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
padding: 24px 0
|
||||
color: rgba(255, 255, 255, 0.6)
|
||||
position: relative
|
||||
background: color-navy
|
||||
background: var(--color-navy)
|
||||
text-align: center
|
||||
@media mq-normal
|
||||
padding: 40px 0
|
||||
|
||||
+12
-9
@@ -29,14 +29,13 @@ logo-size = 36px
|
||||
#main-nav
|
||||
display: none
|
||||
flex: 1 auto
|
||||
padding-left: 20px
|
||||
@media mq-normal
|
||||
display: block
|
||||
|
||||
#search-input-wrap
|
||||
display: none
|
||||
padding-left: 6px
|
||||
border-bottom: 1px solid color-gray
|
||||
border-bottom: 1px solid var(--color-gray)
|
||||
&.on
|
||||
display: inline-block
|
||||
> i, > span, #search-input
|
||||
@@ -58,22 +57,26 @@ logo-size = 36px
|
||||
color: #fff
|
||||
text-decoration: none
|
||||
line-height: logo-size
|
||||
white-space: nowrap
|
||||
opacity: 0.7
|
||||
transition: 0.2s
|
||||
transition: opacity 0.2s, color 0.2s
|
||||
display: inline-block
|
||||
padding: 0 15px
|
||||
i
|
||||
padding-right: 8px
|
||||
padding: 0 12px
|
||||
&.active
|
||||
opacity: 1
|
||||
&:hover
|
||||
opacity: 1
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
text-decoration: none
|
||||
|
||||
#lang-select-wrap
|
||||
#github-link-wrap
|
||||
.icon-nav-link
|
||||
display: none
|
||||
position: relative
|
||||
padding: 0 8px
|
||||
.icon-nav-title
|
||||
display: none
|
||||
@media mq-small
|
||||
display: inline
|
||||
@media mq-normal
|
||||
display: block
|
||||
|
||||
|
||||
+30
-26
@@ -1,21 +1,25 @@
|
||||
// https://github.com/chriskempson/tomorrow-theme
|
||||
highlight-background = #fff
|
||||
highlight-current-line = #efefef
|
||||
highlight-selection = #d6d6d6
|
||||
highlight-foreground = #4d4d4c
|
||||
highlight-comment = #8e908c
|
||||
highlight-red = #c82829
|
||||
highlight-orange = #f5871f
|
||||
highlight-yellow = #eab700
|
||||
highlight-green = #718c00
|
||||
highlight-aqua = #3e999f
|
||||
highlight-blue = #4271ae
|
||||
highlight-purple = #8959a8
|
||||
// Tomorrow Night Eighties
|
||||
:root {
|
||||
--highlight-background: #2d2d2d
|
||||
--highlight-current-line: #393939
|
||||
--highlight-selection: #515151
|
||||
--highlight-foreground: #cccccc
|
||||
--highlight-comment: #999999
|
||||
--highlight-red: #f2777a
|
||||
--highlight-orange: #f99157
|
||||
--highlight-yellow: #ffcc66
|
||||
--highlight-green: #99cc99
|
||||
--highlight-aqua: #66cccc
|
||||
--highlight-blue: #6699cc
|
||||
--highlight-purple: #cc99cc
|
||||
}
|
||||
|
||||
pre, code
|
||||
font-family: font-mono
|
||||
color: highlight-foreground
|
||||
background: #eee
|
||||
color: var(--highlight-foreground)
|
||||
background: var(--highlight-background)
|
||||
white-space: pre-wrap
|
||||
|
||||
code
|
||||
padding: 0 5px
|
||||
@@ -29,9 +33,9 @@ pre
|
||||
padding: 0
|
||||
|
||||
.highlight
|
||||
background: #eee
|
||||
padding: 10px 15px
|
||||
color: highlight-foreground
|
||||
color: var(--highlight-foreground)
|
||||
background: var(--highlight-background)
|
||||
overflow: auto
|
||||
margin: 0
|
||||
table
|
||||
@@ -42,12 +46,12 @@ pre
|
||||
figcaption
|
||||
clearfix()
|
||||
margin: -5px 0 5px
|
||||
color: color-gray
|
||||
color: var(--color-gray)
|
||||
a
|
||||
float: right
|
||||
pre
|
||||
margin: 0
|
||||
padding: 0
|
||||
border: none
|
||||
background: none
|
||||
.line
|
||||
height: 22px
|
||||
@@ -55,7 +59,7 @@ pre
|
||||
pre
|
||||
.comment
|
||||
.title
|
||||
color: highlight-comment
|
||||
color: var(--highlight-comment)
|
||||
.variable
|
||||
.attribute
|
||||
.tag
|
||||
@@ -68,27 +72,27 @@ pre
|
||||
.css .id
|
||||
.css .class
|
||||
.css .pseudo
|
||||
color: highlight-red
|
||||
color: var(--highlight-red)
|
||||
.number
|
||||
.preprocessor
|
||||
.built_in
|
||||
.literal
|
||||
.params
|
||||
.constant
|
||||
color: highlight-orange
|
||||
color: var(--highlight-orange)
|
||||
.class
|
||||
.ruby .class .title
|
||||
.css .rules .attribute
|
||||
color: highlight-green
|
||||
color: var(--highlight-green)
|
||||
.string
|
||||
.value
|
||||
.inheritance
|
||||
.header
|
||||
.ruby .symbol
|
||||
.xml .cdata
|
||||
color: highlight-green
|
||||
color: var(--highlight-green)
|
||||
.css .hexcolor
|
||||
color: highlight-aqua
|
||||
color: var(--highlight-aqua)
|
||||
.function
|
||||
.python .decorator
|
||||
.python .title
|
||||
@@ -97,7 +101,7 @@ pre
|
||||
.perl .sub
|
||||
.javascript .title
|
||||
.coffeescript .title
|
||||
color: highlight-blue
|
||||
color: var(--highlight-blue)
|
||||
.keyword
|
||||
.javascript .function
|
||||
color: highlight-purple
|
||||
color: var(--highlight-purple)
|
||||
+35
-35
@@ -30,7 +30,7 @@
|
||||
font-size: 18px
|
||||
|
||||
#banner-start-command
|
||||
background: lighten(color-navy, 10%)
|
||||
background: var(--color-navy-lighter)
|
||||
padding: 0 15px
|
||||
color: #fff
|
||||
display: inline-block
|
||||
@@ -41,20 +41,20 @@
|
||||
|
||||
#banner-start-link
|
||||
color: #fff
|
||||
background: color-link
|
||||
background: var(--color-link)
|
||||
display: inline-block
|
||||
padding: 0 15px
|
||||
text-decoration: none
|
||||
transition: 0.2s
|
||||
transition: background 0.2s
|
||||
&:hover
|
||||
background: color-link-hover
|
||||
background: var(--color-link-hover)
|
||||
|
||||
#banner-share
|
||||
display: block
|
||||
padding-bottom: 60px
|
||||
|
||||
#intro-news-list
|
||||
background-color: darken(color-navy, 10%)
|
||||
background-color: var(--color-news-bg)
|
||||
margin: -1px 0
|
||||
|
||||
#intro-news-flex
|
||||
@@ -67,7 +67,7 @@
|
||||
padding: 24px 30px
|
||||
text-decoration: none
|
||||
&:hover
|
||||
background-color: color-link-hover
|
||||
background-color: var(--color-link-hover)
|
||||
@media mq-normal
|
||||
flex: 0 0 50%
|
||||
padding: 36px 30px
|
||||
@@ -108,7 +108,7 @@
|
||||
padding-left: 70px
|
||||
|
||||
.intro-feature-icon
|
||||
color: color-link
|
||||
color: var(--color-link)
|
||||
font-size: 36px
|
||||
margin-bottom: 6px;
|
||||
text-align: center
|
||||
@@ -121,7 +121,7 @@
|
||||
width: @font-size
|
||||
|
||||
.intro-feature-title
|
||||
color: color-link
|
||||
color: var(--color-link)
|
||||
font-size: 24px
|
||||
|
||||
.intro-feature-desc
|
||||
@@ -136,29 +136,21 @@
|
||||
color: black
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
@media (prefers-color-scheme: dark)
|
||||
color: white
|
||||
&:visited, &:hover
|
||||
color: white
|
||||
|
||||
#intro-cmd-wrap
|
||||
max-width: 700px
|
||||
background: #eee
|
||||
padding: 15px 0
|
||||
padding: 15px 30px
|
||||
font-size: 16px
|
||||
pre
|
||||
line-height: 32px
|
||||
overflow: auto
|
||||
margin: 50px auto 0;
|
||||
@media mq-normal
|
||||
margin: 70px auto 0;
|
||||
li
|
||||
font-size: 16px
|
||||
font-family: font-mono
|
||||
line-height: 2
|
||||
padding: 0 30px
|
||||
|
||||
.intro-code-item
|
||||
white-space: pre
|
||||
.hl
|
||||
color: color-link
|
||||
.hl-str
|
||||
color: #9e0001ad
|
||||
.hl-comment
|
||||
color: #056d05b5
|
||||
|
||||
#intro-get-started-wrap
|
||||
text-align: center
|
||||
@@ -170,17 +162,17 @@
|
||||
font-size: 18px
|
||||
display: inline-block
|
||||
text-decoration: none
|
||||
border: 3px solid lighten(color-link, 20%)
|
||||
border: 3px solid var(--color-link-hover)
|
||||
padding: 12px 24px
|
||||
position: relative
|
||||
transition: 0.2s
|
||||
color: color-link
|
||||
transition: background 0.2s, color 0.2s
|
||||
color: var(--color-link)
|
||||
&:hover
|
||||
background: lighten(color-link, 20%)
|
||||
background: var(--color-link-hover)
|
||||
color: #fff
|
||||
|
||||
#sponsors-wrap, #contributors-wrap
|
||||
background: lighten(color-navy, 10%)
|
||||
background: var(--color-navy-lighter)
|
||||
border-top: 1px solid #161d24
|
||||
border-bottom: 1px solid #161d24
|
||||
margin: -1px 0
|
||||
@@ -210,29 +202,37 @@
|
||||
display: inline-block
|
||||
height: 72px
|
||||
width: 72px
|
||||
background: color-gray
|
||||
background: var(--color-gray)
|
||||
border-radius: 50%
|
||||
margin-bottom: 10px
|
||||
.description a
|
||||
color: #fff
|
||||
&:hover
|
||||
background: #fff
|
||||
color: lighten(color-navy, 10%)
|
||||
color: var(--color-navy-lighter)
|
||||
|
||||
#sponsors-wrap
|
||||
background: #fff
|
||||
background: var(--color-content-bg)
|
||||
border: none
|
||||
text-align: right
|
||||
overflow: hidden;
|
||||
.inner
|
||||
h3
|
||||
color: color-default
|
||||
color: var(--color-default)
|
||||
p
|
||||
color: color-gray
|
||||
color: var(--color-gray)
|
||||
.open-collective
|
||||
margin: 0 auto
|
||||
.description a
|
||||
color: #333
|
||||
&:hover
|
||||
background: color-navy
|
||||
background: var(--color-navy)
|
||||
color: #fff
|
||||
@media (prefers-color-scheme: dark)
|
||||
p
|
||||
color: var(--color-default)
|
||||
.description a
|
||||
color: #fff
|
||||
&:hover
|
||||
background: #fff
|
||||
color: var(--color-navy-lighter)
|
||||
|
||||
+13
-14
@@ -15,13 +15,13 @@ lang-select-height = 40px
|
||||
width: mobile-nav-width
|
||||
left: @width * -1
|
||||
height: 100%
|
||||
background: color-navy
|
||||
background: var(--color-navy)
|
||||
transition: transition-duration
|
||||
.mobile-nav-on &
|
||||
transform: translateX(100%)
|
||||
|
||||
.mobile-sidebar-list
|
||||
background: lighten(color-navy, 10%)
|
||||
background: var(--color-navy-lighter)
|
||||
|
||||
#mobile-nav-inner
|
||||
overflow-y: auto
|
||||
@@ -78,14 +78,14 @@ lang-select-height = 40px
|
||||
color: #fff
|
||||
text-decoration: none
|
||||
display: block
|
||||
padding: 10px 15px
|
||||
padding: 12px 16px
|
||||
line-height: 1
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
.mobile-nav-title
|
||||
color: color-link
|
||||
color: var(--color-link)
|
||||
font-weight: bold
|
||||
padding: 10px 15px
|
||||
line-height: 1
|
||||
@@ -96,22 +96,21 @@ lang-select-height = 40px
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
#mobile-lang-select-wrap
|
||||
#mobile-button-list
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
background: color-navy
|
||||
background: var(--color-navy)
|
||||
border-top: 1px solid #444
|
||||
padding: 12px 16px
|
||||
.mobile-nav-link
|
||||
display: inline-block
|
||||
padding: 0
|
||||
margin-right: 16px
|
||||
|
||||
#mobile-lang-select-label
|
||||
line-height: lang-select-height
|
||||
color: #fff
|
||||
padding: 10px 15px
|
||||
i
|
||||
opacity: 0.7
|
||||
span
|
||||
padding-left: 8px
|
||||
#mobile-lang-select-wrap
|
||||
position: relative
|
||||
|
||||
#mobile-lang-select
|
||||
-webkit-appearance: menulist-button
|
||||
|
||||
+20
-27
@@ -53,10 +53,10 @@ note-warn = hsl(0, 100%, 50%)
|
||||
margin-top: 13px
|
||||
font-size: 0.9em
|
||||
text-decoration: none
|
||||
color: color-default
|
||||
color: var(--color-default)
|
||||
display: block
|
||||
&:hover
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
|
||||
.article-header
|
||||
padding-bottom: 20px
|
||||
@@ -67,20 +67,20 @@ note-warn = hsl(0, 100%, 50%)
|
||||
font-size: 36px
|
||||
font-weight: 300
|
||||
text-decoration: none
|
||||
color: color-default
|
||||
color: var(--color-default)
|
||||
transition: 0.2s
|
||||
a&:hover
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
.post &
|
||||
float: none
|
||||
|
||||
.article-date
|
||||
color: color-gray
|
||||
color: var(--color-gray)
|
||||
text-decoration: none
|
||||
display: inline-block
|
||||
margin-top: 1em
|
||||
&:hover
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
|
||||
.article-edit-link
|
||||
float: right
|
||||
@@ -91,7 +91,7 @@ note-warn = hsl(0, 100%, 50%)
|
||||
transition: 0.2s
|
||||
display: none
|
||||
&:hover
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
@media mq-normal
|
||||
display: block
|
||||
|
||||
@@ -107,7 +107,7 @@ note-warn = hsl(0, 100%, 50%)
|
||||
|
||||
.article-content
|
||||
line-height: line-height
|
||||
color: color-default
|
||||
color: var(--color-default)
|
||||
@media print
|
||||
font-size: 12pt
|
||||
p, ol, ul, dl, table, blockquote, iframe, .highlight
|
||||
@@ -116,7 +116,7 @@ note-warn = hsl(0, 100%, 50%)
|
||||
font-size: 2em
|
||||
h2
|
||||
font-size: 1.5em
|
||||
border-bottom: 1px solid color-border
|
||||
border-bottom: 1px solid var(--color-border)
|
||||
padding-bottom: 10px
|
||||
margin-bottom: 15px
|
||||
h3
|
||||
@@ -127,19 +127,8 @@ note-warn = hsl(0, 100%, 50%)
|
||||
margin: 1em 0
|
||||
hr
|
||||
border-bottom: none;
|
||||
border-top: 1px dashed color-border;
|
||||
border-top: 1px dashed var(--color-border);
|
||||
a
|
||||
color: color-link
|
||||
text-decoration: none
|
||||
&:hover
|
||||
color: color-link-hover
|
||||
text-decoration: underline
|
||||
@media print
|
||||
color: color-default
|
||||
text-decoration: underline
|
||||
&:after
|
||||
content: " (" attr(href) ")"
|
||||
font-size: 80%
|
||||
&[target="_blank"]
|
||||
position: relative
|
||||
&::after
|
||||
@@ -175,7 +164,7 @@ note-warn = hsl(0, 100%, 50%)
|
||||
blockquote
|
||||
padding: 0 20px
|
||||
position: relative
|
||||
border: 1px solid color-border
|
||||
border: 1px solid var(--color-border)
|
||||
border-left: 5px solid #ddd
|
||||
footer
|
||||
margin: 1em 0
|
||||
@@ -186,6 +175,8 @@ note-warn = hsl(0, 100%, 50%)
|
||||
padding: 0 0.3em
|
||||
a
|
||||
color: color-grey
|
||||
@media (prefers-color-scheme: dark)
|
||||
border-color: var(--color-gray)
|
||||
.note
|
||||
&.tip
|
||||
border-left-color: note-tip
|
||||
@@ -207,7 +198,7 @@ note-warn = hsl(0, 100%, 50%)
|
||||
margin-top: 0
|
||||
table
|
||||
max-width: 100%
|
||||
border: 1px solid color-border
|
||||
border: 1px solid var(--color-border)
|
||||
th
|
||||
font-weight: bold
|
||||
th, td
|
||||
@@ -215,12 +206,14 @@ note-warn = hsl(0, 100%, 50%)
|
||||
tr
|
||||
&:nth-child(2n)
|
||||
background: #eee
|
||||
@media (prefers-color-scheme: dark)
|
||||
background: var(--color-gray)
|
||||
|
||||
.article-footer
|
||||
margin: 60px 0 0
|
||||
border-top: 1px solid color-border
|
||||
border-top: 1px solid var(--color-border)
|
||||
text-align: center
|
||||
color: color-gray
|
||||
color: var(--color-gray)
|
||||
line-height: 1em
|
||||
padding-top: 1em
|
||||
position: relative
|
||||
@@ -228,13 +221,13 @@ note-warn = hsl(0, 100%, 50%)
|
||||
display: none
|
||||
|
||||
$article-footer-link
|
||||
color: color-link
|
||||
color: var(--color-link)
|
||||
text-decoration: none
|
||||
font-weight: bold
|
||||
text-transform: uppercase
|
||||
position: absolute
|
||||
&:hover
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
@media print
|
||||
display: none
|
||||
span
|
||||
|
||||
+19
-3
@@ -1,6 +1,8 @@
|
||||
#playground
|
||||
background: white
|
||||
background: var(--color-content-bg)
|
||||
overflow: hidden
|
||||
.wrapper
|
||||
margin-bottom: 40px
|
||||
h1
|
||||
font-size: 36px
|
||||
font-weight: 300
|
||||
@@ -10,11 +12,10 @@
|
||||
display: flex
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between
|
||||
margin-bottom: 30px
|
||||
font-size: 1em
|
||||
.col
|
||||
flex: 1 1 100%
|
||||
margin-bottom: 30px
|
||||
margin-bottom: 10px
|
||||
|
||||
@media mq-normal
|
||||
.row
|
||||
@@ -29,3 +30,18 @@
|
||||
margin-top: 2vh
|
||||
#previewEl
|
||||
height: 70vh
|
||||
.hide
|
||||
display: none
|
||||
.loader
|
||||
width: 75px
|
||||
height: 75px
|
||||
margin: 150px auto 200px
|
||||
border-top: 5px solid #292929
|
||||
border-right: 5px solid #efefef
|
||||
border-bottom: 5px solid #efefef
|
||||
border-left: 5px solid #efefef
|
||||
border-radius: 100px
|
||||
animation: spin 1s infinite linear
|
||||
@keyframes spin
|
||||
100%
|
||||
transform: rotate(360deg)
|
||||
|
||||
+5
-5
@@ -11,22 +11,22 @@
|
||||
margin-top: 40px
|
||||
padding: 10px 0
|
||||
font-weight: bold
|
||||
color: color-link
|
||||
color: var(--color-link)
|
||||
display: inline-block
|
||||
border-top: 1px solid color-border
|
||||
border-top: 1px solid var(--color-border)
|
||||
line-height: 1
|
||||
.sidebar-title:first-child
|
||||
margin-top: 0
|
||||
|
||||
.sidebar-link
|
||||
display: block
|
||||
color: color-default
|
||||
color: var(--color-default)
|
||||
text-decoration: none
|
||||
padding: 7px 0
|
||||
line-height: 1
|
||||
position: relative
|
||||
width: 100%
|
||||
&.current
|
||||
color: color-link
|
||||
color: var(--color-link)
|
||||
&:hover
|
||||
color: color-link-hover
|
||||
color: var(--color-link-hover)
|
||||
+30
-7
@@ -3,12 +3,34 @@ support-for-ie = false
|
||||
vendor-prefixes = webkit moz ms official
|
||||
|
||||
// Colors
|
||||
color-default = #444
|
||||
color-gray = #999
|
||||
color-border = #e3e3e3
|
||||
color-navy = hsl(210, 25%, 12%)
|
||||
color-link = #0e83cd
|
||||
color-link-hover = lighten(color-link, 10%)
|
||||
:root {
|
||||
--color-default: #444
|
||||
--color-default-invert: #d1d2d4
|
||||
--color-gray: #999
|
||||
--color-border: #e3e3e3
|
||||
--color-navy: hsl(210, 25%, 12%)
|
||||
--color-content-bg: #fff
|
||||
--color-news-bg: darken(hsl(210, 25%, 12%), 5%)
|
||||
--color-navy-lighter: lighten(hsl(210, 25%, 12%), 10%)
|
||||
--color-link: #0e83cd
|
||||
--color-link-hover: lighten(#0e83cd, 10%)
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-default: #d1d2d4
|
||||
--color-default-invert: #444
|
||||
--color-gray: invert(#999)
|
||||
--color-border: #6a6a6a
|
||||
--color-backgrond: #fff
|
||||
--color-navy: hsl(210, 25%, 12%)
|
||||
--color-news-bg: darken(hsl(210, 25%, 12%), 5%)
|
||||
--color-navy-lighter: lighten(hsl(210, 25%, 12%), 7%)
|
||||
--color-content-bg: #1c1c1c
|
||||
--color-link: #0e83cd
|
||||
--color-link-hover: lighten(#0e83cd, 10%)
|
||||
}
|
||||
}
|
||||
|
||||
// Typography
|
||||
font-sans = "Helvetica Neue", Helvetica, Arial, sans-serif
|
||||
@@ -18,7 +40,7 @@ font-size = 15px
|
||||
line-height = 1.6em
|
||||
|
||||
// Layout
|
||||
max-width = 1200px
|
||||
max-width = 1800px
|
||||
gutter-width = 20px
|
||||
sidebar-width = 220px
|
||||
mobile-nav-width = 260px
|
||||
@@ -26,4 +48,5 @@ mobile-nav-width = 260px
|
||||
// Media queries
|
||||
mq-mobile = "screen and (max-width: 768px)"
|
||||
mq-normal = "screen and (min-width: 769px)"
|
||||
mq-small = "screen and (min-width: 992px)"
|
||||
mq-tablet = "screen and (min-width: 480px)"
|
||||
|
||||
Vendored
+19
-1
@@ -59,6 +59,7 @@
|
||||
// playground
|
||||
/* global liquidjs, ace */
|
||||
if (!location.pathname.match(/playground.html$/)) return;
|
||||
updateVersion(liquidjs.version);
|
||||
const engine = new liquidjs.Liquid();
|
||||
const editor = createEditor('editorEl', 'liquid');
|
||||
const dataEditor = createEditor('dataEl', 'json');
|
||||
@@ -75,9 +76,20 @@
|
||||
editor.on('change', update);
|
||||
dataEditor.on('change', update);
|
||||
update();
|
||||
ready();
|
||||
|
||||
function ready() {
|
||||
const loader = document.querySelector('.loader');
|
||||
loader.classList.add('hide');
|
||||
loader.setAttribute('aria-busy', false);
|
||||
|
||||
const editors = document.querySelector('#editors');
|
||||
editors.classList.remove('hide');
|
||||
editors.setAttribute('aria-hide', false);
|
||||
}
|
||||
|
||||
function createEditor(id, lang) {
|
||||
var editor = ace.edit(id);
|
||||
const editor = ace.edit(id);
|
||||
editor.setTheme('ace/theme/tomorrow_night');
|
||||
editor.getSession().setMode('ace/mode/' + lang);
|
||||
editor.getSession().setOptions({
|
||||
@@ -121,6 +133,12 @@
|
||||
function utoa(str) {
|
||||
return btoa(unescape(encodeURIComponent(str)));
|
||||
}
|
||||
|
||||
function updateVersion(version) {
|
||||
document.querySelector('.version').innerHTML = `
|
||||
liquidjs@<a target="_blank" href="https://www.npmjs.com/package/liquidjs/v/${version}">${version}</a>
|
||||
`
|
||||
}
|
||||
}());
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
declare module 'rollup-plugin-uglify' {
|
||||
export function uglify (): any
|
||||
export function uglify (options?: any): any
|
||||
}
|
||||
declare module 'rollup-plugin-replace' {
|
||||
export default function replace(options: any): any
|
||||
|
||||
Generated
+6987
-10572
File diff suppressed because it is too large
Load Diff
+38
-16
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "liquidjs",
|
||||
"version": "9.28.0",
|
||||
"version": "9.39.2",
|
||||
"description": "A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.",
|
||||
"main": "dist/liquid.node.cjs.js",
|
||||
"module": "dist/liquid.node.esm.js",
|
||||
@@ -12,13 +12,15 @@
|
||||
"types": "dist/liquid.d.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint \"**/*.ts\" .",
|
||||
"check": "npm test && npm run lint",
|
||||
"check": "npm run build && npm test && npm run lint && npm run perf:diff",
|
||||
"test": "nyc mocha \"test/**/*.ts\"",
|
||||
"test:e2e": "mocha \"test/e2e/**/*.ts\"",
|
||||
"perf": "cd benchmark && npm ci && npm start",
|
||||
"perf:diff": "bin/perf-diff.sh",
|
||||
"perf:engines": "cd benchmark && npm run engines",
|
||||
"postversion": "npm run build:dist",
|
||||
"build": "npm run build:dist && npm run build:docs",
|
||||
"build:dist": "rollup -c rollup.config.ts && ls -lh dist",
|
||||
"build:dist": "rollup -c rollup.config.ts",
|
||||
"build:docs": "bin/build-docs.sh"
|
||||
},
|
||||
"bin": {
|
||||
@@ -72,15 +74,15 @@
|
||||
"@types/sinon": "^7.0.6",
|
||||
"@types/sinon-chai": "^3.2.2",
|
||||
"@types/supertest": "^2.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^2.0.0",
|
||||
"@typescript-eslint/parser": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"all-contributors-cli": "^6.20.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"coveralls": "^3.0.2",
|
||||
"cross-env": "^5.2.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.15.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
@@ -89,23 +91,24 @@
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"express": "^4.16.4",
|
||||
"husky": "^4.2.5",
|
||||
"jsdom": "^13.2.0",
|
||||
"jsdom": "^16.5.0",
|
||||
"mocha": "^9.0.1",
|
||||
"nyc": "^15.1.0",
|
||||
"regenerator-runtime": "^0.12.1",
|
||||
"rollup": "^1.1.2",
|
||||
"rollup": "^2.61.0",
|
||||
"rollup-plugin-replace": "^2.1.0",
|
||||
"rollup-plugin-typescript2": "^0.21.1",
|
||||
"rollup-plugin-uglify": "^5.0.2",
|
||||
"semantic-release": "^17.2.3",
|
||||
"rollup-plugin-typescript2": "^0.31.1",
|
||||
"rollup-plugin-uglify": "^6.0.4",
|
||||
"rollup-plugin-version-injector": "^1.3.3",
|
||||
"semantic-release": "^19.0.3",
|
||||
"sinon": "^7.5.0",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"supertest": "^3.4.2",
|
||||
"ts-node": "^8.0.2",
|
||||
"tslib": "^1.9.3",
|
||||
"typedoc": "^0.15.0",
|
||||
"tslib": "^2.3.1",
|
||||
"typedoc": "^0.19.2",
|
||||
"typedoc-plugin-markdown": "^2.2.17",
|
||||
"typescript": "^3.3.3"
|
||||
"typescript": "^4.5.3"
|
||||
},
|
||||
"release": {
|
||||
"branch": "master",
|
||||
@@ -125,7 +128,25 @@
|
||||
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "dist/*.umd.js",
|
||||
"label": "liquid.js"
|
||||
},
|
||||
{
|
||||
"path": "dist/*.min.js",
|
||||
"label": "liquid.min.js"
|
||||
},
|
||||
{
|
||||
"path": "dist/*.min.js.map",
|
||||
"label": "liquid.min.js.map"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"nyc": {
|
||||
@@ -138,5 +159,6 @@
|
||||
"pre-commit": "npm run check",
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
+50
-59
@@ -2,6 +2,7 @@ import { uglify } from 'rollup-plugin-uglify'
|
||||
import pkg from './package.json'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
import replace from 'rollup-plugin-replace'
|
||||
import versionInjector from 'rollup-plugin-version-injector'
|
||||
|
||||
const version = process.env.VERSION || pkg.version
|
||||
const sourcemap = true
|
||||
@@ -13,17 +14,44 @@ const banner = `/*
|
||||
const treeshake = {
|
||||
propertyReadSideEffects: false
|
||||
}
|
||||
const tsconfig = {
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'es5',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
}
|
||||
const versionInjection = versionInjector({
|
||||
injectInComments: false,
|
||||
injectInTags: {
|
||||
fileRegexp: /\.(ts|js|html|css)$/,
|
||||
tagId: 'VI',
|
||||
dateFormat: 'mmmm d, yyyy HH:MM:ss'
|
||||
},
|
||||
packageJson: './package.json',
|
||||
logLevel: 'info',
|
||||
logger: console,
|
||||
exclude: []
|
||||
})
|
||||
const input = './src/liquid.ts'
|
||||
const replaceFS = {
|
||||
const browserFS = {
|
||||
include: './src/liquid-options.ts',
|
||||
delimiters: ['', ''],
|
||||
'./fs/node': './fs/browser'
|
||||
}
|
||||
const replaceStream = {
|
||||
const browserStream = {
|
||||
include: './src/render/render.ts',
|
||||
delimiters: ['', ''],
|
||||
'../emitters/streamed-emitter': '../emitters/streamed-emitter-browser'
|
||||
}
|
||||
const esmRequire = {
|
||||
include: './src/fs/node.ts',
|
||||
delimiters: ['', ''],
|
||||
'./node-require': './node-require.mjs'
|
||||
}
|
||||
|
||||
const nodeCjs = {
|
||||
output: [{
|
||||
@@ -32,16 +60,7 @@ const nodeCjs = {
|
||||
banner
|
||||
}],
|
||||
external: ['path', 'fs'],
|
||||
plugins: [typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'ES2017',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
})],
|
||||
plugins: [versionInjection, typescript(tsconfig)],
|
||||
treeshake,
|
||||
input
|
||||
}
|
||||
@@ -53,16 +72,11 @@ const nodeEsm = {
|
||||
banner
|
||||
}],
|
||||
external: ['path', 'fs'],
|
||||
plugins: [typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'ES2017',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
})],
|
||||
plugins: [
|
||||
versionInjection,
|
||||
replace(esmRequire),
|
||||
typescript(tsconfig)
|
||||
],
|
||||
treeshake,
|
||||
input
|
||||
}
|
||||
@@ -75,18 +89,10 @@ const browserEsm = {
|
||||
}],
|
||||
external: ['path', 'fs'],
|
||||
plugins: [
|
||||
replace(replaceFS),
|
||||
replace(replaceStream),
|
||||
typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'ES2017',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
})
|
||||
versionInjection,
|
||||
replace(browserFS),
|
||||
replace(browserStream),
|
||||
typescript(tsconfig)
|
||||
],
|
||||
treeshake,
|
||||
input
|
||||
@@ -101,18 +107,10 @@ const browserUmd = {
|
||||
banner
|
||||
}],
|
||||
plugins: [
|
||||
replace(replaceFS),
|
||||
replace(replaceStream),
|
||||
typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'es5',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
})
|
||||
versionInjection,
|
||||
replace(browserFS),
|
||||
replace(browserStream),
|
||||
typescript(tsconfig)
|
||||
],
|
||||
treeshake,
|
||||
input
|
||||
@@ -123,21 +121,14 @@ const browserMin = {
|
||||
file: 'dist/liquid.browser.min.js',
|
||||
name: 'liquidjs',
|
||||
format: 'umd',
|
||||
sourcemap
|
||||
sourcemap,
|
||||
banner
|
||||
}],
|
||||
plugins: [
|
||||
replace(replaceFS),
|
||||
replace(replaceStream),
|
||||
typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'es5',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
}),
|
||||
versionInjection,
|
||||
replace(browserFS),
|
||||
replace(browserStream),
|
||||
typescript(tsconfig),
|
||||
uglify()
|
||||
],
|
||||
treeshake,
|
||||
|
||||
@@ -1,53 +1,75 @@
|
||||
import { isArray, isNil, last as arrayLast } from '../../util/underscore'
|
||||
import { argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, hasOwnProperty } from '../../util/underscore'
|
||||
import { toArray } from '../../util/collection'
|
||||
import { isTruthy } from '../../render/boolean'
|
||||
import { FilterImpl } from '../../template/filter/filter-impl'
|
||||
import { Scope } from '../../context/scope'
|
||||
import { isComparable } from '../../drop/comparable'
|
||||
|
||||
export const join = (v: any[], arg: string) => v.join(arg === undefined ? ' ' : arg)
|
||||
export const last = (v: any) => isArray(v) ? arrayLast(v) : ''
|
||||
export const first = (v: any) => isArray(v) ? v[0] : ''
|
||||
export const reverse = (v: any[]) => [...v].reverse()
|
||||
export const join = argumentsToValue((v: any[], arg: string) => toArray(v).join(arg === undefined ? ' ' : arg))
|
||||
export const last = argumentsToValue((v: any) => isArray(v) ? arrayLast(v) : '')
|
||||
export const first = argumentsToValue((v: any) => isArray(v) ? v[0] : '')
|
||||
export const reverse = argumentsToValue((v: any[]) => [...toArray(v)].reverse())
|
||||
|
||||
export function sort<T> (this: FilterImpl, arr: T[], property?: string) {
|
||||
const getValue = (obj: Scope) => property ? this.context.getFromScope(obj, property.split('.')) : obj
|
||||
return toArray(arr).sort((lhs, rhs) => {
|
||||
arr = toValue(arr)
|
||||
const getValue = (obj: Scope) => property ? this.context.getFromScope(obj, stringify(property).split('.')) : obj
|
||||
return [...toArray(arr)].sort((lhs, rhs) => {
|
||||
lhs = getValue(lhs)
|
||||
rhs = getValue(rhs)
|
||||
return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0)
|
||||
})
|
||||
}
|
||||
|
||||
export function sortNatural<T> (input: T[], property?: string) {
|
||||
input = toValue(input)
|
||||
const propertyString = stringify(property)
|
||||
const compare = property === undefined
|
||||
? caseInsensitiveCompare
|
||||
: (lhs: T, rhs: T) => caseInsensitiveCompare(lhs[propertyString], rhs[propertyString])
|
||||
return [...toArray(input)].sort(compare)
|
||||
}
|
||||
|
||||
export const size = (v: string | any[]) => (v && v.length) || 0
|
||||
|
||||
export function map<T1, T2> (this: FilterImpl, arr: Scope[], property: string) {
|
||||
return toArray(arr).map(obj => this.context.getFromScope(obj, property.split('.')))
|
||||
export function map (this: FilterImpl, arr: Scope[], property: string) {
|
||||
arr = toValue(arr)
|
||||
return toArray(arr).map(obj => this.context.getFromScope(obj, stringify(property).split('.')))
|
||||
}
|
||||
|
||||
export function compact<T> (this: FilterImpl, arr: T[]) {
|
||||
return toArray(arr).filter(x => !isNil(x))
|
||||
arr = toValue(arr)
|
||||
return toArray(arr).filter(x => !isNil(toValue(x)))
|
||||
}
|
||||
|
||||
export function concat<T1, T2> (v: T1[], arg: T2[] | T2): (T1 | T2)[] {
|
||||
export function concat<T1, T2> (v: T1[], arg: T2[] = []): (T1 | T2)[] {
|
||||
v = toValue(v)
|
||||
arg = toArray(arg).map(v => toValue(v))
|
||||
return toArray(v).concat(arg)
|
||||
}
|
||||
|
||||
export function slice<T> (v: T[], begin: number, length = 1): T[] {
|
||||
export function slice<T> (v: T[] | string, begin: number, length = 1): T[] | string {
|
||||
v = toValue(v)
|
||||
if (isNil(v)) return []
|
||||
if (!isArray(v)) v = stringify(v)
|
||||
begin = begin < 0 ? v.length + begin : begin
|
||||
return v.slice(begin, begin + length)
|
||||
}
|
||||
|
||||
export function where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): T[] {
|
||||
arr = toValue(arr)
|
||||
return toArray(arr).filter(obj => {
|
||||
const value = this.context.getFromScope(obj, String(property).split('.'))
|
||||
return expected === undefined ? isTruthy(value, this.context) : value === expected
|
||||
const value = this.context.getFromScope(obj, stringify(property).split('.'))
|
||||
if (expected === undefined) return isTruthy(value, this.context)
|
||||
if (isComparable(expected)) return expected.equals(value)
|
||||
return value === expected
|
||||
})
|
||||
}
|
||||
|
||||
export function uniq<T> (arr: T[]): T[] {
|
||||
arr = toValue(arr)
|
||||
const u = {}
|
||||
return (arr || []).filter(val => {
|
||||
if (u.hasOwnProperty(String(val))) return false
|
||||
if (hasOwnProperty.call(u, String(val))) return false
|
||||
u[String(val)] = true
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import strftime from '../../util/strftime'
|
||||
import { isString, isNumber } from '../../util/underscore'
|
||||
import { LiquidDate } from '../../util/liquid-date'
|
||||
import { toValue, stringify, isString, isNumber } from '../../util/underscore'
|
||||
import { FilterImpl } from '../../template/filter/filter-impl'
|
||||
import { TimezoneDate } from '../../util/timezone-date'
|
||||
|
||||
export function date (this: FilterImpl, v: string | Date, arg: string) {
|
||||
const opts = this.context.opts
|
||||
let date: Date
|
||||
let date: LiquidDate
|
||||
v = toValue(v)
|
||||
arg = stringify(arg)
|
||||
if (v === 'now' || v === 'today') {
|
||||
date = new Date()
|
||||
} else if (isNumber(v)) {
|
||||
@@ -29,5 +32,5 @@ export function date (this: FilterImpl, v: string | Date, arg: string) {
|
||||
}
|
||||
|
||||
function isValidDate (date: any): date is Date {
|
||||
return date instanceof Date && !isNaN(date.getTime())
|
||||
return (date instanceof Date || date instanceof TimezoneDate) && !isNaN(date.getTime())
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@ export function escape (str: string) {
|
||||
}
|
||||
|
||||
function unescape (str: string) {
|
||||
return String(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
|
||||
return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
|
||||
}
|
||||
|
||||
export function escapeOnce (str: string) {
|
||||
return escape(unescape(str))
|
||||
return escape(unescape(stringify(str)))
|
||||
}
|
||||
|
||||
export function newlineToBr (v: string) {
|
||||
return v.replace(/\n/g, '<br />\n')
|
||||
return stringify(v).replace(/\n/g, '<br />\n')
|
||||
}
|
||||
|
||||
export function stripHtml (v: string) {
|
||||
return v.replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
|
||||
return stringify(v).replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ export * from './math'
|
||||
export * from './url'
|
||||
export * from './array'
|
||||
export * from './date'
|
||||
export * from './object'
|
||||
export * from './misc'
|
||||
export * from './string'
|
||||
|
||||
+14
-20
@@ -1,30 +1,24 @@
|
||||
import { caseInsensitiveCompare } from '../../util/underscore'
|
||||
import { toValue, argumentsToValue } from '../../util/underscore'
|
||||
|
||||
export const abs = Math.abs
|
||||
export const atLeast = Math.max
|
||||
export const atMost = Math.min
|
||||
export const ceil = Math.ceil
|
||||
export const dividedBy = (v: number, arg: number) => v / arg
|
||||
export const floor = Math.floor
|
||||
export const minus = (v: number, arg: number) => v - arg
|
||||
export const modulo = (v: number, arg: number) => v % arg
|
||||
export const times = (v: number, arg: number) => v * arg
|
||||
export const abs = argumentsToValue(Math.abs)
|
||||
export const atLeast = argumentsToValue(Math.max)
|
||||
export const atMost = argumentsToValue(Math.min)
|
||||
export const ceil = argumentsToValue(Math.ceil)
|
||||
export const dividedBy = 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 function round (v: number, arg = 0) {
|
||||
v = toValue(v)
|
||||
arg = toValue(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)
|
||||
}
|
||||
|
||||
export function sortNatural (input: any[], property?: string) {
|
||||
if (!input || !input.sort) return []
|
||||
if (property !== undefined) {
|
||||
return [...input].sort(
|
||||
(lhs, rhs) => caseInsensitiveCompare(lhs[property], rhs[property])
|
||||
)
|
||||
}
|
||||
return [...input].sort(caseInsensitiveCompare)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user