Skip to content

Commit

Permalink
Merge pull request #7 from curbengh/noopener
Browse files Browse the repository at this point in the history
feat: always inject "noopener external nofollow noreferrer"
  • Loading branch information
SukkaW committed Nov 19, 2019
2 parents b1cd88a + 8d890fb commit ae79530
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 111 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

Add nofollow attribute to all external links automatically.

`hexo-filter-nofollow` add `rel="external nofollow noreferrer"` to all external links for security, privacy and SEO. [Read more](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types).
`hexo-filter-nofollow` add `rel="noopener external nofollow noreferrer"` to all external links for security, privacy and SEO. [Read more](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types).

## Installations

Expand Down
17 changes: 10 additions & 7 deletions lib/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ module.exports = function(data) {
}

const filterExternal = (data) => {
const noFollow = 'external nofollow noreferrer';

return data.replace(/<a.*?(href=['"](.*?)['"]).*?>/gi, (str, hrefStr, href) => {
if (!isExternal(href, config)) return str;

let noFollow = ['noopener', 'external', 'nofollow', 'noreferrer'];

if (/rel=/gi.test(str)) {
return str.replace(/rel="(.*?)"/gi, (relStr, rel) => {
// Avoid duplicate attribute
if (!/(external|nofollow|noreferrer)/gi.test(rel)) relStr = relStr.replace(rel, `${rel} ${noFollow}`);
return relStr;
str = str.replace(/\srel="(.*?)"/gi, (relStr, rel) => {
rel = rel.split(' ');
noFollow.push(...rel);
// De-duplicate
noFollow = [...new Set(noFollow)];

return '';
});
}

return str.replace(hrefStr, `${hrefStr} rel="${noFollow}"`);
return str.replace(hrefStr, `${hrefStr} rel="${noFollow.join(' ')}"`);
});
};

Expand Down
163 changes: 60 additions & 103 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ describe('hexo-filter-nofollow', () => {
hexo.config.url = 'https://example.com';
hexo.config.nofollow = {};

it('Default (field to "site")', () => {
describe('Default', () => {
const content = [
'# External link test',
'1. External link',
'<a href="https://hexo.io/">Hexo</a>',
'2. External link with existed "rel" Attribute',
'<a rel="license" href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE">Hexo</a>',
'<a href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE" rel="license">Hexo</a>',
'3. External link with existed "rel=noopenner", "rel=external" or "rel=noreferrer"',
'<a rel="noopenner" href="https://hexo.io/">Hexo</a>',
'3. External link with existing "rel=noopener", "rel=external" or "rel=noreferrer"',
'<a rel="noopener" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="noreferrer">Hexo</a>',
'<a rel="noopenner noreferrer" href="https://hexo.io/">Hexo</a>',
'<a rel="noopener noreferrer" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="external noreferrer">Hexo</a>',
'4. External link with Other Attributes',
'<a class="img" href="https://hexo.io/">Hexo</a>',
Expand All @@ -33,108 +33,46 @@ describe('hexo-filter-nofollow', () => {
'<a>Anchor</a>'
].join('\n');

const result = nofollowFilter(content);

result.should.eql([
const expected = [
'# External link test',
'1. External link',
'<a href="https://hexo.io/" rel="external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'2. External link with existed "rel" Attribute',
'<a rel="license external nofollow noreferrer" href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE">Hexo</a>',
'<a href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE" rel="license external nofollow noreferrer">Hexo</a>',
'3. External link with existed "rel=noopenner", "rel=external" or "rel=noreferrer"',
'<a rel="noopenner external nofollow noreferrer" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="noreferrer">Hexo</a>',
'<a rel="noopenner noreferrer" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="external noreferrer">Hexo</a>',
'<a href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE" rel="noopener external nofollow noreferrer license">Hexo</a>',
'<a href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE" rel="noopener external nofollow noreferrer license">Hexo</a>',
'3. External link with existing "rel=noopener", "rel=external" or "rel=noreferrer"',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'4. External link with Other Attributes',
'<a class="img" href="https://hexo.io/" rel="external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="external nofollow noreferrer" class="img">Hexo</a>',
'5. Internal link',
'<a href="/archives/foo.html">Link</a>',
'6. Ignore links don\'t have "href" attribute',
'<a>Anchor</a>'
].join('\n'));
});

it('Set field as "post"', () => {
const content = [
'# External link test',
'1. External link',
'<a href="https://hexo.io/">Hexo</a>',
'2. External link with existed "rel" Attribute',
'<a rel="license" href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE">Hexo</a>',
'<a href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE" rel="license">Hexo</a>',
'3. External link with existed "rel=noopenner", "rel=external" or "rel=noreferrer"',
'<a rel="noopenner" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="noreferrer">Hexo</a>',
'<a rel="noopenner noreferrer" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="external noreferrer">Hexo</a>',
'4. External link with Other Attributes',
'<a class="img" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" class="img">Hexo</a>',
'<a class="img" href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer" class="img">Hexo</a>',
'5. Internal link',
'<a href="/archives/foo.html">Link</a>',
'6. Ignore links don\'t have "href" attribute',
'<a>Anchor</a>'
].join('\n');

hexo.config.nofollow.field = 'post';

const data = { content };
const result = nofollowFilter(data).content;

result.should.eql([
'# External link test',
'1. External link',
'<a href="https://hexo.io/" rel="external nofollow noreferrer">Hexo</a>',
'2. External link with existed "rel" Attribute',
'<a rel="license external nofollow noreferrer" href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE">Hexo</a>',
'<a href="https://github.com/hexojs/hexo-filter-nofollow/blob/master/LICENSE" rel="license external nofollow noreferrer">Hexo</a>',
'3. External link with existed "rel=noopenner", "rel=external" or "rel=noreferrer"',
'<a rel="noopenner external nofollow noreferrer" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="noreferrer">Hexo</a>',
'<a rel="noopenner noreferrer" href="https://hexo.io/">Hexo</a>',
'<a href="https://hexo.io/" rel="external noreferrer">Hexo</a>',
'4. External link with Other Attributes',
'<a class="img" href="https://hexo.io/" rel="external nofollow noreferrer">Hexo</a>',
'<a href="https://hexo.io/" rel="external nofollow noreferrer" class="img">Hexo</a>',
'5. Internal link',
'<a href="/archives/foo.html">Link</a>',
'6. Ignore links don\'t have "href" attribute',
'<a>Anchor</a>'
].join('\n'));
it('Default to field = "site"', () => {
const result = nofollowFilter(content);

hexo.config.nofollow.field = 'site';
});
result.should.eql(expected);
});

it('Pass a string to hexo.config.nofollow.exclude', () => {
const content = [
'# Exclude link test',
'1. External link',
'<a href="https://hexo.io/">Hexo</a>',
'2. Ignore links whose hostname is same as config',
'<a href="https://example.com">Example Domain</a>',
'3. Ignore links whose hostname is same as exclude',
'<a href="https://example.org">Example Domain</a>'
].join('\n');
it('field = "post"', () => {
hexo.config.nofollow.field = 'post';

hexo.config.nofollow.exclude = 'example.org';
const data = { content };
const result = nofollowFilter(data).content;

const result = nofollowFilter(content);
result.should.eql(expected);

result.should.eql([
'# Exclude link test',
'1. External link',
'<a href="https://hexo.io/" rel="external nofollow noreferrer">Hexo</a>',
'2. Ignore links whose hostname is same as config',
'<a href="https://example.com">Example Domain</a>',
'3. Ignore links whose hostname is same as exclude',
'<a href="https://example.org">Example Domain</a>'
].join('\n'));
hexo.config.nofollow.field = 'site';
});
});

it('Pass a Array to hexo.config.nofollow.exclude', () => {
describe('Exclude', () => {
const content = [
'# Exclude link test',
'1. External link',
Expand All @@ -146,19 +84,38 @@ describe('hexo-filter-nofollow', () => {
'<a href="https://test.example.org">Example Domain</a>'
].join('\n');

hexo.config.nofollow.exclude = ['example.org', 'test.example.org'];

const result = nofollowFilter(content);

result.should.eql([
'# Exclude link test',
'1. External link',
'<a href="https://hexo.io/" rel="external nofollow noreferrer">Hexo</a>',
'2. Ignore links whose hostname is same as config',
'<a href="https://example.com">Example Domain</a>',
'3. Ignore links whose hostname is included in exclude',
'<a href="https://example.org">Example Domain</a>',
'<a href="https://test.example.org">Example Domain</a>'
].join('\n'));
it('String', () => {
hexo.config.nofollow.exclude = 'example.org';

const result = nofollowFilter(content);

result.should.eql([
'# Exclude link test',
'1. External link',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'2. Ignore links whose hostname is same as config',
'<a href="https://example.com">Example Domain</a>',
'3. Ignore links whose hostname is included in exclude',
'<a href="https://example.org">Example Domain</a>',
'<a href="https://test.example.org" rel="noopener external nofollow noreferrer">Example Domain</a>'
].join('\n'));
});

it('Array', () => {
hexo.config.nofollow.exclude = ['example.org', 'test.example.org'];

const result = nofollowFilter(content);

result.should.eql([
'# Exclude link test',
'1. External link',
'<a href="https://hexo.io/" rel="noopener external nofollow noreferrer">Hexo</a>',
'2. Ignore links whose hostname is same as config',
'<a href="https://example.com">Example Domain</a>',
'3. Ignore links whose hostname is included in exclude',
'<a href="https://example.org">Example Domain</a>',
'<a href="https://test.example.org">Example Domain</a>'
].join('\n'));
});
});
});

0 comments on commit ae79530

Please sign in to comment.