Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/linkify/core/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const TT_POUND = TEXT_TOKENS.POUND;
const TT_PROTOCOL = TEXT_TOKENS.PROTOCOL;
const TT_QUERY = TEXT_TOKENS.QUERY;
const TT_SLASH = TEXT_TOKENS.SLASH;
const TT_UNDERSCORE = TEXT_TOKENS.UNDERSCORE;
const TT_SYM = TEXT_TOKENS.SYM;
const TT_TLD = TEXT_TOKENS.TLD;
const TT_OPENBRACE = TEXT_TOKENS.OPENBRACE;
Expand Down Expand Up @@ -149,6 +150,7 @@ let qsAccepting = [
TT_PROTOCOL,
TT_SLASH,
TT_TLD,
TT_UNDERSCORE,
TT_SYM
];

Expand Down Expand Up @@ -237,6 +239,7 @@ let localpartAccepting = [
TT_PLUS,
TT_POUND,
TT_QUERY,
TT_UNDERSCORE,
TT_SYM,
TT_TLD
];
Expand Down
1 change: 1 addition & 0 deletions src/linkify/core/scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ S_START
.on('#', makeState(TOKENS.POUND))
.on('?', makeState(TOKENS.QUERY))
.on('/', makeState(TOKENS.SLASH))
.on('_', makeState(TOKENS.UNDERSCORE))
.on(COLON, makeState(TOKENS.COLON))
.on('{', makeState(TOKENS.OPENBRACE))
.on('[', makeState(TOKENS.OPENBRACKET))
Expand Down
7 changes: 7 additions & 0 deletions src/linkify/core/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ const QUERY = inheritsToken('?');
*/
const SLASH = inheritsToken('/');

/**
@class UNDERSCORE
@extends TextToken
*/
const UNDERSCORE = inheritsToken('_');

/**
One ore more non-whitespace symbol.
@class SYM
Expand Down Expand Up @@ -176,6 +182,7 @@ let text = {
QUERY,
PROTOCOL,
SLASH,
UNDERSCORE,
SYM,
TLD,
WS,
Expand Down
91 changes: 75 additions & 16 deletions src/linkify/plugins/mention.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,83 @@
Quick Mention parser plugin for linkify
*/
export default function mention(linkify) {
let TT = linkify.scanner.TOKENS; // Text tokens
let MT = linkify.parser.TOKENS; // Multi tokens
let MultiToken = MT.Base;
let S_START = linkify.parser.start;
let S_AT, S_MENTION;

class MENTION extends MultiToken {
constructor(value) {
super(value);
this.type = 'mention';
this.isLink = true;
}
const TT = linkify.scanner.TOKENS; // Text tokens
const {TOKENS: MT, State} = linkify.parser; // Multi tokens, state
const MultiToken = MT.Base;
const S_START = linkify.parser.start;

const TT_DOMAIN = TT.DOMAIN;
const TT_LOCALHOST = TT.LOCALHOST;
const TT_NUM = TT.NUM;
const TT_SLASH = TT.SLASH;
const TT_TLD = TT.TLD;
const TT_UNDERSCORE = TT.UNDERSCORE;

function MENTION(value) {
this.v = value;
}

S_AT = new linkify.parser.State();
S_MENTION = new linkify.parser.State(MENTION);
linkify.inherits(MultiToken, MENTION, {
type: 'mention',
isLink: true,
toHref() {
return '/' + this.toString().substr(1);
}
});

const S_AT = new State();
const S_AT_SYMS = new State();
const S_MENTION = new State(MENTION);
const S_MENTION_SLASH = new State();
const S_MENTION_SLASH_SYMS = new State();

// @
S_START.on(TT.AT, S_AT);
S_AT.on(TT.DOMAIN, S_MENTION);
S_AT.on(TT.TLD, S_MENTION);

// @_,
S_AT.on(TT_UNDERSCORE, S_AT_SYMS);

// @_*
S_AT_SYMS.on(TT_UNDERSCORE, S_AT_SYMS);

// Valid mention (not made up entirely of symbols)
S_AT
.on(TT_DOMAIN, S_MENTION)
.on(TT_LOCALHOST, S_MENTION)
.on(TT_TLD, S_MENTION)
.on(TT_NUM, S_MENTION);

S_AT_SYMS
.on(TT_DOMAIN, S_MENTION)
.on(TT_LOCALHOST, S_MENTION)
.on(TT_TLD, S_MENTION)
.on(TT_NUM, S_MENTION);

// More valid mentions
S_MENTION
.on(TT_DOMAIN, S_MENTION)
.on(TT_LOCALHOST, S_MENTION)
.on(TT_TLD, S_MENTION)
.on(TT_NUM, S_MENTION)
.on(TT_UNDERSCORE, S_MENTION);

// Mention with a slash
S_MENTION.on(TT_SLASH, S_MENTION_SLASH);

// Mention _ trailing stash plus syms
S_MENTION_SLASH.on(TT_UNDERSCORE, S_MENTION_SLASH_SYMS);
S_MENTION_SLASH_SYMS.on(TT_UNDERSCORE, S_MENTION_SLASH_SYMS);

// Once we get a word token, mentions can start up again
S_MENTION_SLASH
.on(TT_DOMAIN, S_MENTION)
.on(TT_LOCALHOST, S_MENTION)
.on(TT_TLD, S_MENTION)
.on(TT_NUM, S_MENTION);

S_MENTION_SLASH_SYMS
.on(TT_DOMAIN, S_MENTION)
.on(TT_LOCALHOST, S_MENTION)
.on(TT_TLD, S_MENTION)
.on(TT_NUM, S_MENTION);
}
2 changes: 1 addition & 1 deletion templates/linkify/plugins/mention.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
;(function (linkify) {
<%= contents %>
mention(linkify);
plugin(linkify);
})(window.linkify);
16 changes: 16 additions & 0 deletions test/qunit/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ QUnit.test('finds valid hashtags', function (assert) {
}]);
});


