Shorten text after 50 characters but only after the current word

Viewed 58

I need to shorten text after 50 characters but if the 50th char is at the middle of a word - cut only after the word and not before.

example text: Contrary to popular belief, Lorem Ipsum is not simply text (59 chars)

expected output: Contrary to popular belief, Lorem Ipsum is not simply (54 chars)

I was trying this: text.substr(0,50).lastIndexOf(' ')); - not good for me cause it cuts of the "simply" and I want the simply to be at the shorten text. Thanks for the help!

5 Answers

If the phrase length is less than the limit, return the phrase; else, set the slice index to the limit and scan until you find a word boundary and slice the phrase.

const clip = (phrase, limit) => {
  if (phrase.length <= limit) return phrase;
  let sliceIndex = limit;
  while (sliceIndex < phrase.length && /\b/.test(phrase[sliceIndex])) sliceIndex++;
  return phrase.slice(0, sliceIndex);
};

const input = "Contrary to popular belief, Lorem Ipsum is not simply text";

const expected = "Contrary to popular belief, Lorem Ipsum is not simply";

const actual = clip(input, 50);

console.log(actual === expected); // true

You were close with the substring & indexOf. If you look for a space after the first 50 characters it will work. Then there is an edge case where the string is longer then 50 characters and no space.

const shortener = (str) => {
  const offset = str.substring(50).indexOf(' '); 
  return offset !== -1   ? str.substring(0,50 + offset) + '...' : 
         str.length > 50 ? str + '...' : str;
}

Regular expression can help you with it.
The idea is to find at least 50 symbols that finishes with word boundary \b

let text = 'Contrary to popular belief, Lorem Ipsum is not simply text';
let shortetedText = text.match(/.{50,}?(?=\b)/)[0];
console.log(shortetedText);

UPD:
If you want to create a function that shorten your text if it's longer than 50 symbols we can create a function that do it instead of you.

let short = text => text.length > 50 ? text.match(/.{50,}?(?=\b)/)[0]: text;  

Then just use it like that:

let longText = 'Contrary to popular belief, Lorem Ipsum is not simply text';
let shortText = 'Lorem ipsum';
console.log(short(longText)); //will shorten text
console.log(short(shortText)); //will leave text as it is bcoz it's shorter than 50 symbols
const text ="Contrary to popular belief, Lorem Ipsum is not simply text"


// start of the last word 
const start =  text.substr(0,50).lastIndexOf(' ')
// start from where you left + 1 to remove space
const end = text.substr(start+1,text.length).indexOf(' ')

console.log(text.substr(0,start + end + 1));

Regex one-liners

Parts of the below regexes may seem redundant, but they are actually there to cover the edge cases.

// Length fixed at "50"
const trim50 = str => str?.match(/^.{0,50}.*?(?=\b| |$)/)[0];

// Dynamic length
const trim = (str, n) => new RegExp(`^.{0,${n}}.*?(?=\b| |$)`).exec(str)[0];

// Demo
console.log(trim('Contrary to popular belief, Lorem Ipsum is not simply text', 50))

Regex-free

const trim = (str, n) => {
  const start = str.substring(0, n);
  const index = str.indexOf(' ', n - 1);
  const end   = str.slice(n, index < 0 ? str.length : index);
  return start + end;
}

console.log(trim('Contrary to popular belief, Lorem Ipsum is not simply text', 50));

Test cases

Due to the limitations of the SO's built-in console, please first bring up the DevTools's console with F12, and then run the below code for a little demo.

const testCases = [
  { input:    'Contrary to popular belief, Lorem Ipsum is not simply text',
    expected: 'Contrary to popular belief, Lorem Ipsum is not simply' },
  { input:    '1234 6789 1234 6789 1234 6789 1234 6789 1234 678901234 6789',
    expected: '1234 6789 1234 6789 1234 6789 1234 6789 1234 678901234' },
  { input:    '1234 6789 1234 6789 1234 6789 1234 6789 1234 6789 1234 6789',
    expected: '1234 6789 1234 6789 1234 6789 1234 6789 1234 6789 ' },
  { input:    '1234 6789 1234 6789 1234 6789 1234 6789 1234 67890123456789',
    expected: '1234 6789 1234 6789 1234 6789 1234 6789 1234 67890123456789' },
  { input:    '                                                            ',
    expected: '                                                  ' },
  { input:    '1234 5678',
    expected: '1234 5678' },
  { input:    '1234',
    expected: '1234' },
  { input:    '',
    expected: '' }
];

const test = trim => {
  console.table(
    testCases.map(
      testCase => Object.assign(
        Object.assign(testCase, { output: trim(testCase.input) }),
        { pass: testCase.output === testCase.expected }
      )
    )
  );
};

test(str => str?.match(/^.{0,50}.*?(?=\b| |$)/)[0]);

Related