@tienedev/datype
Version:
Modern TypeScript utility library with pragmatic typing and zero dependencies
191 lines (189 loc) • 6.95 kB
JavaScript
/**
* Truncates a string if it's longer than the specified length.
* The last characters of the truncated string are replaced with the omission string.
*
* @param str - The string to truncate
* @param length - The maximum length of the truncated string (default: 30)
* @param omission - The string used to indicate omitted characters (default: '...')
* @returns A truncated string if needed, otherwise the original string
*
* @example
* ```typescript
* import { truncate } from 'datype';
*
* // Basic usage
* truncate('Hello world, this is a long string', 20);
* // 'Hello world, this...'
*
* // Custom omission
* truncate('Hello world, this is a long string', 20, '…');
* // 'Hello world, this i…'
*
* // Custom omission with multiple characters
* truncate('Hello world, this is a long string', 20, ' [more]');
* // 'Hello world [more]'
*
* // String shorter than limit
* truncate('Short text', 20);
* // 'Short text'
*
* // Empty omission
* truncate('Hello world, this is a long string', 15, '');
* // 'Hello world, th'
*
* // Real-world examples
* const title = 'Understanding Advanced TypeScript Utility Types';
* truncate(title, 30); // 'Understanding Advanced...'
*
* const description = 'This is a very long product description that needs to be shortened';
* truncate(description, 50, '... read more');
* // 'This is a very long product descr... read more'
*
* // For URLs
* const url = 'https://www.example.com/very/long/path/to/resource';
* truncate(url, 30, '...');
* // 'https://www.example.com/...'
* ```
*/
function truncate(str, length = 30, omission = '...') {
if (typeof str !== 'string') {
throw new TypeError('Expected first argument to be a string');
}
if (typeof length !== 'number' || !Number.isInteger(length) || length < 0) {
throw new TypeError('Expected length to be a non-negative integer');
}
if (typeof omission !== 'string') {
throw new TypeError('Expected omission to be a string');
}
// If string is already shorter than or equal to the limit, return as-is
if (str.length <= length) {
return str;
}
// If omission is longer than the limit, just return the omission truncated
if (omission.length >= length) {
return omission.slice(0, length);
}
// Calculate how much of the original string we can keep
const truncateAt = length - omission.length;
// If there's no room for any original content, just return omission
if (truncateAt <= 0) {
return omission.slice(0, length);
}
return str.slice(0, truncateAt) + omission;
}
/**
* Truncates a string at the nearest word boundary to avoid cutting words in half.
*
* @param str - The string to truncate
* @param length - The maximum length of the truncated string
* @param omission - The string used to indicate omitted characters (default: '...')
* @returns A truncated string at word boundary if possible
*
* @example
* ```typescript
* import { truncateWords } from 'datype';
*
* // Truncate at word boundary
* truncateWords('Hello world this is a test', 15);
* // 'Hello world...'
*
* // Compare with regular truncate
* truncate('Hello world this is a test', 15);
* // 'Hello world thi...'
*
* // Long single word
* truncateWords('Supercalifragilisticexpialidocious', 15);
* // 'Supercalifrag...' (falls back to character truncation)
*
* // No spaces in limit
* truncateWords('Hello world', 5);
* // 'He...' (falls back to character truncation)
* ```
*/
function truncateWords(str, length, omission = '...') {
if (typeof str !== 'string') {
throw new TypeError('Expected first argument to be a string');
}
if (typeof length !== 'number' || !Number.isInteger(length) || length < 0) {
throw new TypeError('Expected length to be a non-negative integer');
}
if (typeof omission !== 'string') {
throw new TypeError('Expected omission to be a string');
}
// If string is already shorter than or equal to the limit, return as-is
if (str.length <= length) {
return str;
}
// First, do a regular truncation to get the maximum possible substring
const maxSubstring = str.slice(0, length - omission.length);
// Find the last space in this substring
const lastSpaceIndex = maxSubstring.lastIndexOf(' ');
// If there's no space (single long word), fall back to character truncation
if (lastSpaceIndex === -1) {
return truncate(str, length, omission);
}
// Truncate at the last word boundary
return str.slice(0, lastSpaceIndex) + omission;
}
/**
* Truncates text in the middle, keeping both the beginning and end visible.
* Useful for file paths, URLs, or other strings where both ends are important.
*
* @param str - The string to truncate
* @param length - The maximum length of the truncated string
* @param omission - The string used to indicate omitted characters (default: '...')
* @returns A string truncated in the middle
*
* @example
* ```typescript
* import { truncateMiddle } from 'datype';
*
* // File paths
* truncateMiddle('/very/long/path/to/important/file.txt', 25);
* // '/very/long/.../file.txt'
*
* // URLs
* truncateMiddle('https://www.example.com/very/long/path/page.html', 35);
* // 'https://www.example.com/.../page.html'
*
* // Email addresses
* truncateMiddle('verylongemailaddress@example.com', 25);
* // 'verylongemail...@example.com'
*
* // Custom omission
* truncateMiddle('abcdefghijklmnopqrstuvwxyz', 15, '…');
* // 'abcdef…tuvwxyz'
* ```
*/
function truncateMiddle(str, length, omission = '...') {
if (typeof str !== 'string') {
throw new TypeError('Expected first argument to be a string');
}
if (typeof length !== 'number' || !Number.isInteger(length) || length < 0) {
throw new TypeError('Expected length to be a non-negative integer');
}
if (typeof omission !== 'string') {
throw new TypeError('Expected omission to be a string');
}
// If string is already shorter than or equal to the limit, return as-is
if (str.length <= length) {
return str;
}
// If omission is longer than the limit, just return the omission truncated
if (omission.length >= length) {
return omission.slice(0, length);
}
// Calculate how much content we can keep (excluding omission)
const contentLength = length - omission.length;
// If there's no room for any content, just return omission
if (contentLength <= 0) {
return omission.slice(0, length);
}
// Split the available content length between start and end
const startLength = Math.ceil(contentLength / 2);
const endLength = contentLength - startLength;
const start = str.slice(0, startLength);
const end = endLength > 0 ? str.slice(-endLength) : '';
return start + omission + end;
}
export { truncate, truncateMiddle, truncateWords };