Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9bc58c9

Browse files
fix: improve slug generation (#2581)
1 parent eeacfcc commit 9bc58c9

File tree

4 files changed

+49
-16
lines changed

4 files changed

+49
-16
lines changed

‎src/core/render/compiler/heading.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { slugify } from '../slugify.js';
77
import { stripUrlExceptId } from '../../router/util.js';
88

99
export const headingCompiler = ({ renderer, router, compiler }) =>
10-
(renderer.heading = function ({ tokens, depth }) {
11-
const text = this.parser.parseInline(tokens);
12-
let { str, config } = getAndRemoveConfig(text);
10+
(renderer.heading = function ({ tokens, depth, text }) {
11+
const parsedText = this.parser.parseInline(tokens);
12+
let { str, config } = getAndRemoveConfig(parsedText);
1313
const nextToc = { depth, title: str };
1414

1515
const { content, ignoreAllSubs, ignoreSubHeading } =
@@ -19,7 +19,7 @@ export const headingCompiler = ({ renderer, router, compiler }) =>
1919
nextToc.title = removeAtag(str);
2020
nextToc.ignoreAllSubs = ignoreAllSubs;
2121
nextToc.ignoreSubHeading = ignoreSubHeading;
22-
const slug = slugify(config.id || str);
22+
const slug = slugify(config.id || text);
2323
const url = router.toURL(router.getCurrentPath(), { id: slug });
2424
nextToc.slug = stripUrlExceptId(url);
2525
compiler.toc.push(nextToc);

‎src/core/render/slugify.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ export function slugify(str) {
1212

1313
let slug = str
1414
.trim()
15+
.normalize('NFKD')
16+
.replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu, '')
1517
.replace(/[A-Z]+/g, lower)
1618
.replace(/<[^>]+>/g, '')
1719
.replace(re, '')
1820
.replace(/\s/g, '-')
19-
.replace(/-+/g, '-')
2021
.replace(/^(\d)/, '_1ドル');
2122
let count = cache[slug];
2223

‎src/plugins/search/search.js‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
getAndRemoveConfig,
33
getAndRemoveDocsifyIgnoreConfig,
4+
removeAtag,
45
} from '../../core/render/utils.js';
56
import { markdownToTxt } from './markdown-to-txt.js';
67
import Dexie from 'dexie';
@@ -110,16 +111,11 @@ export function genIndex(path, content = '', router, depth, indexKey) {
110111
if (token.type === 'heading' && token.depth <= depth) {
111112
const { str, config } = getAndRemoveConfig(token.text);
112113

113-
const text = getAndRemoveDocsifyIgnoreConfig(token.text).content;
114-
115-
if (config.id) {
116-
slug = router.toURL(path, { id: slugify(config.id) });
117-
} else {
118-
slug = router.toURL(path, { id: slugify(escapeHtml(text)) });
119-
}
114+
slug = router.toURL(path, { id: slugify(config.id || token.text) });
120115

121116
if (str) {
122117
title = getAndRemoveDocsifyIgnoreConfig(str).content;
118+
title = removeAtag(title.trim());
123119
}
124120

125121
index[slug] = {

‎test/unit/render-util.test.js‎

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,49 @@ describe('core/render/tpl', () => {
168168

169169
describe('core/render/slugify', () => {
170170
test('slugify()', () => {
171-
const result = slugify(
171+
const htmlStrippedSlug = slugify(
172172
'Bla bla bla <svg aria-label="broken" class="broken" viewPort="0 0 1 1"><circle cx="0.5" cy="0.5"/></svg>',
173173
);
174-
const result2 = slugify(
174+
expect(htmlStrippedSlug).toBe('bla-bla-bla-');
175+
176+
const nestedHtmlStrippedSlug = slugify(
175177
'Another <span style="font-size: 1.2em" class="foo bar baz">broken <span class="aaa">example</span></span>',
176178
);
177-
expect(result).toBe('bla-bla-bla-');
178-
expect(result2).toBe('another-broken-example');
179+
expect(nestedHtmlStrippedSlug).toBe('another-broken-example');
180+
181+
const emojiRemovedSlug = slugify('emoji test ⚠️🔥✅');
182+
expect(emojiRemovedSlug).toBe('emoji-test-️');
183+
184+
const multiSpaceSlug = slugify('Title with multiple spaces');
185+
expect(multiSpaceSlug).toBe('title----with---multiple-spaces');
186+
187+
const numberLeadingSlug = slugify('123abc');
188+
expect(numberLeadingSlug).toBe('_123abc');
189+
190+
const firstDuplicate = slugify('duplicate');
191+
expect(firstDuplicate).toBe('duplicate');
192+
193+
const secondDuplicate = slugify('duplicate');
194+
expect(secondDuplicate).toBe('duplicate-1');
195+
196+
const thirdDuplicate = slugify('duplicate');
197+
expect(thirdDuplicate).toBe('duplicate-2');
198+
199+
const mixedCaseSlug = slugify('This Is Mixed CASE');
200+
expect(mixedCaseSlug).toBe('this-is-mixed-case');
201+
202+
const chinesePreservedSlug = slugify('你好 world');
203+
expect(chinesePreservedSlug).toBe('你好-world');
204+
205+
const specialCharSlug = slugify('C++ vs. Java & Python!');
206+
expect(specialCharSlug).toBe('c-vs-java--python');
207+
208+
const docsifyIgnoreSlug = slugify(
209+
'Ignore Heading <!-- {docsify-ignore} -->',
210+
);
211+
expect(docsifyIgnoreSlug).toBe('ignore-heading-');
212+
213+
const quoteCleanedSlug = slugify('"The content"');
214+
expect(quoteCleanedSlug).toBe('the-content');
179215
});
180216
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /