1
0
mirror of https://github.com/twitter/twemoji.git synced 2025-01-30 15:32:39 +00:00

Merge pull request #73 from twitter/derekmooney-gh-pages

Derekmooney gh pages
This commit is contained in:
Andrea Giammarchi 2015-04-11 19:18:21 +01:00
commit 398533a464
7 changed files with 431 additions and 53 deletions

View File

@ -148,6 +148,7 @@ Here the list of properties accepted by the optional object that could be passed
```js ```js
{ {
callback: Function, // default the common replacer callback: Function, // default the common replacer
attributes: Function, // default returns {}
base: string, // default MaxCDN base: string, // default MaxCDN
ext: string, // default ".png" ext: string, // default ".png"
className: string, // default "emoji" className: string, // default "emoji"
@ -173,6 +174,19 @@ function imageSourceGenerator(icon, options) {
} }
``` ```
##### attributes
The function to invoke in order to generate additional, custom attributes for the image tag.
By default it is a function like the following one:
```js
function attributesCallback(icon, variant) {
return {
title: 'Emoji: ' + icon + variant
};
}
```
Event handlers cannot be specified via this method, and twemoji-provided attributes (src, alt, className, draggable) cannot be re-defined.
##### base ##### base
The default url is the same as `twemoji.base`, so if you modify the former, it will reflect as default for all parsed strings or nodes. The default url is the same as `twemoji.base`, so if you modify the former, it will reflect as default for all parsed strings or nodes.

96
test.js
View File

@ -306,6 +306,102 @@ wru.test([{
div.getElementsByTagName('img')[0].className === className div.getElementsByTagName('img')[0].className === className
); );
} }
},{
name: 'string parsing + attributes callback',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '36x36/2764.png" title="Emoji: \u2764" data-test="We all &lt;3 emoji"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(icon) {
return {
title: 'Emoji: ' + icon,
'data-test': 'We all <3 emoji'
};
}
}
)
);
}
},{
name: 'string parsing + attributes callback content properly encoded',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '36x36/2764.png" title="&amp;amp;lt;script&amp;amp;gt;alert(&quot;yo&quot;)&amp;amp;lt;/script&amp;amp;gt;"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(icon) {
return {
title: '&amp;lt;script&amp;gt;alert("yo")&amp;lt;/script&amp;gt;'
};
}
}
)
);
}
},{
name: 'string parsing + attributes callback "on" attributes are omitted',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="❤" src="' + base + '36x36/2764.png" title="test"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(icon) {
return {
title: 'test',
onsomething: 'whoops!',
onclick: 'nope',
onmousedown: 'nada'
};
}
}
)
);
}
},{
name: 'DOM parsing + attributes callback',
test: function () {
var img,
// without variant
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(
div, {
attributes: function(icon) {
return {
title: 'Emoji: ' + icon,
'data-test': 'We all <3 emoji',
onclick: 'nope',
onmousedown: 'nada'
};
}
}
);
wru.assert('default parsing works creating 3 nodes', div.childNodes.length === 3);
wru.assert('first child is the expected one', div.removeChild(div.firstChild).nodeValue === 'I ');
img = div.removeChild(div.firstChild);
wru.assert('second child is the image', img.nodeName === 'IMG');
wru.assert('img attributes are OK',
img.className === 'emoji' &&
img.getAttribute('draggable') === 'false' &&
img.src === base + '36x36/2764.png' &&
img.alt === '\u2764' &&
img.onerror === twemoji.onerror &&
img.getAttribute('title') === 'Emoji: \u2764' &&
img.getAttribute('data-test') === 'We all <3 emoji'
);
wru.assert('img on attributes are omitted',
img.onclick === null &&
img.onmousedown === null
);
}
},{ },{
name: 'folder option', name: 'folder option',
test: function () { test: function () {

View File

@ -500,10 +500,22 @@ function createTwemoji(re) {
test: test test: test
}, },
// used to escape HTML special chars in attributes
escaper = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
},
// RegExp based on emoji's official Unicode standards // RegExp based on emoji's official Unicode standards
// http://www.unicode.org/Public/UNIDATA/EmojiSources.txt // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
re = /twemoji/, re = /twemoji/,
// used to find HTML special chars in attributes
rescaper = /[&<>'"]/g,
// nodes with type 1 which should **not** be parsed // nodes with type 1 which should **not** be parsed
shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA/, shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA/,
@ -527,6 +539,15 @@ function createTwemoji(re) {
return document.createTextNode(text); return document.createTextNode(text);
} }
/**
* Utility function to escape html attribute text
* @param string text use in HTML attribute
* @return string text encoded to use in HTML attribute
*/
function escapeHTML(s) {
return s.replace(rescaper, replacer);
}
/** /**
* Default callback used to generate emoji src * Default callback used to generate emoji src
* based on Twitter CDN * based on Twitter CDN
@ -604,6 +625,8 @@ function createTwemoji(re) {
var var
allText = grabAllTextNodes(node, []), allText = grabAllTextNodes(node, []),
length = allText.length, length = allText.length,
attrib,
attrname,
modified, modified,
fragment, fragment,
subnode, subnode,
@ -642,8 +665,19 @@ function createTwemoji(re) {
if (src) { if (src) {
img = new Image(); img = new Image();
img.onerror = twemoji.onerror; img.onerror = twemoji.onerror;
img.className = options.className;
img.setAttribute('draggable', 'false'); img.setAttribute('draggable', 'false');
attrib = options.attributes(icon, variant);
for (attrname in attrib) {
if (
attrib.hasOwnProperty(attrname) &&
// don't allow any handlers to be set + don't allow overrides
attrname.indexOf('on') !== 0 &&
!img.hasAttribute(attrname)
) {
img.setAttribute(attrname, attrib[attrname]);
}
}
img.className = options.className;
img.alt = alt; img.alt = alt;
img.src = src; img.src = src;
modified = true; modified = true;
@ -684,7 +718,11 @@ function createTwemoji(re) {
*/ */
function parseString(str, options) { function parseString(str, options) {
return replace(str, function (match, icon, variant) { return replace(str, function (match, icon, variant) {
var src; var
ret = match,
attrib,
attrname,
src;
// verify the variant is not the FE0E one // verify the variant is not the FE0E one
// this variant means "emoji as text" and should not // this variant means "emoji as text" and should not
// require any action/replacement // require any action/replacement
@ -698,7 +736,7 @@ function createTwemoji(re) {
if (src) { if (src) {
// recycle the match string replacing the emoji // recycle the match string replacing the emoji
// with its image counter part // with its image counter part
match = '<img '.concat( ret = '<img '.concat(
'class="', options.className, '" ', 'class="', options.className, '" ',
'draggable="false" ', 'draggable="false" ',
// needs to preserve user original intent // needs to preserve user original intent
@ -708,15 +746,43 @@ function createTwemoji(re) {
'"', '"',
' src="', ' src="',
src, src,
'"', '"'
'>'
); );
attrib = options.attributes(icon, variant);
for (attrname in attrib) {
if (
attrib.hasOwnProperty(attrname) &&
// don't allow any handlers to be set + don't allow overrides
attrname.indexOf('on') !== 0 &&
ret.indexOf(' ' + attrname + '=') === -1
) {
ret = ret.concat(' ', attrname, '="', escapeHTML(attrib[attrname]), '"');
} }
} }
return match; ret = ret.concat('>');
}
}
return ret;
}); });
} }
/**
* Function used to actually replace HTML special chars
* @param string HTML special char
* @return string encoded HTML special char
*/
function replacer(m) {
return escaper[m];
}
/**
* Default options.attribute callback
* @return null
*/
function returnNull() {
return null;
}
/** /**
* Given a generic value, creates its squared counterpart if it's a number. * Given a generic value, creates its squared counterpart if it's a number.
* As example, number 36 will return '36x36'. * As example, number 36 will return '36x36'.
@ -758,6 +824,7 @@ function createTwemoji(re) {
// otherwise use the DOM tree and parse text nodes only // otherwise use the DOM tree and parse text nodes only
return (typeof what === 'string' ? parseString : parseNode)(what, { return (typeof what === 'string' ? parseString : parseNode)(what, {
callback: how.callback || defaultImageSrcGenerator, callback: how.callback || defaultImageSrcGenerator,
attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
base: typeof how.base === 'string' ? how.base : twemoji.base, base: typeof how.base === 'string' ? how.base : twemoji.base,
ext: how.ext || twemoji.ext, ext: how.ext || twemoji.ext,
size: how.folder || toSizeSquaredAsset(how.size || twemoji.size), size: how.folder || toSizeSquaredAsset(how.size || twemoji.size),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
twemoji.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long