mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 21:00:40 -07:00
feat: support Jekyll style where, #768
This commit is contained in:
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"hexo": {
|
"hexo": {
|
||||||
"version": "7.3.0"
|
"version": "5.4.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "hexo generate",
|
"build": "hexo generate",
|
||||||
@@ -34,4 +34,4 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,4 +103,30 @@ Output
|
|||||||
- 3
|
- 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Jekyll style
|
||||||
|
|
||||||
|
{% since %}v10.19.0{% endsince %}
|
||||||
|
|
||||||
|
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for filtering tags.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pages = [
|
||||||
|
{ tags: ["cat", "food"], title: 'Cat Food' },
|
||||||
|
{ tags: ["dog", "food"], title: 'Dog Food' },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Input
|
||||||
|
```liquid
|
||||||
|
{% assign selected = pages | where: 'tags', "cat" %}
|
||||||
|
{% for item in selected -%}
|
||||||
|
- {{ item.title }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
```text
|
||||||
|
Cat Food
|
||||||
|
```
|
||||||
|
|
||||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||||
|
|||||||
@@ -103,5 +103,32 @@ const products = [
|
|||||||
- 3
|
- 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Jekyll 风格
|
||||||
|
|
||||||
|
{% since %}v10.19.0{% endsince %}
|
||||||
|
|
||||||
|
对于从 Jekyll 迁移到 Liquid 的用户,有一个 `jekyllWhere` 选项可以模拟 Jekyll 的 `where` 过滤器的行为。该选项默认设置为 `false`。启用后,如果 `property` 是一个数组,目标值将使用 `Array.includes` 而不是 `==` 进行匹配,这在过滤标签时特别有用。
|
||||||
|
|
||||||
|
例如,以下代码:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pages = [
|
||||||
|
{ tags: ["cat", "food"], title: 'Cat Food' },
|
||||||
|
{ tags: ["dog", "food"], title: 'Dog Food' },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
输入
|
||||||
|
```liquid
|
||||||
|
{% assign selected = pages | where: 'tags', "cat" %}
|
||||||
|
{% for item in selected -%}
|
||||||
|
- {{ item.title }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
输出
|
||||||
|
```text
|
||||||
|
Cat Food
|
||||||
|
```
|
||||||
|
|
||||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ export class BlankDrop extends EmptyDrop {
|
|||||||
if (isString(value)) return /^\s*$/.test(value)
|
if (isString(value)) return /^\s*$/.test(value)
|
||||||
return super.equals(value)
|
return super.equals(value)
|
||||||
}
|
}
|
||||||
|
static is (value: unknown) {
|
||||||
|
return value instanceof BlankDrop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,7 @@ export class EmptyDrop extends Drop implements Comparable {
|
|||||||
public valueOf () {
|
public valueOf () {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
static is (value: unknown) {
|
||||||
|
return value instanceof EmptyDrop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast } from '../util'
|
import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast } from '../util'
|
||||||
import { equals, evalToken, isTruthy } from '../render'
|
import { arrayIncludes, equals, evalToken, isTruthy } from '../render'
|
||||||
import { Value, FilterImpl } from '../template'
|
import { Value, FilterImpl } from '../template'
|
||||||
import { Tokenizer } from '../parser'
|
import { Tokenizer } from '../parser'
|
||||||
import type { Scope } from '../context'
|
import type { Scope } from '../context'
|
||||||
|
import { EmptyDrop } from '../drop'
|
||||||
|
|
||||||
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
|
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
|
||||||
const array = toArray(v)
|
const array = toArray(v)
|
||||||
@@ -124,9 +125,12 @@ export function * where<T extends object> (this: FilterImpl, arr: T[], property:
|
|||||||
for (const item of arr) {
|
for (const item of arr) {
|
||||||
values.push(yield evalToken(token, this.context.spawn(item)))
|
values.push(yield evalToken(token, this.context.spawn(item)))
|
||||||
}
|
}
|
||||||
|
const matcher = this.context.opts.jekyllWhere
|
||||||
|
? (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
||||||
|
: (v: any) => equals(v, expected)
|
||||||
return arr.filter((_, i) => {
|
return arr.filter((_, i) => {
|
||||||
if (expected === undefined) return isTruthy(values[i], this.context)
|
if (expected === undefined) return isTruthy(values[i], this.context)
|
||||||
return equals(values[i], expected)
|
return matcher(values[i])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export interface LiquidOptions {
|
|||||||
relativeReference?: boolean;
|
relativeReference?: boolean;
|
||||||
/** Use jekyll style include, pass parameters to `include` variable of current scope. Defaults to `false`. */
|
/** Use jekyll style include, pass parameters to `include` variable of current scope. Defaults to `false`. */
|
||||||
jekyllInclude?: boolean;
|
jekyllInclude?: boolean;
|
||||||
|
/** Use jekyll style where filter, enables array item match. Defaults to `false`. */
|
||||||
|
jekyllWhere?: boolean;
|
||||||
/** Add a extname (if filepath doesn't include one) before template file lookup. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */
|
/** Add a extname (if filepath doesn't include one) before template file lookup. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */
|
||||||
extname?: string;
|
extname?: string;
|
||||||
/** Whether or not to cache resolved templates. Defaults to `false`. */
|
/** Whether or not to cache resolved templates. Defaults to `false`. */
|
||||||
|
|||||||
@@ -58,3 +58,7 @@ function arrayEquals (lhs: any[], rhs: any[]): boolean {
|
|||||||
if (lhs.length !== rhs.length) return false
|
if (lhs.length !== rhs.length) return false
|
||||||
return !lhs.some((value, i) => !equals(value, rhs[i]))
|
return !lhs.some((value, i) => !equals(value, rhs[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function arrayIncludes (arr: any[], item: any): boolean {
|
||||||
|
return arr.some(value => equals(value, item))
|
||||||
|
}
|
||||||
|
|||||||
@@ -484,6 +484,28 @@ describe('filters/array', function () {
|
|||||||
Kitchen products:
|
Kitchen products:
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
it('should support nil as target', () => {
|
||||||
|
const scope = { list: [{ foo: 'FOO' }, { bar: 'BAR', type: 2 }] }
|
||||||
|
return test('{{list | where: "type", nil | json}}', scope, '[{"foo":"FOO"}]')
|
||||||
|
})
|
||||||
|
it('should support empty as target', async () => {
|
||||||
|
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||||
|
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]')
|
||||||
|
})
|
||||||
|
it('should not match string with array', async () => {
|
||||||
|
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||||
|
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[]')
|
||||||
|
})
|
||||||
|
describe('jekyll style', () => {
|
||||||
|
it('should not match string with array', async () => {
|
||||||
|
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||||
|
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[{"foo":["FOO","bar"]}]', { jekyllWhere: true })
|
||||||
|
})
|
||||||
|
it('should support empty as target', async () => {
|
||||||
|
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||||
|
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]', { jekyllWhere: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
describe('where_exp', function () {
|
describe('where_exp', function () {
|
||||||
const products = [
|
const products = [
|
||||||
|
|||||||
Reference in New Issue
Block a user