QUnit.module('linkify-plugin-metntion');

QUnit.test('finds valid metntions', function (assert) {
var result = w.linkify.find('Hey @foo say hello to @bar!');
assert.deepEqual(result, [{
type: 'mention',
value: '@foo',
href: '/foo',
}, {
type: 'mention',
value: '@bar',
href: '/bar'
}]);
});

// HTML rendered in body
var originalHtml = 'Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right? <p>Here is a nested github.com/SoapBox/linkifyjs paragraph</p>';

Expand Down
4 changes: 4 additions & 0 deletions test/spec/linkify/core/parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ var tests = [
'More weird character in http://facebook.com/#aZ?/:@-._~!$&\'()*+,;= that URL',
[TEXT, URL, TEXT],
['More weird character in ', 'http://facebook.com/#aZ?/:@-._~!$&\'()*+,;=', ' that URL']
], [
'Email with a underscore is [email protected] asd',
[TEXT, EMAIL, TEXT],
['Email with a underscore is ', '[email protected]', ' asd']
]
];

Expand Down
103 changes: 95 additions & 8 deletions test/spec/linkify/plugins/mention-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const mention = require(`${__base}linkify/plugins/mention`).default;

describe('linkify/plugins/mention', () => {
it('Cannot parse mentions before applying the plugin', () => {
expect(linkify.find('There is a @mention @YOLO2015 and @1234 and @%^&*( should not work'))
expect(linkify.find('There is a @mention @YOLO2016 and @1234 and @%^&*( should not work'))
.to.be.eql([]);

expect(linkify.test('@wat', 'mention')).to.not.be.ok;
Expand All @@ -12,24 +12,111 @@ describe('linkify/plugins/mention', () => {

describe('after plugin is applied', () => {
before(() => {
debugger;
mention(linkify);
});

it ('Can parse mentions after applying the plugin', () => {
expect(linkify.find('There is a @mention @YOLO2015 and @1234 and @%^&*( should not work'))
.to.be.eql([{
expect(linkify.find('There is a @mention @YOLO2016 and @1234 and @%^&*( should not work')).to.deep.equal([{
type: 'mention',
value: '@mention',
href: '@mention'
href: '/mention'
}, {
type: 'mention',
value: '@YOLO2015',
href: '@YOLO2015'
value: '@YOLO2016',
href: '/YOLO2016'
}, {
type: 'mention',
value: '@1234',
href: '/1234'
}]);

expect(linkify.test('@wat', 'mention')).to.be.ok;
expect(linkify.test('@987', 'mention')).to.not.be.ok;
expect(linkify.test('@987', 'mention')).to.be.ok;
});

it('detects mentions with just text', () => {
expect(linkify.find('Hey @nfrasser')).to.deep.equal([{
type: 'mention',
value: '@nfrasser',
href: '/nfrasser'
}]);
});

it('parses mentions that begin and end with underscores', () => {
expect(linkify.find('Mention for @__lI3t__')).to.deep.equal([{
type: 'mention',
value: '@__lI3t__',
href: '/__lI3t__'
}]);
});

it('parses mentions with hyphens and underscores', () => {
expect(linkify.find('Paging @sir_mc-lovin')).to.deep.equal([{
type: 'mention',
value: '@sir_mc-lovin',
href: '/sir_mc-lovin'
}]);
});

it('parses mentions github team-style mentions with slashes', () => {
expect(linkify.find('Hey @500px/web please review this')).to.deep.equal([{
type: 'mention',
value: '@500px/web',
href: '/500px/web'
}]);
});

it('ignores extra slashes at the end of mentions', () => {
expect(linkify.find('We should get ///@soapbox/_developers/@soapbox/cs//// to review these')).to.deep.equal([{
type: 'mention',
value: '@soapbox/_developers',
href: '/soapbox/_developers'
}, {
type: 'mention',
value: '@soapbox/cs',
href: '/soapbox/cs'
}]);
});

it('does not treat @/.* as a mention', () => {
expect(linkify.find('What about @/ and @/nfrasser?')).to.deep.equal([]);
});

it('ignores text only made up of symbols', () => {
expect(linkify.find('Is @- or @__ a person? What about @%_% no, probably not')).to.deep.equal([]);
});

it('ignores punctuation at the end of mentions', () => {
expect(linkify.find('These people are awesome: @graham, @brennan, and @chris! Also @nick.')).to.deep.equal([{
type: 'mention',
value: '@graham',
href: '/graham'
}, {
type: 'mention',
value: '@brennan',
href: '/brennan'
}, {
type: 'mention',
value: '@chris',
href: '/chris'
}, {
type: 'mention',
value: '@nick',
href: '/nick'
}]);
});

it('detects numerical mentions', () => {
expect(linkify.find('Hey @123 and @456_78910__')).to.deep.equal([{
type: 'mention',
value: '@123',
href: '/123'
}, {
type: 'mention',
value: '@456_78910__',
href: '/456_78910__'
}]);
});

});
});