@danielhaim/titlecaser 1.2.49 → 1.2.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,9 @@ Transform any text to proper title case format using popular style guides such a
13
13
  ## Table of Contents
14
14
 
15
15
  - [TitleCaser](#titlecaser)
16
- * [Demo](#demo)
16
+ * [Demo](https://danielhaim1.github.io/TitleCaser/)
17
+ * [CodePen Demo 1](https://codepen.io/danielhaim/pen/oNQgjBv)
18
+ * [CodePen Demo 2](https://codepen.io/danielhaim/pen/oNPGzKw)
17
19
  * [Table of Contents](#table-of-contents)
18
20
  * [Introduction](#introduction)
19
21
  * [Key Features:](#key-features)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielhaim/titlecaser",
3
- "version": "1.2.49",
3
+ "version": "1.2.54",
4
4
  "description": "Converts a string to title case with multiple style options, ability to ignore certain words, and handle acronyms",
5
5
  "keywords": [
6
6
  "title case",
package/src/TitleCaser.js CHANGED
@@ -28,15 +28,19 @@ export class TitleCaser {
28
28
  const {
29
29
  style = "ap",
30
30
  neverCapitalize = [],
31
- replaceTermList = wordReplacementsList
31
+ replaceTermList = wordReplacementsList,
32
+ smartQuotes = false // Set to false by default
32
33
  } = this.options;
34
+
35
+
33
36
  const ignoreList = [ "nl2br", ...neverCapitalize ];
34
37
  const {
35
38
  articlesList,
36
39
  shortConjunctionsList,
37
40
  shortPrepositionsList,
38
41
  neverCapitalizedList,
39
- replaceTerms
42
+ replaceTerms,
43
+ smartQuotes: mergedSmartQuotes // Rename for clarity
40
44
  } = TitleCaserUtils.getTitleCaseOptions ( this.options, commonAbbreviationList, wordReplacementsList );
41
45
 
42
46
  // Prerocess the replaceTerms array to make it easier to search for.
@@ -50,21 +54,20 @@ export class TitleCaser {
50
54
  '&': '&',
51
55
  '<': '&lt;',
52
56
  '>': '&gt;',
57
+ // '\u2018': '\u2019', // Smart single quote
58
+ // '\u201C': '\u201D', // Smart double quote
53
59
  '"': '&quot;',
54
60
  "'": '&#039;'
55
61
  };
56
62
 
57
63
  // Remove extra spaces and replace <br> tags with a placeholder.
58
64
  let inputString = str.trim ();
59
-
65
+
60
66
  // Replace <br> and <br /> tags with a placeholder.
61
67
  inputString = inputString.replace(/<\s*br\s*\/?\s*>/gi, " nl2br ");
62
68
 
63
69
  // Remove extra spaces and replace <br> tags with a placeholder.
64
70
  inputString = inputString.replace ( / {2,}/g, ( match ) => match.slice ( 0, 1 ) );
65
-
66
-
67
- // console.log(inputString);
68
71
 
69
72
 
70
73
  // Split the string into an array of words.
@@ -122,7 +125,7 @@ export class TitleCaser {
122
125
  case TitleCaserUtils.hasNumbers ( word ):
123
126
  // If the word has numbers, return the correct casing.
124
127
  return word;
125
- default:
128
+ default:
126
129
  // Default to returning the word with the correct casing.
127
130
  return word.charAt ( 0 )
128
131
  .toUpperCase () + word.slice ( 1 )
@@ -143,6 +146,12 @@ export class TitleCaser {
143
146
 
144
147
  // Replace the nl2br placeholder with <br> tags.
145
148
  inputString = inputString.replace(/nl2br/gi, "<br />");
149
+
150
+ // Convert quotation marks to smart quotes if enabled
151
+ // Refer to: https://github.com/danielhaim1/TitleCaser/issues/4
152
+ if (smartQuotes) {
153
+ inputString = TitleCaserUtils.convertQuotesToCurly(inputString);
154
+ }
146
155
 
147
156
  // Return the string.
148
157
  return inputString;
@@ -9,7 +9,40 @@ export const correctTitleCasingList = [
9
9
  'MobX', 'SCSS', 'TypeScript', 'Vue.js', '.NET', 'ASP', 'ASPX',
10
10
  'MySQL', 'PHP', 'PostgresQL', 'Python', 'SQL', 'GraphQL',
11
11
  'HTML5',
12
-
12
+
13
+ // Countries
14
+ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda',
15
+ 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas',
16
+ 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin',
17
+ 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei',
18
+ 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon',
19
+ 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia',
20
+ 'Comoros', 'Congo', 'Costa Rica', 'Cote d\'Ivoire', 'Croatia', 'Cuba',
21
+ 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica',
22
+ 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea',
23
+ 'Eritrea', 'Estonia', 'Eswatini', 'Ethiopia', 'Fiji', 'Finland', 'France',
24
+ 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada',
25
+ 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Honduras', 'Hungary',
26
+ 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy',
27
+ 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Korea',
28
+ 'Kosovo', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho',
29
+ 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar',
30
+ 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands',
31
+ 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco',
32
+ 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar', 'Namibia',
33
+ 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria',
34
+ 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Panama', 'Papua New Guinea',
35
+ 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania',
36
+ 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines',
37
+ 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia',
38
+ 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands',
39
+ 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan',
40
+ 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Taiwan', 'Tajikistan', 'Tanzania',
41
+ 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia',
42
+ 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates',
43
+ 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Vatican City',
44
+ 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',
45
+
13
46
  // Acronyms/Abbreviations
14
47
  'API', 'APIs', 'ASCII', 'CI', 'CircleCI', 'CLI', 'DLL', 'DNS',
15
48
  'EC2', 'FTP', 'HTTP', 'HTTPs', 'ICMP', 'IDE', 'IP', 'ISP',
@@ -78,7 +111,7 @@ export const correctTitleCasingList = [
78
111
  'ProTools', 'QuickTime', 'AdWords', 'AdSense', 'TikTok', 'Slack', 'Trello',
79
112
  'Zoom', 'Twitch', 'Snapchat', 'WhatsApp', 'Telegram', 'Discord', 'Reddit',
80
113
  'Quora', 'StackOverflow', 'StackExchange', 'Coca-Cola',
81
- 'AWS', 'GCP', 'VMware', 'CVS', 'ESL', 'EE', 'CW', 'EE',
114
+ 'AWS', 'GCP', 'VMware', 'CVS', 'ESL', 'EE', 'CW', 'EE', 'ITV',
82
115
 
83
116
  // Sports
84
117
  'NBA', 'NCAA', 'NFL', 'WWE', 'WWF', 'FIFA',
@@ -100,6 +133,9 @@ export const correctTitleCasingList = [
100
133
 
101
134
  // Commercial
102
135
  'Ltd.', 'Co.', 'Inc.', 'St.', 'Ave.', 'Bldg.', 'No.',
136
+
137
+ // Other
138
+ '×', 'x', 'TV', 'IRL', 'UK'
103
139
  ];
104
140
 
105
141
  export const wordReplacementsList = [
@@ -126,6 +162,7 @@ export const wordReplacementsList = [
126
162
  { 't.b.d': 'TBD' },
127
163
  { 'vuejs': 'Vue.js' },
128
164
  { 'phd': 'ph.d.' },
165
+ { 'x': '×' },
129
166
  ];
130
167
 
131
168
  // ! TODO
@@ -71,12 +71,12 @@ export class TitleCaserUtils {
71
71
  return TitleCaserUtils.titleCaseOptionsCache.get ( cacheKey );
72
72
  }
73
73
 
74
- // Merge the default options with the user-provided options
75
74
  const mergedOptions = {
76
- ...titleCaseDefaultOptionsList[options.style || "ap"],
77
- ...options
75
+ ...titleCaseDefaultOptionsList[options.style || "ap"],
76
+ ...options,
77
+ smartQuotes: options.hasOwnProperty('smartQuotes') ? options.smartQuotes : false
78
78
  };
79
-
79
+
80
80
  // Merge the default articles with user-provided articles and lowercase words
81
81
  const mergedArticles = mergedOptions.articlesList.concat ( lowercaseWords )
82
82
  .filter ( ( word, index, array ) => array.indexOf ( word ) === index );
@@ -97,14 +97,15 @@ export class TitleCaserUtils {
97
97
  ];
98
98
 
99
99
  // Return the merged options
100
- const result = {
100
+ const result = {
101
101
  articlesList: mergedArticles,
102
102
  shortConjunctionsList: mergedShortConjunctions,
103
103
  shortPrepositionsList: mergedShortPrepositions,
104
- neverCapitalizedList: [ ...mergedOptions.neverCapitalizedList ],
104
+ neverCapitalizedList: [...mergedOptions.neverCapitalizedList],
105
105
  replaceTerms: mergedReplaceTerms,
106
+ smartQuotes: mergedOptions.smartQuotes // Add smartQuotes option to result
106
107
  };
107
-
108
+
108
109
  // Add the merged options to the cache and return them
109
110
  TitleCaserUtils.titleCaseOptionsCache.set ( cacheKey, result );
110
111
  return result;
@@ -387,7 +388,42 @@ export class TitleCaserUtils {
387
388
  // Check if the targetWord is in the wordList
388
389
  return wordList.some ( ( word ) => word.toLowerCase () === targetWord.toLowerCase () );
389
390
  }
390
-
391
+
392
+ static convertQuotesToCurly(input) {
393
+ const curlyQuotes = {
394
+ "'": ['\u2018', '\u2019'],
395
+ '"': ['\u201C', '\u201D'],
396
+ };
397
+
398
+ let replacedText = '';
399
+
400
+ for (let i = 0; i < input.length; i++) {
401
+ const char = input[i];
402
+ const curlyQuotePair = curlyQuotes[char];
403
+
404
+ if (curlyQuotePair) {
405
+ const prevChar = input[i - 1];
406
+ const nextChar = input[i + 1];
407
+
408
+ // Determine whether to use left or right curly quote
409
+ const isLeftAligned = (!prevChar || prevChar === ' ' || prevChar === '\n');
410
+ const curlyQuote = isLeftAligned ? curlyQuotePair[0] : curlyQuotePair[1];
411
+ replacedText += curlyQuote;
412
+
413
+ // Handle cases where right curly quote is followed by punctuation or space
414
+ if (curlyQuote === curlyQuotePair[1] && /[.,;!?()\[\]{}:]/.test(nextChar)) {
415
+ replacedText += nextChar;
416
+ i++; // Skip the next character
417
+ }
418
+ } else {
419
+ replacedText += char;
420
+ }
421
+ }
422
+
423
+ return replacedText;
424
+ }
425
+
426
+
391
427
  // This function is used to replace a word with a term in the replaceTerms object
392
428
  static replaceTerm ( word, replaceTermObj ) {
393
429
  // Validate input