Skip to content

Commit 9107eb1

Browse files
committed
feat: support Jekyll style where, #768
1 parent 11f013b commit 9107eb1

File tree

9 files changed

+95
-4
lines changed

9 files changed

+95
-4
lines changed

docs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.0.0",
44
"private": true,
55
"hexo": {
6-
"version": "7.3.0"
6+
"version": "5.4.0"
77
},
88
"scripts": {
99
"build": "hexo generate",
@@ -34,4 +34,4 @@
3434
"engines": {
3535
"node": ">=8.10.0"
3636
}
37-
}
37+
}

docs/source/filters/where.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,30 @@ Output
103103
- 3
104104
```
105105

106+
## Jekyll style
107+
108+
{% since %}v10.19.0{% endsince %}
109+
110+
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.
111+
112+
```javascript
113+
const pages = [
114+
{ tags: ["cat", "food"], title: 'Cat Food' },
115+
{ tags: ["dog", "food"], title: 'Dog Food' },
116+
]
117+
```
118+
119+
Input
120+
```liquid
121+
{% assign selected = pages | where: 'tags', "cat" %}
122+
{% for item in selected -%}
123+
- {{ item.title }}
124+
{% endfor %}
125+
```
126+
127+
Output
128+
```text
129+
Cat Food
130+
```
131+
106132
[truthy]: ../tutorials/truthy-and-falsy.html

docs/source/zh-cn/filters/where.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,32 @@ const products = [
103103
- 3
104104
```
105105

106+
## Jekyll 风格
107+
108+
{% since %}v10.19.0{% endsince %}
109+
110+
对于从 Jekyll 迁移到 Liquid 的用户,有一个 `jekyllWhere` 选项可以模拟 Jekyll 的 `where` 过滤器的行为。该选项默认设置为 `false`。启用后,如果 `property` 是一个数组,目标值将使用 `Array.includes` 而不是 `==` 进行匹配,这在过滤标签时特别有用。
111+
112+
例如,以下代码:
113+
114+
```javascript
115+
const pages = [
116+
{ tags: ["cat", "food"], title: 'Cat Food' },
117+
{ tags: ["dog", "food"], title: 'Dog Food' },
118+
]
119+
```
120+
121+
输入
122+
```liquid
123+
{% assign selected = pages | where: 'tags', "cat" %}
124+
{% for item in selected -%}
125+
- {{ item.title }}
126+
{% endfor %}
127+
```
128+
129+
输出
130+
```text
131+
Cat Food
132+
```
106133

107134
[truthy]: ../tutorials/truthy-and-falsy.html

src/drop/blank-drop.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ export class BlankDrop extends EmptyDrop {
88
if (isString(value)) return /^\s*$/.test(value)
99
return super.equals(value)
1010
}
11+
static is (value: unknown) {
12+
return value instanceof BlankDrop
13+
}
1114
}

src/drop/empty-drop.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ export class EmptyDrop extends Drop implements Comparable {
2525
public valueOf () {
2626
return ''
2727
}
28+
static is (value: unknown) {
29+
return value instanceof EmptyDrop
30+
}
2831
}

src/filters/array.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast } from '../util'
2-
import { equals, evalToken, isTruthy } from '../render'
2+
import { arrayIncludes, equals, evalToken, isTruthy } from '../render'
33
import { Value, FilterImpl } from '../template'
44
import { Tokenizer } from '../parser'
55
import type { Scope } from '../context'
6+
import { EmptyDrop } from '../drop'
67

78
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
89
const array = toArray(v)
@@ -124,9 +125,12 @@ export function * where<T extends object> (this: FilterImpl, arr: T[], property:
124125
for (const item of arr) {
125126
values.push(yield evalToken(token, this.context.spawn(item)))
126127
}
128+
const matcher = this.context.opts.jekyllWhere
129+
? (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
130+
: (v: any) => equals(v, expected)
127131
return arr.filter((_, i) => {
128132
if (expected === undefined) return isTruthy(values[i], this.context)
129-
return equals(values[i], expected)
133+
return matcher(values[i])
130134
})
131135
}
132136

src/liquid-options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface LiquidOptions {
2222
relativeReference?: boolean;
2323
/** Use jekyll style include, pass parameters to `include` variable of current scope. Defaults to `false`. */
2424
jekyllInclude?: boolean;
25+
/** Use jekyll style where filter, enables array item match. Defaults to `false`. */
26+
jekyllWhere?: boolean;
2527
/** 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 `""`. */
2628
extname?: string;
2729
/** Whether or not to cache resolved templates. Defaults to `false`. */

src/render/operator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ function arrayEquals (lhs: any[], rhs: any[]): boolean {
5858
if (lhs.length !== rhs.length) return false
5959
return !lhs.some((value, i) => !equals(value, rhs[i]))
6060
}
61+
62+
export function arrayIncludes (arr: any[], item: any): boolean {
63+
return arr.some(value => equals(value, item))
64+
}

test/integration/filters/array.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,28 @@ describe('filters/array', function () {
484484
Kitchen products:
485485
`)
486486
})
487+
it('should support nil as target', () => {
488+
const scope = { list: [{ foo: 'FOO' }, { bar: 'BAR', type: 2 }] }
489+
return test('{{list | where: "type", nil | json}}', scope, '[{"foo":"FOO"}]')
490+
})
491+
it('should support empty as target', async () => {
492+
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
493+
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]')
494+
})
495+
it('should not match string with array', async () => {
496+
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
497+
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[]')
498+
})
499+
describe('jekyll style', () => {
500+
it('should not match string with array', async () => {
501+
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
502+
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[{"foo":["FOO","bar"]}]', { jekyllWhere: true })
503+
})
504+
it('should support empty as target', async () => {
505+
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
506+
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]', { jekyllWhere: true })
507+
})
508+
})
487509
})
488510
describe('where_exp', function () {
489511
const products = [

0 commit comments

Comments
 (0)