diff --git a/lib/index.js b/lib/index.js index bf7103e..488a84f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -38,8 +38,11 @@ * dash; * when `'inverted'`, turns three dashes into an en dash and two into an em * dash. - * @property {boolean | null | undefined} [ellipses=true] - * Transform triple dots (default: `true`), with or without spaces between. + * @property {'spaced' | 'unspaced' | boolean | null | undefined} [ellipses=true] + * Transform triple dots (default: `true`). + * when `'spaced'`, turns triple dots with spaces into ellipses; + * when `'unspaced'`, turns triple dots without spaces into ellipses; + * when `true`, turns triple dots with or without spaces into ellipses. * @property {QuoteCharacterMap | null | undefined} [openingQuotes] * Opening quotes to use (default: `{double: '“', single: '‘'}`). * @property {boolean | null | undefined} [quotes=true] @@ -91,7 +94,11 @@ export default function retextSmartypants(options) { methods.push(quotesDefault) } - if (settings.ellipses !== false) { + if (settings.ellipses === 'spaced') { + methods.push(ellipsesSpaced) + } else if (settings.ellipses === 'unspaced') { + methods.push(ellipsesUnspaced) + } else if (settings.ellipses !== false) { methods.push(ellipsesDefault) } @@ -219,15 +226,19 @@ function dashesOldschool(_, node) { * @type {Method} */ function ellipsesDefault(_, node, index, parent) { + ellipsesSpaced(_, node, index, parent) + ellipsesUnspaced(_, node, index, parent) +} + +/** + * Transform multiple dots with spaces into unicode ellipses. + * + * @type {Method} + */ +function ellipsesSpaced(_, node, index, parent) { const value = node.value const siblings = parent.children - // Simple node with three dots and without whitespace. - if (/^\.{3,}$/.test(node.value)) { - node.value = '…' - return - } - if (!/^\.+$/.test(value)) { return } @@ -275,6 +286,18 @@ function ellipsesDefault(_, node, index, parent) { node.value = '…' } +/** + * Transform multiple dots without spaces into unicode ellipses. + * + * @type {Method} + */ +function ellipsesUnspaced(_, node) { + // Simple node with three dots and without whitespace. + if (/^\.{3,}$/.test(node.value)) { + node.value = '…' + } +} + /** * Transform straight single- and double quotes into smart quotes. * diff --git a/readme.md b/readme.md index ff59cec..3e84116 100644 --- a/readme.md +++ b/readme.md @@ -115,19 +115,22 @@ Configuration (TypeScript type). `{double: '”', single: '’'}`) — closing quotes to use * `dashes` (`'inverted'` or `'oldschool'` or `boolean`, default: `true`) - transform dashes; + — transform dashes; when `true`, turns two dashes into an em dash character; when `'oldschool'`, turns three dashes into an em dash and two into an en dash; when `'inverted'`, turns three dashes into an en dash and two into an em dash -* `ellipses` (`boolean`, default: `true`) - — transform triple dots, with or without spaces between +* `ellipses` (`'spaced'` or `'unspaced'` or `boolean`, default: `true`) + — transform triple dots; + when `'spaced'`, turns triple dots with spaces into ellipses; + when `'unspaced'`, turns triple dots without spaces into ellipses; + when `true`, turns triple dots with or without spaces into ellipses * `openingQuotes` ([`QuoteCharacterMap`][api-quote-character-map], default: `{double: '“', single: '‘'}`) — opening quotes to use * `quotes` (`boolean`, default: `true`) - — Transform straight quotes into smart quotes + — transform straight quotes into smart quotes ### `QuoteCharacterMap` diff --git a/test.js b/test.js index 6b4d739..c4da238 100644 --- a/test.js +++ b/test.js @@ -554,3 +554,156 @@ test('Ellipses', async function (t) { } ) }) + +test('Ellipses (unspaced)', async function (t) { + const processor = retext().use(retextSmartypants, {ellipses: 'unspaced'}) + + await t.test('should replace three full stops', async function () { + assert.equal( + processor.processSync('Alfred... Bertrand.').toString(), + 'Alfred\u2026 Bertrand.' + ) + }) + + await t.test('should replace three initial full stops', async function () { + assert.equal( + processor.processSync('...Alfred Bertrand.').toString(), + '\u2026Alfred Bertrand.' + ) + }) + + await t.test('should replace three final full stops', async function () { + assert.equal( + processor.processSync('Alfred Bertrand...').toString(), + 'Alfred Bertrand\u2026' + ) + }) + + await t.test('should replace three padded full stops', async function () { + assert.equal( + processor.processSync('Alfred ... Bertrand.').toString(), + 'Alfred \u2026 Bertrand.' + ) + }) + + await t.test( + 'should replace three padded initial full stops', + async function () { + assert.equal( + processor.processSync('... Alfred Bertrand.').toString(), + '\u2026 Alfred Bertrand.' + ) + } + ) + + await t.test( + 'should replace three padded final full stops', + async function () { + assert.equal( + processor.processSync('Alfred Bertrand ...').toString(), + 'Alfred Bertrand \u2026' + ) + } + ) + + await t.test('should replace more than three full stops', async function () { + assert.equal( + processor.processSync('Alfred..... Bertrand.').toString(), + 'Alfred\u2026 Bertrand.' + ) + + assert.equal( + processor.processSync('Alfred bertrand....').toString(), + 'Alfred bertrand\u2026' + ) + + assert.equal( + processor.processSync('......Alfred bertrand.').toString(), + '\u2026Alfred bertrand.' + ) + }) + + await t.test( + 'should NOT replace less than three full stops', + async function () { + assert.equal( + processor.processSync('Alfred.. Bertrand.').toString(), + 'Alfred.. Bertrand.' + ) + + assert.equal( + processor.processSync('Alfred bertrand. .').toString(), + 'Alfred bertrand. .' + ) + + assert.equal( + processor.processSync('.Alfred bertrand.').toString(), + '.Alfred bertrand.' + ) + } + ) +}) + +test('Ellipses (spaced)', async function (t) { + const processor = retext().use(retextSmartypants, {ellipses: 'spaced'}) + + await t.test( + 'should replace three padded full stops with spaces', + async function () { + assert.equal( + processor.processSync('Alfred . . . Bertrand.').toString(), + 'Alfred \u2026 Bertrand.' + ) + } + ) + + await t.test( + 'should replace three padded initial full stops with spaces', + async function () { + assert.equal( + processor.processSync('. . . Alfred Bertrand.').toString(), + '\u2026 Alfred Bertrand.' + ) + } + ) + + await t.test( + 'should replace three padded final full stops with spaces', + async function () { + assert.equal( + processor.processSync('Alfred Bertrand . . .').toString(), + 'Alfred Bertrand \u2026' + ) + } + ) + + await t.test( + 'should replace three full stops with spaces', + async function () { + assert.equal( + processor.processSync('Alfred. . . Bertrand.').toString(), + 'Alfred\u2026 Bertrand.' + ) + } + ) + + await t.test( + 'should replace three initial full stops with spaces', + async function () { + assert.equal( + processor.processSync('. . .Alfred Bertrand.').toString(), + '\u2026Alfred Bertrand.' + ) + } + ) + + await t.test( + 'should replace three final full stops with spaces', + async function () { + assert.equal( + processor.processSync('Alfred Bertrand. . .').toString(), + 'Alfred Bertrand\u2026' + ) + } + ) +})