diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b24fe9f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Frameable Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 24625eb..9b9d018 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Encode html to its component plain-text and meta style parts ```javascript -{ encode, decode } = require('html-text-projection'); +{ encode, decode } = require('html-text-weaver'); const html = '

Hey, you! Get out of there!

'; diff --git a/index.js b/index.js index d386fc6..0bf3b36 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,6 @@ const ATTRIBUTES = 3; const KEY = 0; const VALUE = 1; -const { document } = (typeof global === 'undefined' ? eval('this') : eval('global')); const linkify = require('./linkify'); class Weaver { @@ -48,7 +47,10 @@ class Weaver { let attributes = null; for (const attributeName of this.tagAttributes[tagName] || []) { attributes = attributes || []; - attributes.push([ attributeName, child.getAttribute(attributeName) ]); + const attrValue = child.getAttribute(attributeName); + if (attrValue) { + attributes.push([ attributeName, attrValue ]); + } } const marker = [tagName, text.length, null, attributes]; meta.push(marker); @@ -70,6 +72,8 @@ class Weaver { } decode({ text, meta }) { + if (text === undefined) throw new Error('no text passed to decode'); + if (meta === undefined) throw new Error('no meta passed to decode'); const links = linkify.findLinkOffsets(text); @@ -82,6 +86,7 @@ class Weaver { ]; const _between = (a, b, c) => a < b && b < c; + const _betweenOrEq = (a, b, c) => a <= b && b <= c; for (const m of meta) { const startInside = _between(link.offsets[0], m[OPEN_OFFSET], link.offsets[1]); @@ -92,10 +97,17 @@ class Weaver { let candidateIndex = null; for (const [i, m] of meta.entries()) { - if (m[OPEN_OFFSET] < link.offsets[0]) + if (m[OPEN_OFFSET] <= link.offsets[0]) candidateIndex = i + 1; } candidateIndex = candidateIndex || 0; + + const enclosingMetaATags = meta + .filter(m => m[TAG_NAME].toLowerCase() === 'a') + .filter(m => _betweenOrEq(m[OPEN_OFFSET], link.offsets[0], m[CLOSE_OFFSET])); + + if (enclosingMetaATags.length) continue LINK; + meta.splice(candidateIndex, 0, anchorMeta); } diff --git a/package.json b/package.json index 32b8453..036eedd 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "html-text-weaver", - "version": "1.0.2", + "version": "1.0.11", "description": "", "main": "index.js", "scripts": { "test": "node_modules/.bin/mocha -u tdd test" }, "author": "", - "license": "ISC", + "license": "MIT", "dependencies": { "basichtml": "^0.22.1", "linkifyjs": "^2.1.8" diff --git a/test.js b/test.js index 2b50dd1..6e98e0a 100644 --- a/test.js +++ b/test.js @@ -16,7 +16,7 @@ suite('encode', () => { }); test('alert', () => { - const encodedText = weaver.encode(escapeHTML('')) + const encodedText = weaver.encode(escapeHTML('')); assert.deepEqual(encodedText, { text: '', meta: [] @@ -33,6 +33,14 @@ suite('encode', () => { }); }); + test('trim the input', () => { + const encodedText = weaver.encode(' my padded text \n'); + assert.deepEqual(encodedText,{ + text: 'my padded text', + meta: [], + }); + }); + test('links', () => { const encodedText = weaver.encode('YAHOO'); assert.deepEqual(encodedText, { @@ -54,7 +62,7 @@ suite('encode', () => { }); test('stacked tags', () => { - const encodedText = weaver.encode('she told him that he was the worst!') + const encodedText = weaver.encode('she told him that he was the worst!'); assert.deepEqual(encodedText, { text: 'she told him that he was the worst!', meta: [ @@ -68,6 +76,13 @@ suite('encode', () => { suite('decode', () => { + test('errors with bad inputs', () => { + const noMeta = () => weaver.decode({ text: 'test' }); + assert.throws(noMeta, Error); + const noText = () => weaver.decode({ meta: [] }); + assert.throws(noText, Error); + }); + test('alert', () => { const decodedText = weaver.decode({ text: '', @@ -95,7 +110,7 @@ suite('decode', () => { ['i', 31, 37], ] }); - assert.equal(decodedText, 'she told him that
he
was the worst!') + assert.equal(decodedText, 'she told him that
he
was the worst!'); }); test('stacked tags', () => { @@ -107,7 +122,7 @@ suite('decode', () => { ['i', 29, 35], ] }); - assert.equal(decodedText, 'she told him that he was the worst!') + assert.equal(decodedText, 'she told him that he was the worst!'); }); }); @@ -127,11 +142,51 @@ suite('linkify', () => { meta: [ ['b', 0, 5], ['i', 6, 15] ] }); - assert.equal(html, 'visit yahoo.com and gmx.net today!'); + assert.equal(html, 'visit yahoo.com and gmx.net today!'); + }); + + test('ignore links with partially overlapping tags', () => { + const overlappingAfter = weaver.decode(weaver.encode('yahoooo.com is great')); + assert.equal(overlappingAfter, 'yahoooo.com is great'); + const overlappingBefore = weaver.decode(weaver.encode('yahoooo.com is great')); + assert.equal(overlappingBefore, 'yahoooo.com is great'); + const notOverlapping = weaver.decode(weaver.encode('yahoooo.com is great')); + assert.equal(notOverlapping, 'yahoooo.com is great'); + }); + + test('linkify inside style', () => { + assert.equal( + weaver.decode(weaver.encode('yahoo.com')), + 'yahoo.com' + ); + }); + + test('round trip without adding extra links', () => { + const html = weaver.decode(weaver.encode(weaver.decode({ + text: 'yahoo.com', + meta: [] + }))); + assert.equal(html, 'yahoo.com'); + }); + + test("round trip without adding extra links when href doesn't match content", () => { + const html = weaver.decode(weaver.encode('yahoo.com')); + assert.equal(html, 'yahoo.com'); }); - + test("don't linkify when we are inside an a tag", () => { + const input = 'A bunch of stuff yadayahoo.com'; + const html = weaver.decode(weaver.encode(input)); + assert.equal(html, input); + }); + test('double round trip without adding extra links', () => { + const html = weaver.decode(weaver.encode(weaver.decode(weaver.encode(weaver.decode({ + text: 'yahoo.com', + meta: [] + }))))); + assert.equal(html, 'yahoo.com'); + }); }); @@ -158,6 +213,10 @@ suite('encode-decode', () => { } }); + test('works with empty string', () => { + const html = weaver.decode(weaver.encode('')); + assert.equal(html, ''); + }); test('newlines', () => { const inputs = [ @@ -166,7 +225,18 @@ suite('encode-decode', () => { for (const input of inputs) { assert.equal(input, weaver.decode(weaver.encode(input))); } + }); - }) + test('no doubling up', () => { + const input = 'visit yahoo.com today!'; + const decodedHtml = weaver.decode(weaver.encode(input)); + assert.equal(decodedHtml, input); + }); + + test('no doubling doubling up', () => { + const input = 'visit yahoo.com today!'; + const decodedHtml = weaver.decode(weaver.encode(weaver.decode(weaver.encode(input)))); + assert.equal(decodedHtml, input); + }); });