From b1c94c6397c24f46436238e04305fbb586be3cf0 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 11 Jan 2017 13:45:22 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20correctly=20count=20multibyte=20?= =?UTF-8?q?chars=20in=20character=20counters=20(#487)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/TryGhost/Ghost/issues/7739 - use es6's `Array.from` to convert the string to an array to get a symbol count rather than a byte count --- .../admin/app/helpers/gh-count-characters.js | 11 +++++- .../app/helpers/gh-count-down-characters.js | 12 ++++-- .../unit/helpers/gh-count-characters-test.js | 37 +++++++++++++++++++ .../helpers/gh-count-down-characters-test.js | 31 ++++++++++++++++ 4 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 ghost/admin/tests/unit/helpers/gh-count-characters-test.js create mode 100644 ghost/admin/tests/unit/helpers/gh-count-down-characters-test.js diff --git a/ghost/admin/app/helpers/gh-count-characters.js b/ghost/admin/app/helpers/gh-count-characters.js index 344dc4f307..107d2ea273 100644 --- a/ghost/admin/app/helpers/gh-count-characters.js +++ b/ghost/admin/app/helpers/gh-count-characters.js @@ -1,14 +1,17 @@ import {helper} from 'ember-helper'; import {htmlSafe} from 'ember-string'; -export default helper(function (params) { +export function countCharacters(params) { if (!params || !params.length) { return; } let el = document.createElement('span'); let content = params[0] || ''; - let {length} = content; + + // convert to array so that we get accurate symbol counts for multibyte chars + // this will still count emoji+modifer as two chars + let {length} = Array.from(content); el.className = 'word-count'; @@ -21,4 +24,8 @@ export default helper(function (params) { el.innerHTML = 200 - length; return htmlSafe(el.outerHTML); +} + +export default helper(function (params) { + return countCharacters(params); }); diff --git a/ghost/admin/app/helpers/gh-count-down-characters.js b/ghost/admin/app/helpers/gh-count-down-characters.js index 4f7077846d..3abab9c61a 100644 --- a/ghost/admin/app/helpers/gh-count-down-characters.js +++ b/ghost/admin/app/helpers/gh-count-down-characters.js @@ -1,17 +1,17 @@ import {helper} from 'ember-helper'; import {htmlSafe} from 'ember-string'; -export default helper(function (params) { +export function countDownCharacters(params) { if (!params || params.length < 2) { return; } let el = document.createElement('span'); let [content, maxCharacters] = params; - let length; - content = content || ''; - length = content.length; + // convert to array so that we get accurate symbol counts for multibyte chars + // this will still count emoji+modifer as two chars + let {length} = Array.from(content || ''); el.className = 'word-count'; @@ -24,4 +24,8 @@ export default helper(function (params) { el.innerHTML = length; return htmlSafe(el.outerHTML); +} + +export default helper(function (params) { + return countDownCharacters(params); }); diff --git a/ghost/admin/tests/unit/helpers/gh-count-characters-test.js b/ghost/admin/tests/unit/helpers/gh-count-characters-test.js new file mode 100644 index 0000000000..fabde5f636 --- /dev/null +++ b/ghost/admin/tests/unit/helpers/gh-count-characters-test.js @@ -0,0 +1,37 @@ +import {expect} from 'chai'; +import {describe, it} from 'mocha'; +import {countCharacters} from 'ghost-admin/helpers/gh-count-characters'; + +describe('Unit: Helper: gh-count-characters', function() { + let defaultStyle = 'color: rgb(158, 157, 149);'; + let errorStyle = 'color: rgb(226, 84, 64);'; + + it('counts remaining chars', function() { + let result = countCharacters(['test']); + expect(result.string) + .to.equal(`196`); + }); + + it('warns when nearing limit', function () { + let result = countCharacters([Array(195 + 1).join('x')]); + expect(result.string) + .to.equal(`5`); + }); + + it('indicates too many chars', function () { + let result = countCharacters([Array(205 + 1).join('x')]); + expect(result.string) + .to.equal(`-5`); + }); + + it('counts multibyte correctly', function () { + let result = countCharacters(['💩']); + expect(result.string) + .to.equal(`199`); + + // emoji + modifier is still two chars + result = countCharacters(['💃🏻']); + expect(result.string) + .to.equal(`198`); + }); +}); diff --git a/ghost/admin/tests/unit/helpers/gh-count-down-characters-test.js b/ghost/admin/tests/unit/helpers/gh-count-down-characters-test.js new file mode 100644 index 0000000000..41d4eb06bd --- /dev/null +++ b/ghost/admin/tests/unit/helpers/gh-count-down-characters-test.js @@ -0,0 +1,31 @@ +import {expect} from 'chai'; +import {describe, it} from 'mocha'; +import {countDownCharacters} from 'ghost-admin/helpers/gh-count-down-characters'; + +describe('Unit: Helper: gh-count-down-characters', function() { + let validStyle = 'color: rgb(159, 187, 88);'; + let errorStyle = 'color: rgb(226, 84, 64);'; + + it('counts chars', function() { + let result = countDownCharacters(['test', 200]); + expect(result.string) + .to.equal(`4`); + }); + + it('warns with too many chars', function () { + let result = countDownCharacters([Array(205 + 1).join('x'), 200]); + expect(result.string) + .to.equal(`205`); + }); + + it('counts multibyte correctly', function () { + let result = countDownCharacters(['💩', 200]); + expect(result.string) + .to.equal(`1`); + + // emoji + modifier is still two chars + result = countDownCharacters(['💃🏻', 200]); + expect(result.string) + .to.equal(`2`); + }); +});