Skip to content

Commit 47ddc11

Browse files
committed
feat: slugify filter from Jekyll, #443
1 parent 50253a9 commit 47ddc11

File tree

8 files changed

+215
-4
lines changed

8 files changed

+215
-4
lines changed

docs/source/_data/sidebar.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ filters:
8282
shift: shift.html
8383
size: size.html
8484
slice: slice.html
85+
slugify: slugify.html
8586
sort: sort.html
8687
sort_natural: sort_natural.html
8788
split: split.html

docs/source/filters/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Categories | Filters
1111
--- | ---
1212
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
1313
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last,remove, remove_first, remove_last, truncate, truncatewords, normalize_whitespace, number_of_words, array_to_sentence_string
14-
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br, xml_escape, cgi_escape, uri_escape
14+
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br, xml_escape, cgi_escape, uri_escape, slugify
1515
Array | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
1616
Date | date, date_to_xmlschema, date_to_rfc822, date_to_string, date_to_long_string
1717
Misc | default, json, jsonify, inspect, raw, to_integer

docs/source/filters/slugify.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
title: slugify
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
Convert a string into a lowercase URL "slug". The slugify filter accepts 2 options:
8+
9+
1. `mode: string`. The default is `"default"`. They are as follows (with what they filter):
10+
- `"none"`: no characters
11+
- `"raw"`: spaces
12+
- `"default"`: spaces and non-alphanumeric characters
13+
- `"pretty"`: spaces and non-alphanumeric characters except for `._~!$&'()+,;=@`
14+
- `"ascii"`: spaces, non-alphanumeric, and non-ASCII characters
15+
- `"latin"`: like default, except Latin characters are first transliterated (e.g. àèïòü to aeiou).
16+
2. `case: boolean`. The default is `false`. The original case of slug will be retained if set to `true`.
17+
18+
Input
19+
```liquid
20+
{{ "The _config.yml file" | slugify }}
21+
```
22+
Output
23+
```
24+
the-config-yml-file
25+
```
26+
27+
Input
28+
```liquid
29+
{{ "The _config.yml file" | slugify: "pretty" }}
30+
```
31+
Output
32+
```
33+
the-_config.yml-file
34+
```
35+
36+
Input
37+
```liquid
38+
{{ "The _cönfig.yml file" | slugify: "ascii" }}
39+
```
40+
Output
41+
```
42+
the-c-nfig-yml-file
43+
```
44+
45+
Input
46+
```liquid
47+
{{ "The cönfig.yml file" | slugify: "latin" }}
48+
```
49+
Output
50+
```
51+
the-config-yml-file
52+
```
53+
54+
Input
55+
```liquid
56+
{{ "The cönfig.yml file" | slugify: "latin", true }}
57+
```
58+
Output
59+
```
60+
The-config-yml-file
61+
```

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类:
1111
--- | ---
1212
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
1313
字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last, remove, remove_first, remove_last, truncate, truncatewords, normalize_whitespace, number_of_words, array_to_sentence_string
14-
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br, xml_escape, cgi_escape, uri_escape
14+
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br, xml_escape, cgi_escape, uri_escape, slugify
1515
数组 | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
1616
日期 | date, date_to_xmlschema, date_to_rfc822, date_to_string, date_to_long_string
1717
其他 | default, json, jsonify, inspect, raw, to_integer

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: slugify
3+
---
4+
5+
将字符串转换为小写的 URL “slug”。`slugify` 过滤器接受两个选项:
6+
7+
1. `mode: string`。默认为`"default"`,它可选的值如下:
8+
- `"none"`:没有字符
9+
- `"raw"`:空格
10+
- `"default"`:空格和非字母数字字符
11+
- `"pretty"`:空格和非字母数字字符,但排除 `._~!$&'()+,;=@`
12+
- `"ascii"`:空格、非字母数字和非 ASCII 字符
13+
- `"latin"`:与默认相同,但拉丁字符首先进行音译(例如,àèïòü 转换为 aeiou)。
14+
2. `case: boolean`。默认为 `false`。如果为 `true`,则保留 `slug` 原本的大小写。
15+
16+
输入
17+
```liquid
18+
{{ "The _config.yml file" | slugify }}
19+
```
20+
输出
21+
```
22+
the-config-yml-file
23+
```
24+
25+
输入
26+
```liquid
27+
{{ "The _config.yml file" | slugify: "pretty" }}
28+
```
29+
输出
30+
```
31+
the-_config.yml-file
32+
```
33+
34+
输入
35+
```liquid
36+
{{ "The _cönfig.yml file" | slugify: "ascii" }}
37+
```
38+
输出
39+
```
40+
the-c-nfig-yml-file
41+
```
42+
43+
输入
44+
```liquid
45+
{{ "The cönfig.yml file" | slugify: "latin" }}
46+
```
47+
输出
48+
```
49+
the-config-yml-file
50+
```
51+
52+
输入
53+
```liquid
54+
{{ "The cönfig.yml file" | slugify: "latin", true }}
55+
```
56+
输出
57+
```
58+
The-config-yml-file
59+
```

