|
1 | 1 | 'use strict'; |
2 | 2 |
|
3 | 3 | // precompile for speed |
4 | | -var HTML_TAGS_REGEX = /<[^>]*>/g; // anything contained in < > tags |
5 | | -var FORBIDDEN_CHARS_REGEX = /[\\/:*?"<>|$%&!@#~.^`'(){}[\],=+;]/g; // Characters in the set: \/:*?"<>|$%&!@#~.^`'(){}[],=+; |
6 | | -var CONTROL_CHARS_REGEX = /\p{Cc}/gu; // Unicode control characters |
| 4 | +var HTML_TAGS_REGEX = /<[^>]*>/g; // Anything contained in < > tags |
| 5 | +// Forbid ANY character in the Unicode Symbol or Punctuation categories, |
| 6 | +// which includes pretty much every common non-word character, |
| 7 | +// including all of the following: \/:*?"<>|$%&!@#~.^`'(){}[]-_,=+; |
| 8 | +// We actually want to allow hyphens (WORD_SEP_CHAR), but it's not easy |
| 9 | +// to exclude them from the regex, so we'll handle them in the match function. |
| 10 | +var FORBIDDEN_CHARS_REGEX = /[\p{S}\p{P}]/gu; |
| 11 | +// Control chars, format chars (e.g. ZWJ), variation selectors, and the |
| 12 | +// combining enclosing keycap. Some of these may be left behind after emoji |
| 13 | +// symbols are removed, so we explicitly remove them here. |
| 14 | +var INVISIBLE_CHARS_REGEX = /[\p{Cc}\p{Cf}\uFE00-\uFE0F\u20E3]/gu; |
7 | 15 |
|
8 | | -var UNICODE_REPLACEMENT_CHAR_REGEX = /�/g; // U+FFFD, the Unicode replacement character |
9 | | -var WHITESPACE_REGEX = /\s+/g; |
| 16 | +var UNICODE_REPLACEMENT_CHAR_REGEX = /�/g; // U+FFFD, the Unicode replacement character |
| 17 | +var WHITESPACE_REGEX = /\s+/g; // All whitespace characters |
10 | 18 |
|
11 | | -var WORD_SEP_CHAR = '-'; // character used to separate words (replaces whitespace) |
| 19 | +var WORD_SEP_CHAR = '-'; // Character used to separate words (replaces whitespace) |
12 | 20 | var _WORD_SEP_ESCAPED = WORD_SEP_CHAR.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
13 | | -var WORD_SEP_CHARS_REGEX = new RegExp(_WORD_SEP_ESCAPED + '{3,}', 'g'); // three or more consecutive word separator chars |
14 | | -var TRAILING_WORD_SEP_CHAR_REGEX = new RegExp(_WORD_SEP_ESCAPED + '$', 'g'); // trailing word separator char |
| 21 | +var WORD_SEP_CHARS_REGEX = new RegExp(_WORD_SEP_ESCAPED + '{3,}', 'g'); // Three or more consecutive word separator chars |
| 22 | +var TRAILING_WORD_SEP_CHAR_REGEX = new RegExp(_WORD_SEP_ESCAPED + '$', 'g'); // Trailing word separator char |
15 | 23 |
|
16 | 24 | // Safely under the limit for most filesystems |
17 | 25 | var DEFAULT_MAX_LEN = 60; |
@@ -39,15 +47,17 @@ function toWellFormed(str) { |
39 | 47 | * @return {string} |
40 | 48 | */ |
41 | 49 | module.exports = function slugify(str, maxLen = DEFAULT_MAX_LEN) { |
42 | | - var slug = toWellFormed(str ?? '') // Guarantee well-formed Unicode text |
43 | | - .replace(UNICODE_REPLACEMENT_CHAR_REGEX, '') // Drop Unicode replacement chars left by previous step |
44 | | - .replace(HTML_TAGS_REGEX, ' ') // Remove < > tags, such as <br> (replace with space) |
45 | | - .replace(FORBIDDEN_CHARS_REGEX, '') // Remove forbidden filename characters |
46 | | - .toLowerCase() // Lowercase everything |
47 | | - .trim() // Strip leading/trailing whitespace |
48 | | - .replace(WHITESPACE_REGEX, WORD_SEP_CHAR) // Replace any remaining whitespace with the word sep char |
49 | | - .replace(CONTROL_CHARS_REGEX, '') // Remove control characters (after whitespace) |
50 | | - .replace(WORD_SEP_CHARS_REGEX, WORD_SEP_CHAR); // Replace multiple word sep chars with a single one |
| 50 | + var slug = toWellFormed(str ?? '') // Guarantee well-formed Unicode text |
| 51 | + .replace(UNICODE_REPLACEMENT_CHAR_REGEX, '') // Drop Unicode replacement chars left by previous step |
| 52 | + .replace(HTML_TAGS_REGEX, ' ') // Remove < > tags, such as <br> (replace with space) |
| 53 | + .replace(FORBIDDEN_CHARS_REGEX, (c) => { // Remove forbidden filename characters (but allow the word sep char) |
| 54 | + return c === WORD_SEP_CHAR ? c : ''; |
| 55 | + }) |
| 56 | + .toLowerCase() // Lowercase everything |
| 57 | + .trim() // Strip leading/trailing whitespace |
| 58 | + .replace(WHITESPACE_REGEX, WORD_SEP_CHAR) // Replace any remaining whitespace with the word sep char |
| 59 | + .replace(INVISIBLE_CHARS_REGEX, '') // Remove control/format chars and emoji glue (after whitespace) |
| 60 | + .replace(WORD_SEP_CHARS_REGEX, WORD_SEP_CHAR); // Replace multiple word sep chars with a single one |
51 | 61 |
|
52 | 62 | if (slug.length <= maxLen) return slug; |
53 | 63 | // Apply maxLen to the resulting string. Use Array.from().slice() instead of String.prototype.split() |
|
0 commit comments