Skip to content

Commit d05f0ed

Browse files
authored
feat(build): allow modifying slugify options (#2898)
1 parent ed704c4 commit d05f0ed

File tree

7 files changed

+75
-12
lines changed

7 files changed

+75
-12
lines changed

docs/content/docs/1.getting-started/3.configuration.md

+15
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,21 @@ export default defineNuxtConfig({
234234

235235
Read more about adding languages in the [Shiki documentation](https://github.com/shikijs/shiki/blob/main/docs/languages.md#adding-grammar).
236236

237+
## `pathMeta`
238+
239+
Content module uses files path to generate the slug, default title and content order, you can customize this behavior with `pathMeta` option.
240+
241+
### `pathMeta.forceLeadingSlash`
242+
243+
If set to `true`, the path will be prefixed with a leading slash. Default value is `true`.
244+
245+
246+
### `pathMeta.slugifyOptions`
247+
248+
Content module uses [slugify](https://github.com/simov/slugify) to generate the slug, you can customize the behavior of slugify with this option.
249+
250+
Checkout [slugify options](https://github.com/simov/slugify#options) for more information.
251+
237252
## `database`
238253

239254
By default Nuxt Content uses a local SQLite database to store and query content. If you like to use another database or you plan to deploy on Cloudflare Workers, you can modify this option.

src/module.ts

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export default defineNuxtModule<ModuleOptions>({
6666
},
6767
},
6868
build: {
69+
pathMeta: {},
6970
markdown: {},
7071
yaml: {},
7172
csv: {

src/types/module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { BuiltinLanguage as ShikiLang, BuiltinTheme as ShikiTheme, Language
22
import type { ListenOptions } from 'listhen'
33
import type { GitInfo } from '../utils/git'
44
import type { MarkdownPlugin } from './content'
5+
import type { PathMetaOptions } from './path-meta'
56

67
export interface D1DatabaseConfig {
78
type: 'd1'
@@ -152,6 +153,7 @@ export interface ModuleOptions {
152153
themes?: (ShikiTheme | ThemeRegistrationAny)[]
153154
}
154155
}
156+
pathMeta: PathMetaOptions
155157
/**
156158
* Options for yaml parser.
157159
*

src/types/path-meta.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export interface SlugifyOptions {
2+
/**
3+
* Characters to remove from the slug
4+
*
5+
* @default undefined
6+
*/
7+
remove?: RegExp
8+
replacement?: string
9+
/**
10+
* Convert the slug to lowercase
11+
*
12+
* @default true
13+
*/
14+
lower?: boolean
15+
strict?: boolean
16+
locale?: string
17+
trim?: boolean
18+
}
19+
20+
export interface PathMetaOptions {
21+
/**
22+
* If set to `true`, the path will be prefixed with a leading slash.
23+
*
24+
* @default true
25+
*/
26+
forceLeadingSlash?: boolean
27+
/**
28+
* Slugify options
29+
*
30+
* @see https://github.com/simov/slugify#options
31+
*/
32+
slugifyOptions?: SlugifyOptions
33+
}

src/utils/content/index.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -99,26 +99,27 @@ async function _getHighlightPlugin(options: HighlighterOptions) {
9999

100100
export async function parseContent(key: string, content: string, collection: ResolvedCollection, nuxt?: Nuxt) {
101101
const mdcOptions = (nuxt?.options as unknown as { mdc: MDCModuleOptions })?.mdc || {}
102-
const contentOptions = (nuxt?.options as unknown as { content: ModuleOptions })?.content?.build?.markdown || {}
102+
const { pathMeta = {}, markdown = {} } = (nuxt?.options as unknown as { content: ModuleOptions })?.content?.build || {}
103103

104-
const rehypeHighlightPlugin = contentOptions.highlight !== false
105-
? await getHighlightPluginInstance(defu(contentOptions.highlight as HighlighterOptions, mdcOptions.highlight, { compress: true }))
104+
const rehypeHighlightPlugin = markdown.highlight !== false
105+
? await getHighlightPluginInstance(defu(markdown.highlight as HighlighterOptions, mdcOptions.highlight, { compress: true }))
106106
: undefined
107107

108108
const parsedContent = await transformContent(key, content, {
109+
pathMeta: pathMeta,
109110
markdown: {
110111
compress: true,
111112
...mdcOptions,
112-
...contentOptions,
113+
...markdown,
113114
rehypePlugins: {
114115
highlight: rehypeHighlightPlugin,
115116
...mdcOptions?.rehypePlugins,
116-
...contentOptions?.rehypePlugins,
117+
...markdown?.rehypePlugins,
117118
},
118119
remarkPlugins: {
119120
'remark-emoji': {},
120121
...mdcOptions?.remarkPlugins,
121-
...contentOptions?.remarkPlugins,
122+
...markdown?.remarkPlugins,
122123
},
123124
highlight: undefined,
124125
},

src/utils/content/transformers/path-meta.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import { pascalCase } from 'scule'
22
import slugify from 'slugify'
33
import { withoutTrailingSlash, withLeadingSlash } from 'ufo'
4+
import defu from 'defu'
5+
import type { PathMetaOptions } from '../../../types/path-meta'
46
import { defineTransformer } from './utils'
57

68
const SEMVER_REGEX = /^\d+(?:\.\d+)*(?:\.x)?$/
79

8-
interface PathMetaOptions {
9-
respectPathCase?: boolean
10+
const defaultOptions: PathMetaOptions = {
11+
slugifyOptions: {
12+
lower: true,
13+
},
1014
}
1115

1216
export default defineTransformer({
1317
name: 'path-meta',
1418
extensions: ['.*'],
1519
transform(content, options: PathMetaOptions = {}) {
16-
const { respectPathCase = false } = options
20+
const opts = defu(options, defaultOptions)
1721
const { basename, extension, stem } = describeId(content.id)
1822
// Check first part for locale name
19-
const filePath = generatePath(stem, { respectPathCase })
23+
const filePath = generatePath(stem, opts)
2024

2125
return {
2226
path: filePath,
@@ -34,8 +38,8 @@ export default defineTransformer({
3438
* @param path file full path
3539
* @returns generated slug
3640
*/
37-
export const generatePath = (path: string, { forceLeadingSlash = true, respectPathCase = false } = {}): string => {
38-
path = path.split('/').map(part => slugify(refineUrlPart(part), { lower: !respectPathCase })).join('/')
41+
export const generatePath = (path: string, { forceLeadingSlash = true, slugifyOptions = {} } = {}): string => {
42+
path = path.split('/').map(part => slugify(refineUrlPart(part), slugifyOptions)).join('/')
3943
return forceLeadingSlash ? withLeadingSlash(withoutTrailingSlash(path)) : path
4044
}
4145

test/unit/parseContent.path-meta.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ const testCases = {
7171
extension: 'md',
7272
stem: 'indexer.draft',
7373
},
74+
'content/فرهنگ/فارسی/فرهنگ.md': {
75+
__description: 'Handle special chars in file name',
76+
title: 'فرهنگ',
77+
path: '/frhng/farsy/frhng',
78+
extension: 'md',
79+
stem: 'فرهنگ/فارسی/فرهنگ',
80+
},
7481
}
7582

7683
describe('Transformer (path-meta)', () => {

0 commit comments

Comments
 (0)