From 123f5b717c9223520da75d022d30476a3348f57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20So=C5=9Bnierz?= Date: Wed, 21 Jul 2021 16:19:45 +0200 Subject: [PATCH] Try not to break words when quoting original message during replies --- changelog.d/1428.feature | 1 + spec/unit/TrimString.spec.js | 16 ++++++++++++++++ src/util/TrimString.ts | 35 ++++++++++++++++++++++++----------- 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 changelog.d/1428.feature diff --git a/changelog.d/1428.feature b/changelog.d/1428.feature new file mode 100644 index 000000000..fca637986 --- /dev/null +++ b/changelog.d/1428.feature @@ -0,0 +1 @@ +Truncate original messages more gently when replying diff --git a/spec/unit/TrimString.spec.js b/spec/unit/TrimString.spec.js index 72822c20a..ad35a81f0 100644 --- a/spec/unit/TrimString.spec.js +++ b/spec/unit/TrimString.spec.js @@ -17,4 +17,20 @@ describe("trimString", function() { done(); }); + + it('should stop trimming at the word boundary if reasonable', (done) => { + const input = "this sentence is waaaaay too long"; + const result = trimString(input, 20); + expect(result).toEqual('this sentence is'); + + done(); + }); + + it('should give up looking for a word boundary if result would become too short', (done) => { + const input = "we're in Llanfairpwllgwyngyll"; + const result = trimString(input, 24); + expect(result).toContain("we're in Llan"); + + done(); + }); }); diff --git a/src/util/TrimString.ts b/src/util/TrimString.ts index 606b22aaa..58b87de27 100644 --- a/src/util/TrimString.ts +++ b/src/util/TrimString.ts @@ -14,20 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -// a unicode-aware substring(0, $x) +// since trimRight()/trimEnd() may break unicode characters +function trimTrailingWhitespace(input: string): string { + return input.replace(/\s*$/u, ''); +} + +// a unicode-aware substring(0, $x) that tries to not break words if possible export function trimString(input: string, maxLength: number): string { - const re = new RegExp(`^([\\s\\S]{0,${maxLength}})`, 'u'); + const re = new RegExp(`^([\\s\\S]{0,${maxLength}})(\\p{L}?)`, 'u'); const match = input.match(re); - let trimmed: string; - if (match) { - trimmed = match[1]; - } - else { + if (!match) { // fallback to a dumb substring() if the regex failed for any reason - trimmed = input.substring(0, maxLength); + return trimTrailingWhitespace(input.substring(0, maxLength)); + } + + const trimmed = trimTrailingWhitespace(match[1]); + + if (match[2]) { + // find as much as you can that is followed by a word boundary, + // shorter than what we have now, but at least 75% of the desired length + const smallerMatch = trimmed.match(/^([\s\S]*\S)\b[\s\S]/u); + const minLength = maxLength * 0.75; + + if (smallerMatch && smallerMatch[1].length >= minLength) { + return smallerMatch[1]; + } } - // - // trimRight()/trimEnd() may break unicode characters - return trimmed.replace(/\s*$/u, ''); + + return trimmed; }