docs/themes/navy/source/css/_variables.styl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ vendor-prefixes = webkit moz ms official
3636
font-sans = "Helvetica Neue", Helvetica, Arial, sans-serif
3737
font-serif = Garamond, Georgia, "Times New Roman", serif
3838
font-mono = "Source Code Pro", Monaco, Menlo, Consolas, monospace
39-
font-size = 15px
40-
line-height = 1.6em
39+
font-size = 16px
40+
line-height = 1.8em
4141

4242
// Layout
4343
max-width = 1800px

src/filters/url.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,44 @@ export const cgi_escape = (x: string) => encodeURIComponent(stringify(x))
88
export const uri_escape = (x: string) => encodeURI(stringify(x))
99
.replace(/%5B/g, '[')
1010
.replace(/%5D/g, ']')
11+
12+
const rSlugifyDefault = /[^\p{M}\p{L}\p{Nd}]+/ug
13+
const rSlugifyReplacers = {
14+
'raw': /\s+/g,
15+
'default': rSlugifyDefault,
16+
'pretty': /[^\p{M}\p{L}\p{Nd}._~!$&'()+,;=@]+/ug,
17+
'ascii': /[^A-Za-z0-9]+/g,
18+
'latin': rSlugifyDefault,
19+
'none': null
20+
}
21+
22+
export function slugify (str: string, mode: keyof typeof rSlugifyReplacers = 'default', cased = false): string {
23+
str = stringify(str)
24+
25+
const replacer = rSlugifyReplacers[mode]
26+
if (replacer) {
27+
if (mode === 'latin') str = removeAccents(str)
28+
str = str.replace(replacer, '-').replace(/^-|-$/g, '')
29+
}
30+
31+
return cased ? str : str.toLowerCase()
32+
}
33+
34+
function removeAccents (str: string): string {
35+
return str.replace(/[àáâãäå]/g, 'a')
36+
.replace(/[æ]/g, 'ae')
37+
.replace(/[ç]/g, 'c')
38+
.replace(/[èéêë]/g, 'e')
39+
.replace(/[ìíîï]/g, 'i')
40+
.replace(/[ð]/g, 'd')
41+
.replace(/[ñ]/g, 'n')
42+
.replace(/[òóôõöø]/g, 'o')
43+
.replace(/[ùúûü]/g, 'u')
44+
.replace(/[ýÿ]/g, 'y')
45+
.replace(/[ß]/g, 'ss')
46+
.replace(/[œ]/g, 'oe')
47+
.replace(/[þ]/g, 'th')
48+
.replace(/[]/g, 'SS')
49+
.replace(/[Œ]/g, 'OE')
50+
.replace(/[Þ]/g, 'TH')
51+
}

test/integration/filters/url.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,53 @@ describe('filters/url', () => {
4242
expect(html).toEqual(reserved)
4343
})
4444
})
45+
46+
describe('slugify', () => {
47+
describe('slugify', () => {
48+
it('should slugify with default mode', () => {
49+
const html = liquid.parseAndRenderSync('{{ "The _config.yml file" | slugify }}')
50+
expect(html).toEqual('the-config-yml-file')
51+
})
52+
53+
it('should slugify with pretty mode', () => {
54+
const html = liquid.parseAndRenderSync('{{ "The _config.yml file" | slugify: "pretty" }}')
55+
expect(html).toEqual('the-_config.yml-file')
56+
})
57+
58+
it('should slugify with ascii mode', () => {
59+
const html = liquid.parseAndRenderSync('{{ "The _cönfig.yml file" | slugify: "ascii" }}')
60+
expect(html).toEqual('the-c-nfig-yml-file')
61+
})
62+
63+
it('should slugify with latin mode', () => {
64+
const html = liquid.parseAndRenderSync('{{ "The cönfig.yml file" | slugify: "latin" }}')
65+
expect(html).toEqual('the-config-yml-file')
66+
})
67+
68+
it('should slugify with none mode', () => {
69+
const html = liquid.parseAndRenderSync('{{ "The _config.yml file" | slugify: "none" }}')
70+
expect(html).toEqual('the _config.yml file')
71+
})
72+
73+
it('should slugify with invalid mode', () => {
74+
const html = liquid.parseAndRenderSync('{{ "The _config.yml file" | slugify: "invalid_mode" }}')
75+
expect(html).toEqual('the _config.yml file')
76+
})
77+
78+
it('should slugify with empty string', () => {
79+
const html = liquid.parseAndRenderSync('{{ "" | slugify }}')
80+
expect(html).toEqual('')
81+
})
82+
83+
it('should slugify with cased=false', () => {
84+
const html = liquid.parseAndRenderSync('{{ "Test String" | slugify: "pretty", false }}')
85+
expect(html).toEqual('test-string')
86+
})
87+
88+
it('should slugify with cased=true', () => {
89+
const html = liquid.parseAndRenderSync('{{ "Test String" | slugify: "pretty", true }}')
90+
expect(html).toEqual('Test-String')
91+
})
92+
})
93+
})
4594
})

0 commit comments

Comments
 (0)