diff --git a/1/templates/preview.html b/1/templates/preview.html
new file mode 100644
index 00000000..1ee8325a
--- /dev/null
+++ b/1/templates/preview.html
@@ -0,0 +1,55 @@
+
+
+
+ Twitter Emoji (Twemoji) Preview
+
+
+
+
+
+
+
+
+
diff --git a/1/test/preview-svg.html b/1/test/preview-svg.html
new file mode 100644
index 00000000..cfb1657f
--- /dev/null
+++ b/1/test/preview-svg.html
@@ -0,0 +1,928 @@
+
+
+
+ Twitter Emoji (Twemoji) Preview
+
+
+
+
+
+
+ - 🀄
+ - 🃏
+ - 🅰
+ - 🅱
+ - 🅾
+ - 🅿
+ - 🆎
+ - 🆑
+ - 🆒
+ - 🆓
+ - 🆔
+ - 🆕
+ - 🆖
+ - 🆗
+ - 🆘
+ - 🆙
+ - 🆚
+ - 🇦
+ - 🇧
+ - 🇨🇳
+ - 🇨
+ - 🇩🇪
+ - 🇩
+ - 🇪🇸
+ - 🇪
+ - 🇫🇷
+ - 🇫
+ - 🇬🇧
+ - 🇬
+ - 🇭
+ - 🇮🇹
+ - 🇮
+ - 🇯🇵
+ - 🇯
+ - 🇰🇷
+ - 🇰
+ - 🇱
+ - 🇲
+ - 🇳
+ - 🇴
+ - 🇵
+ - 🇶
+ - 🇷🇺
+ - 🇷
+ - 🇸
+ - 🇹
+ - 🇺🇸
+ - 🇺
+ - 🇻
+ - 🇼
+ - 🇽
+ - 🇾
+ - 🇿
+ - 🈁
+ - 🈂
+ - 🈚
+ - 🈯
+ - 🈲
+ - 🈳
+ - 🈴
+ - 🈵
+ - 🈶
+ - 🈷
+ - 🈸
+ - 🈹
+ - 🈺
+ - 🉐
+ - 🉑
+ - 🌀
+ - 🌁
+ - 🌂
+ - 🌃
+ - 🌄
+ - 🌅
+ - 🌆
+ - 🌇
+ - 🌈
+ - 🌉
+ - 🌊
+ - 🌋
+ - 🌌
+ - 🌍
+ - 🌎
+ - 🌏
+ - 🌐
+ - 🌑
+ - 🌒
+ - 🌓
+ - 🌔
+ - 🌕
+ - 🌖
+ - 🌗
+ - 🌘
+ - 🌙
+ - 🌚
+ - 🌛
+ - 🌜
+ - 🌝
+ - 🌞
+ - 🌟
+ - 🌠
+ - 🌰
+ - 🌱
+ - 🌲
+ - 🌳
+ - 🌴
+ - 🌵
+ - 🌷
+ - 🌸
+ - 🌹
+ - 🌺
+ - 🌻
+ - 🌼
+ - 🌽
+ - 🌾
+ - 🌿
+ - 🍀
+ - 🍁
+ - 🍂
+ - 🍃
+ - 🍄
+ - 🍅
+ - 🍆
+ - 🍇
+ - 🍈
+ - 🍉
+ - 🍊
+ - 🍋
+ - 🍌
+ - 🍍
+ - 🍎
+ - 🍏
+ - 🍐
+ - 🍑
+ - 🍒
+ - 🍓
+ - 🍔
+ - 🍕
+ - 🍖
+ - 🍗
+ - 🍘
+ - 🍙
+ - 🍚
+ - 🍛
+ - 🍜
+ - 🍝
+ - 🍞
+ - 🍟
+ - 🍠
+ - 🍡
+ - 🍢
+ - 🍣
+ - 🍤
+ - 🍥
+ - 🍦
+ - 🍧
+ - 🍨
+ - 🍩
+ - 🍪
+ - 🍫
+ - 🍬
+ - 🍭
+ - 🍮
+ - 🍯
+ - 🍰
+ - 🍱
+ - 🍲
+ - 🍳
+ - 🍴
+ - 🍵
+ - 🍶
+ - 🍷
+ - 🍸
+ - 🍹
+ - 🍺
+ - 🍻
+ - 🍼
+ - 🎀
+ - 🎁
+ - 🎂
+ - 🎃
+ - 🎄
+ - 🎅
+ - 🎆
+ - 🎇
+ - 🎈
+ - 🎉
+ - 🎊
+ - 🎋
+ - 🎌
+ - 🎍
+ - 🎎
+ - 🎏
+ - 🎐
+ - 🎑
+ - 🎒
+ - 🎓
+ - 🎠
+ - 🎡
+ - 🎢
+ - 🎣
+ - 🎤
+ - 🎥
+ - 🎦
+ - 🎧
+ - 🎨
+ - 🎩
+ - 🎪
+ - 🎫
+ - 🎬
+ - 🎭
+ - 🎮
+ - 🎯
+ - 🎰
+ - 🎱
+ - 🎲
+ - 🎳
+ - 🎴
+ - 🎵
+ - 🎶
+ - 🎷
+ - 🎸
+ - 🎹
+ - 🎺
+ - 🎻
+ - 🎼
+ - 🎽
+ - 🎾
+ - 🎿
+ - 🏀
+ - 🏁
+ - 🏂
+ - 🏃
+ - 🏄
+ - 🏆
+ - 🏇
+ - 🏈
+ - 🏉
+ - 🏊
+ - 🏠
+ - 🏡
+ - 🏢
+ - 🏣
+ - 🏤
+ - 🏥
+ - 🏦
+ - 🏧
+ - 🏨
+ - 🏩
+ - 🏪
+ - 🏫
+ - 🏬
+ - 🏭
+ - 🏮
+ - 🏯
+ - 🏰
+ - 🐀
+ - 🐁
+ - 🐂
+ - 🐃
+ - 🐄
+ - 🐅
+ - 🐆
+ - 🐇
+ - 🐈
+ - 🐉
+ - 🐊
+ - 🐋
+ - 🐌
+ - 🐍
+ - 🐎
+ - 🐏
+ - 🐐
+ - 🐑
+ - 🐒
+ - 🐓
+ - 🐔
+ - 🐕
+ - 🐖
+ - 🐗
+ - 🐘
+ - 🐙
+ - 🐚
+ - 🐛
+ - 🐜
+ - 🐝
+ - 🐞
+ - 🐟
+ - 🐠
+ - 🐡
+ - 🐢
+ - 🐣
+ - 🐤
+ - 🐥
+ - 🐦
+ - 🐧
+ - 🐨
+ - 🐩
+ - 🐪
+ - 🐫
+ - 🐬
+ - 🐭
+ - 🐮
+ - 🐯
+ - 🐰
+ - 🐱
+ - 🐲
+ - 🐳
+ - 🐴
+ - 🐵
+ - 🐶
+ - 🐷
+ - 🐸
+ - 🐹
+ - 🐺
+ - 🐻
+ - 🐼
+ - 🐽
+ - 🐾
+ - 👀
+ - 👂
+ - 👃
+ - 👄
+ - 👅
+ - 👆
+ - 👇
+ - 👈
+ - 👉
+ - 👊
+ - 👋
+ - 👌
+ - 👍
+ - 👎
+ - 👏
+ - 👐
+ - 👑
+ - 👒
+ - 👓
+ - 👔
+ - 👕
+ - 👖
+ - 👗
+ - 👘
+ - 👙
+ - 👚
+ - 👛
+ - 👜
+ - 👝
+ - 👞
+ - 👟
+ - 👠
+ - 👡
+ - 👢
+ - 👣
+ - 👤
+ - 👥
+ - 👦
+ - 👧
+ - 👨
+ - 👩
+ - 👪
+ - 👫
+ - 👬
+ - 👭
+ - 👮
+ - 👯
+ - 👰
+ - 👱
+ - 👲
+ - 👳
+ - 👴
+ - 👵
+ - 👶
+ - 👷
+ - 👸
+ - 👹
+ - 👺
+ - 👻
+ - 👼
+ - 👽
+ - 👾
+ - 👿
+ - 💀
+ - 💁
+ - 💂
+ - 💃
+ - 💄
+ - 💅
+ - 💆
+ - 💇
+ - 💈
+ - 💉
+ - 💊
+ - 💋
+ - 💌
+ - 💍
+ - 💎
+ - 💏
+ - 💐
+ - 💑
+ - 💒
+ - 💓
+ - 💔
+ - 💕
+ - 💖
+ - 💗
+ - 💘
+ - 💙
+ - 💚
+ - 💛
+ - 💜
+ - 💝
+ - 💞
+ - 💟
+ - 💠
+ - 💡
+ - 💢
+ - 💣
+ - 💤
+ - 💥
+ - 💦
+ - 💧
+ - 💨
+ - 💩
+ - 💪
+ - 💫
+ - 💬
+ - 💭
+ - 💮
+ - 💯
+ - 💰
+ - 💱
+ - 💲
+ - 💳
+ - 💴
+ - 💵
+ - 💶
+ - 💷
+ - 💸
+ - 💹
+ - 💺
+ - 💻
+ - 💼
+ - 💽
+ - 💾
+ - 💿
+ - 📀
+ - 📁
+ - 📂
+ - 📃
+ - 📄
+ - 📅
+ - 📆
+ - 📇
+ - 📈
+ - 📉
+ - 📊
+ - 📋
+ - 📌
+ - 📍
+ - 📎
+ - 📏
+ - 📐
+ - 📑
+ - 📒
+ - 📓
+ - 📔
+ - 📕
+ - 📖
+ - 📗
+ - 📘
+ - 📙
+ - 📚
+ - 📛
+ - 📜
+ - 📝
+ - 📞
+ - 📟
+ - 📠
+ - 📡
+ - 📢
+ - 📣
+ - 📤
+ - 📥
+ - 📦
+ - 📧
+ - 📨
+ - 📩
+ - 📪
+ - 📫
+ - 📬
+ - 📭
+ - 📮
+ - 📯
+ - 📰
+ - 📱
+ - 📲
+ - 📳
+ - 📴
+ - 📵
+ - 📶
+ - 📷
+ - 📹
+ - 📺
+ - 📻
+ - 📼
+ - 🔀
+ - 🔁
+ - 🔂
+ - 🔃
+ - 🔄
+ - 🔅
+ - 🔆
+ - 🔇
+ - 🔈
+ - 🔉
+ - 🔊
+ - 🔋
+ - 🔌
+ - 🔍
+ - 🔎
+ - 🔏
+ - 🔐
+ - 🔑
+ - 🔒
+ - 🔓
+ - 🔔
+ - 🔕
+ - 🔖
+ - 🔗
+ - 🔘
+ - 🔙
+ - 🔚
+ - 🔛
+ - 🔜
+ - 🔝
+ - 🔞
+ - 🔟
+ - 🔠
+ - 🔡
+ - 🔢
+ - 🔣
+ - 🔤
+ - 🔥
+ - 🔦
+ - 🔧
+ - 🔨
+ - 🔩
+ - 🔪
+ - 🔫
+ - 🔬
+ - 🔭
+ - 🔮
+ - 🔯
+ - 🔰
+ - 🔱
+ - 🔲
+ - 🔳
+ - 🔴
+ - 🔵
+ - 🔶
+ - 🔷
+ - 🔸
+ - 🔹
+ - 🔺
+ - 🔻
+ - 🔼
+ - 🔽
+ - 🕐
+ - 🕑
+ - 🕒
+ - 🕓
+ - 🕔
+ - 🕕
+ - 🕖
+ - 🕗
+ - 🕘
+ - 🕙
+ - 🕚
+ - 🕛
+ - 🕜
+ - 🕝
+ - 🕞
+ - 🕟
+ - 🕠
+ - 🕡
+ - 🕢
+ - 🕣
+ - 🕤
+ - 🕥
+ - 🕦
+ - 🕧
+ - 🗻
+ - 🗼
+ - 🗽
+ - 🗾
+ - 🗿
+ - 😀
+ - 😁
+ - 😂
+ - 😃
+ - 😄
+ - 😅
+ - 😆
+ - 😇
+ - 😈
+ - 😉
+ - 😊
+ - 😋
+ - 😌
+ - 😍
+ - 😎
+ - 😏
+ - 😐
+ - 😑
+ - 😒
+ - 😓
+ - 😔
+ - 😕
+ - 😖
+ - 😗
+ - 😘
+ - 😙
+ - 😚
+ - 😛
+ - 😜
+ - 😝
+ - 😞
+ - 😟
+ - 😠
+ - 😡
+ - 😢
+ - 😣
+ - 😤
+ - 😥
+ - 😦
+ - 😧
+ - 😨
+ - 😩
+ - 😪
+ - 😫
+ - 😬
+ - 😭
+ - 😮
+ - 😯
+ - 😰
+ - 😱
+ - 😲
+ - 😳
+ - 😴
+ - 😵
+ - 😶
+ - 😷
+ - 😸
+ - 😹
+ - 😺
+ - 😻
+ - 😼
+ - 😽
+ - 😾
+ - 😿
+ - 🙀
+ - 🙁
+ - 🙂
+ - 🙅
+ - 🙆
+ - 🙇
+ - 🙈
+ - 🙉
+ - 🙊
+ - 🙋
+ - 🙌
+ - 🙍
+ - 🙎
+ - 🙏
+ - 🚀
+ - 🚁
+ - 🚂
+ - 🚃
+ - 🚄
+ - 🚅
+ - 🚆
+ - 🚇
+ - 🚈
+ - 🚉
+ - 🚊
+ - 🚋
+ - 🚌
+ - 🚍
+ - 🚎
+ - 🚏
+ - 🚐
+ - 🚑
+ - 🚒
+ - 🚓
+ - 🚔
+ - 🚕
+ - 🚖
+ - 🚗
+ - 🚘
+ - 🚙
+ - 🚚
+ - 🚛
+ - 🚜
+ - 🚝
+ - 🚞
+ - 🚟
+ - 🚠
+ - 🚡
+ - 🚢
+ - 🚣
+ - 🚤
+ - 🚥
+ - 🚦
+ - 🚧
+ - 🚨
+ - 🚩
+ - 🚪
+ - 🚫
+ - 🚬
+ - 🚭
+ - 🚮
+ - 🚯
+ - 🚰
+ - 🚱
+ - 🚲
+ - 🚳
+ - 🚴
+ - 🚵
+ - 🚶
+ - 🚷
+ - 🚸
+ - 🚹
+ - 🚺
+ - 🚻
+ - 🚼
+ - 🚽
+ - 🚾
+ - 🚿
+ - 🛀
+ - 🛁
+ - 🛂
+ - 🛃
+ - 🛄
+ - 🛅
+ - ‼
+ - ⁉
+ - ™
+ - ℹ
+ - ↔
+ - ↕
+ - ↖
+ - ↗
+ - ↘
+ - ↙
+ - ↩
+ - ↪
+ - #⃣
+ - ⌚
+ - ⌛
+ - ⏩
+ - ⏪
+ - ⏫
+ - ⏬
+ - ⏰
+ - ⏳
+ - Ⓜ
+ - ▪
+ - ▫
+ - ▶
+ - ◀
+ - ◻
+ - ◼
+ - ◽
+ - ◾
+ - ☀
+ - ☁
+ - ☎
+ - ☑
+ - ☔
+ - ☕
+ - ☝
+ - ☺
+ - ♈
+ - ♉
+ - ♊
+ - ♋
+ - ♌
+ - ♍
+ - ♎
+ - ♏
+ - ♐
+ - ♑
+ - ♒
+ - ♓
+ - ♠
+ - ♣
+ - ♥
+ - ♦
+ - ♨
+ - ♻
+ - ♿
+ - ⚓
+ - ⚠
+ - ⚡
+ - ⚪
+ - ⚫
+ - ⚽
+ - ⚾
+ - ⛄
+ - ⛅
+ - ⛎
+ - ⛔
+ - ⛪
+ - ⛲
+ - ⛳
+ - ⛵
+ - ⛺
+ - ⛽
+ - ✂
+ - ✅
+ - ✈
+ - ✉
+ - ✊
+ - ✋
+ - ✌
+ - ✏
+ - ✒
+ - ✔
+ - ✖
+ - ✨
+ - ✳
+ - ✴
+ - ❄
+ - ❇
+ - ❌
+ - ❎
+ - ❓
+ - ❔
+ - ❕
+ - ❗
+ - ❤
+ - ➕
+ - ➖
+ - ➗
+ - ➡
+ - ➰
+ - ➿
+ - ⤴
+ - ⤵
+ - ⬅
+ - ⬆
+ - ⬇
+ - ⬛
+ - ⬜
+ - ⭐
+ - ⭕
+ - 0⃣
+ - 〰
+ - 〽
+ - 1⃣
+ - 2⃣
+ - ㊗
+ - ㊙
+ - 3⃣
+ - 4⃣
+ - 5⃣
+ - 6⃣
+ - 7⃣
+ - 8⃣
+ - 9⃣
+ - ©
+ - ®
+ -
+
+
+
+
diff --git a/1/test/preview.html b/1/test/preview.html
new file mode 100644
index 00000000..db139f4d
--- /dev/null
+++ b/1/test/preview.html
@@ -0,0 +1,928 @@
+
+
+
+ Twitter Emoji (Twemoji) Preview
+
+
+
+
+
+
+ - 🀄
+ - 🃏
+ - 🅰
+ - 🅱
+ - 🅾
+ - 🅿
+ - 🆎
+ - 🆑
+ - 🆒
+ - 🆓
+ - 🆔
+ - 🆕
+ - 🆖
+ - 🆗
+ - 🆘
+ - 🆙
+ - 🆚
+ - 🇦
+ - 🇧
+ - 🇨🇳
+ - 🇨
+ - 🇩🇪
+ - 🇩
+ - 🇪🇸
+ - 🇪
+ - 🇫🇷
+ - 🇫
+ - 🇬🇧
+ - 🇬
+ - 🇭
+ - 🇮🇹
+ - 🇮
+ - 🇯🇵
+ - 🇯
+ - 🇰🇷
+ - 🇰
+ - 🇱
+ - 🇲
+ - 🇳
+ - 🇴
+ - 🇵
+ - 🇶
+ - 🇷🇺
+ - 🇷
+ - 🇸
+ - 🇹
+ - 🇺🇸
+ - 🇺
+ - 🇻
+ - 🇼
+ - 🇽
+ - 🇾
+ - 🇿
+ - 🈁
+ - 🈂
+ - 🈚
+ - 🈯
+ - 🈲
+ - 🈳
+ - 🈴
+ - 🈵
+ - 🈶
+ - 🈷
+ - 🈸
+ - 🈹
+ - 🈺
+ - 🉐
+ - 🉑
+ - 🌀
+ - 🌁
+ - 🌂
+ - 🌃
+ - 🌄
+ - 🌅
+ - 🌆
+ - 🌇
+ - 🌈
+ - 🌉
+ - 🌊
+ - 🌋
+ - 🌌
+ - 🌍
+ - 🌎
+ - 🌏
+ - 🌐
+ - 🌑
+ - 🌒
+ - 🌓
+ - 🌔
+ - 🌕
+ - 🌖
+ - 🌗
+ - 🌘
+ - 🌙
+ - 🌚
+ - 🌛
+ - 🌜
+ - 🌝
+ - 🌞
+ - 🌟
+ - 🌠
+ - 🌰
+ - 🌱
+ - 🌲
+ - 🌳
+ - 🌴
+ - 🌵
+ - 🌷
+ - 🌸
+ - 🌹
+ - 🌺
+ - 🌻
+ - 🌼
+ - 🌽
+ - 🌾
+ - 🌿
+ - 🍀
+ - 🍁
+ - 🍂
+ - 🍃
+ - 🍄
+ - 🍅
+ - 🍆
+ - 🍇
+ - 🍈
+ - 🍉
+ - 🍊
+ - 🍋
+ - 🍌
+ - 🍍
+ - 🍎
+ - 🍏
+ - 🍐
+ - 🍑
+ - 🍒
+ - 🍓
+ - 🍔
+ - 🍕
+ - 🍖
+ - 🍗
+ - 🍘
+ - 🍙
+ - 🍚
+ - 🍛
+ - 🍜
+ - 🍝
+ - 🍞
+ - 🍟
+ - 🍠
+ - 🍡
+ - 🍢
+ - 🍣
+ - 🍤
+ - 🍥
+ - 🍦
+ - 🍧
+ - 🍨
+ - 🍩
+ - 🍪
+ - 🍫
+ - 🍬
+ - 🍭
+ - 🍮
+ - 🍯
+ - 🍰
+ - 🍱
+ - 🍲
+ - 🍳
+ - 🍴
+ - 🍵
+ - 🍶
+ - 🍷
+ - 🍸
+ - 🍹
+ - 🍺
+ - 🍻
+ - 🍼
+ - 🎀
+ - 🎁
+ - 🎂
+ - 🎃
+ - 🎄
+ - 🎅
+ - 🎆
+ - 🎇
+ - 🎈
+ - 🎉
+ - 🎊
+ - 🎋
+ - 🎌
+ - 🎍
+ - 🎎
+ - 🎏
+ - 🎐
+ - 🎑
+ - 🎒
+ - 🎓
+ - 🎠
+ - 🎡
+ - 🎢
+ - 🎣
+ - 🎤
+ - 🎥
+ - 🎦
+ - 🎧
+ - 🎨
+ - 🎩
+ - 🎪
+ - 🎫
+ - 🎬
+ - 🎭
+ - 🎮
+ - 🎯
+ - 🎰
+ - 🎱
+ - 🎲
+ - 🎳
+ - 🎴
+ - 🎵
+ - 🎶
+ - 🎷
+ - 🎸
+ - 🎹
+ - 🎺
+ - 🎻
+ - 🎼
+ - 🎽
+ - 🎾
+ - 🎿
+ - 🏀
+ - 🏁
+ - 🏂
+ - 🏃
+ - 🏄
+ - 🏆
+ - 🏇
+ - 🏈
+ - 🏉
+ - 🏊
+ - 🏠
+ - 🏡
+ - 🏢
+ - 🏣
+ - 🏤
+ - 🏥
+ - 🏦
+ - 🏧
+ - 🏨
+ - 🏩
+ - 🏪
+ - 🏫
+ - 🏬
+ - 🏭
+ - 🏮
+ - 🏯
+ - 🏰
+ - 🐀
+ - 🐁
+ - 🐂
+ - 🐃
+ - 🐄
+ - 🐅
+ - 🐆
+ - 🐇
+ - 🐈
+ - 🐉
+ - 🐊
+ - 🐋
+ - 🐌
+ - 🐍
+ - 🐎
+ - 🐏
+ - 🐐
+ - 🐑
+ - 🐒
+ - 🐓
+ - 🐔
+ - 🐕
+ - 🐖
+ - 🐗
+ - 🐘
+ - 🐙
+ - 🐚
+ - 🐛
+ - 🐜
+ - 🐝
+ - 🐞
+ - 🐟
+ - 🐠
+ - 🐡
+ - 🐢
+ - 🐣
+ - 🐤
+ - 🐥
+ - 🐦
+ - 🐧
+ - 🐨
+ - 🐩
+ - 🐪
+ - 🐫
+ - 🐬
+ - 🐭
+ - 🐮
+ - 🐯
+ - 🐰
+ - 🐱
+ - 🐲
+ - 🐳
+ - 🐴
+ - 🐵
+ - 🐶
+ - 🐷
+ - 🐸
+ - 🐹
+ - 🐺
+ - 🐻
+ - 🐼
+ - 🐽
+ - 🐾
+ - 👀
+ - 👂
+ - 👃
+ - 👄
+ - 👅
+ - 👆
+ - 👇
+ - 👈
+ - 👉
+ - 👊
+ - 👋
+ - 👌
+ - 👍
+ - 👎
+ - 👏
+ - 👐
+ - 👑
+ - 👒
+ - 👓
+ - 👔
+ - 👕
+ - 👖
+ - 👗
+ - 👘
+ - 👙
+ - 👚
+ - 👛
+ - 👜
+ - 👝
+ - 👞
+ - 👟
+ - 👠
+ - 👡
+ - 👢
+ - 👣
+ - 👤
+ - 👥
+ - 👦
+ - 👧
+ - 👨
+ - 👩
+ - 👪
+ - 👫
+ - 👬
+ - 👭
+ - 👮
+ - 👯
+ - 👰
+ - 👱
+ - 👲
+ - 👳
+ - 👴
+ - 👵
+ - 👶
+ - 👷
+ - 👸
+ - 👹
+ - 👺
+ - 👻
+ - 👼
+ - 👽
+ - 👾
+ - 👿
+ - 💀
+ - 💁
+ - 💂
+ - 💃
+ - 💄
+ - 💅
+ - 💆
+ - 💇
+ - 💈
+ - 💉
+ - 💊
+ - 💋
+ - 💌
+ - 💍
+ - 💎
+ - 💏
+ - 💐
+ - 💑
+ - 💒
+ - 💓
+ - 💔
+ - 💕
+ - 💖
+ - 💗
+ - 💘
+ - 💙
+ - 💚
+ - 💛
+ - 💜
+ - 💝
+ - 💞
+ - 💟
+ - 💠
+ - 💡
+ - 💢
+ - 💣
+ - 💤
+ - 💥
+ - 💦
+ - 💧
+ - 💨
+ - 💩
+ - 💪
+ - 💫
+ - 💬
+ - 💭
+ - 💮
+ - 💯
+ - 💰
+ - 💱
+ - 💲
+ - 💳
+ - 💴
+ - 💵
+ - 💶
+ - 💷
+ - 💸
+ - 💹
+ - 💺
+ - 💻
+ - 💼
+ - 💽
+ - 💾
+ - 💿
+ - 📀
+ - 📁
+ - 📂
+ - 📃
+ - 📄
+ - 📅
+ - 📆
+ - 📇
+ - 📈
+ - 📉
+ - 📊
+ - 📋
+ - 📌
+ - 📍
+ - 📎
+ - 📏
+ - 📐
+ - 📑
+ - 📒
+ - 📓
+ - 📔
+ - 📕
+ - 📖
+ - 📗
+ - 📘
+ - 📙
+ - 📚
+ - 📛
+ - 📜
+ - 📝
+ - 📞
+ - 📟
+ - 📠
+ - 📡
+ - 📢
+ - 📣
+ - 📤
+ - 📥
+ - 📦
+ - 📧
+ - 📨
+ - 📩
+ - 📪
+ - 📫
+ - 📬
+ - 📭
+ - 📮
+ - 📯
+ - 📰
+ - 📱
+ - 📲
+ - 📳
+ - 📴
+ - 📵
+ - 📶
+ - 📷
+ - 📹
+ - 📺
+ - 📻
+ - 📼
+ - 🔀
+ - 🔁
+ - 🔂
+ - 🔃
+ - 🔄
+ - 🔅
+ - 🔆
+ - 🔇
+ - 🔈
+ - 🔉
+ - 🔊
+ - 🔋
+ - 🔌
+ - 🔍
+ - 🔎
+ - 🔏
+ - 🔐
+ - 🔑
+ - 🔒
+ - 🔓
+ - 🔔
+ - 🔕
+ - 🔖
+ - 🔗
+ - 🔘
+ - 🔙
+ - 🔚
+ - 🔛
+ - 🔜
+ - 🔝
+ - 🔞
+ - 🔟
+ - 🔠
+ - 🔡
+ - 🔢
+ - 🔣
+ - 🔤
+ - 🔥
+ - 🔦
+ - 🔧
+ - 🔨
+ - 🔩
+ - 🔪
+ - 🔫
+ - 🔬
+ - 🔭
+ - 🔮
+ - 🔯
+ - 🔰
+ - 🔱
+ - 🔲
+ - 🔳
+ - 🔴
+ - 🔵
+ - 🔶
+ - 🔷
+ - 🔸
+ - 🔹
+ - 🔺
+ - 🔻
+ - 🔼
+ - 🔽
+ - 🕐
+ - 🕑
+ - 🕒
+ - 🕓
+ - 🕔
+ - 🕕
+ - 🕖
+ - 🕗
+ - 🕘
+ - 🕙
+ - 🕚
+ - 🕛
+ - 🕜
+ - 🕝
+ - 🕞
+ - 🕟
+ - 🕠
+ - 🕡
+ - 🕢
+ - 🕣
+ - 🕤
+ - 🕥
+ - 🕦
+ - 🕧
+ - 🗻
+ - 🗼
+ - 🗽
+ - 🗾
+ - 🗿
+ - 😀
+ - 😁
+ - 😂
+ - 😃
+ - 😄
+ - 😅
+ - 😆
+ - 😇
+ - 😈
+ - 😉
+ - 😊
+ - 😋
+ - 😌
+ - 😍
+ - 😎
+ - 😏
+ - 😐
+ - 😑
+ - 😒
+ - 😓
+ - 😔
+ - 😕
+ - 😖
+ - 😗
+ - 😘
+ - 😙
+ - 😚
+ - 😛
+ - 😜
+ - 😝
+ - 😞
+ - 😟
+ - 😠
+ - 😡
+ - 😢
+ - 😣
+ - 😤
+ - 😥
+ - 😦
+ - 😧
+ - 😨
+ - 😩
+ - 😪
+ - 😫
+ - 😬
+ - 😭
+ - 😮
+ - 😯
+ - 😰
+ - 😱
+ - 😲
+ - 😳
+ - 😴
+ - 😵
+ - 😶
+ - 😷
+ - 😸
+ - 😹
+ - 😺
+ - 😻
+ - 😼
+ - 😽
+ - 😾
+ - 😿
+ - 🙀
+ - 🙁
+ - 🙂
+ - 🙅
+ - 🙆
+ - 🙇
+ - 🙈
+ - 🙉
+ - 🙊
+ - 🙋
+ - 🙌
+ - 🙍
+ - 🙎
+ - 🙏
+ - 🚀
+ - 🚁
+ - 🚂
+ - 🚃
+ - 🚄
+ - 🚅
+ - 🚆
+ - 🚇
+ - 🚈
+ - 🚉
+ - 🚊
+ - 🚋
+ - 🚌
+ - 🚍
+ - 🚎
+ - 🚏
+ - 🚐
+ - 🚑
+ - 🚒
+ - 🚓
+ - 🚔
+ - 🚕
+ - 🚖
+ - 🚗
+ - 🚘
+ - 🚙
+ - 🚚
+ - 🚛
+ - 🚜
+ - 🚝
+ - 🚞
+ - 🚟
+ - 🚠
+ - 🚡
+ - 🚢
+ - 🚣
+ - 🚤
+ - 🚥
+ - 🚦
+ - 🚧
+ - 🚨
+ - 🚩
+ - 🚪
+ - 🚫
+ - 🚬
+ - 🚭
+ - 🚮
+ - 🚯
+ - 🚰
+ - 🚱
+ - 🚲
+ - 🚳
+ - 🚴
+ - 🚵
+ - 🚶
+ - 🚷
+ - 🚸
+ - 🚹
+ - 🚺
+ - 🚻
+ - 🚼
+ - 🚽
+ - 🚾
+ - 🚿
+ - 🛀
+ - 🛁
+ - 🛂
+ - 🛃
+ - 🛄
+ - 🛅
+ - ‼
+ - ⁉
+ - ™
+ - ℹ
+ - ↔
+ - ↕
+ - ↖
+ - ↗
+ - ↘
+ - ↙
+ - ↩
+ - ↪
+ - #⃣
+ - ⌚
+ - ⌛
+ - ⏩
+ - ⏪
+ - ⏫
+ - ⏬
+ - ⏰
+ - ⏳
+ - Ⓜ
+ - ▪
+ - ▫
+ - ▶
+ - ◀
+ - ◻
+ - ◼
+ - ◽
+ - ◾
+ - ☀
+ - ☁
+ - ☎
+ - ☑
+ - ☔
+ - ☕
+ - ☝
+ - ☺
+ - ♈
+ - ♉
+ - ♊
+ - ♋
+ - ♌
+ - ♍
+ - ♎
+ - ♏
+ - ♐
+ - ♑
+ - ♒
+ - ♓
+ - ♠
+ - ♣
+ - ♥
+ - ♦
+ - ♨
+ - ♻
+ - ♿
+ - ⚓
+ - ⚠
+ - ⚡
+ - ⚪
+ - ⚫
+ - ⚽
+ - ⚾
+ - ⛄
+ - ⛅
+ - ⛎
+ - ⛔
+ - ⛪
+ - ⛲
+ - ⛳
+ - ⛵
+ - ⛺
+ - ⛽
+ - ✂
+ - ✅
+ - ✈
+ - ✉
+ - ✊
+ - ✋
+ - ✌
+ - ✏
+ - ✒
+ - ✔
+ - ✖
+ - ✨
+ - ✳
+ - ✴
+ - ❄
+ - ❇
+ - ❌
+ - ❎
+ - ❓
+ - ❔
+ - ❕
+ - ❗
+ - ❤
+ - ➕
+ - ➖
+ - ➗
+ - ➡
+ - ➰
+ - ➿
+ - ⤴
+ - ⤵
+ - ⬅
+ - ⬆
+ - ⬇
+ - ⬛
+ - ⬜
+ - ⭐
+ - ⭕
+ - 0⃣
+ - 〰
+ - 〽
+ - 1⃣
+ - 2⃣
+ - ㊗
+ - ㊙
+ - 3⃣
+ - 4⃣
+ - 5⃣
+ - 6⃣
+ - 7⃣
+ - 8⃣
+ - 9⃣
+ - ©
+ - ®
+ -
+
+
+
+
diff --git a/1/twemoji.amd.js b/1/twemoji.amd.js
new file mode 100644
index 00000000..44895644
--- /dev/null
+++ b/1/twemoji.amd.js
@@ -0,0 +1,569 @@
+define(function () {
+ /*jslint indent: 2, browser: true, bitwise: true, plusplus: true */
+ var twemoji = (function (
+ /*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
+ https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
+ */
+
+ // WARNING: this file is generated automatically via
+ // `node twemoji-generator.js`
+ // please update its `createTwemoji` function
+ // at the bottom of the same file instead.
+
+ ) {
+ 'use strict';
+
+ /*jshint maxparams:4 */
+
+ var
+ // the exported module object
+ twemoji = {
+
+
+ /////////////////////////
+ // properties //
+ /////////////////////////
+
+ // default assets url, by default will be Twitter Inc. CDN
+ base: 'https://twemoji.maxcdn.com/1/',
+
+ // default assets file extensions, by default '.png'
+ ext: '.png',
+
+ // default assets/folder size, by default "72x72"
+ // available via Twitter CDN: 72
+ size: '72x72',
+
+ // default class name, by default 'emoji'
+ className: 'emoji',
+
+ // basic utilities / helpers to convert code points
+ // to JavaScript surrogates and vice versa
+ convert: {
+
+ /**
+ * Given an HEX codepoint, returns UTF16 surrogate pairs.
+ *
+ * @param string generic codepoint, i.e. '1F4A9'
+ * @return string codepoint transformed into utf16 surrogates pair,
+ * i.e. \uD83D\uDCA9
+ *
+ * @example
+ * twemoji.convert.fromCodePoint('1f1e8');
+ * // "\ud83c\udde8"
+ *
+ * '1f1e8-1f1f3'.split('-').map(twemoji.convert.fromCodePoint).join('')
+ * // "\ud83c\udde8\ud83c\uddf3"
+ */
+ fromCodePoint: fromCodePoint,
+
+ /**
+ * Given UTF16 surrogate pairs, returns the equivalent HEX codepoint.
+ *
+ * @param string generic utf16 surrogates pair, i.e. \uD83D\uDCA9
+ * @param string optional separator for double code points, default='-'
+ * @return string utf16 transformed into codepoint, i.e. '1F4A9'
+ *
+ * @example
+ * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3');
+ * // "1f1e8-1f1f3"
+ *
+ * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3', '~');
+ * // "1f1e8~1f1f3"
+ */
+ toCodePoint: toCodePoint
+ },
+
+
+ /////////////////////////
+ // methods //
+ /////////////////////////
+
+ /**
+ * User first: used to remove missing images
+ * preserving the original text intent when
+ * a fallback for network problems is desired.
+ * Automatically added to Image nodes via DOM
+ * It could be recycled for string operations via:
+ * $('img.emoji').on('error', twemoji.onerror)
+ */
+ onerror: function onerror() {
+ if (this.parentNode) {
+ this.parentNode.replaceChild(createText(this.alt), this);
+ }
+ },
+
+ /**
+ * Main method/logic to generate either
tags or HTMLImage nodes.
+ * "emojify" a generic text or DOM Element.
+ *
+ * @overloads
+ *
+ * String replacement for `innerHTML` or server side operations
+ * twemoji.parse(string);
+ * twemoji.parse(string, Function);
+ * twemoji.parse(string, Object);
+ *
+ * HTMLElement tree parsing for safer operations over existing DOM
+ * twemoji.parse(HTMLElement);
+ * twemoji.parse(HTMLElement, Function);
+ * twemoji.parse(HTMLElement, Object);
+ *
+ * @param string|HTMLElement the source to parse and enrich with emoji.
+ *
+ * string replace emoji matches with
tags.
+ * Mainly used to inject emoji via `innerHTML`
+ * It does **not** parse the string or validate it,
+ * it simply replaces found emoji with a tag.
+ * NOTE: be sure this won't affect security.
+ *
+ * HTMLElement walk through the DOM tree and find emoji
+ * that are inside **text node only** (nodeType === 3)
+ * Mainly used to put emoji in already generated DOM
+ * without compromising surrounding nodes and
+ * **avoiding** the usage of `innerHTML`.
+ * NOTE: Using DOM elements instead of strings should
+ * improve security without compromising too much
+ * performance compared with a less safe `innerHTML`.
+ *
+ * @param Function|Object [optional]
+ * either the callback that will be invoked or an object
+ * with all properties to use per each found emoji.
+ *
+ * Function if specified, this will be invoked per each emoji
+ * that has been found through the RegExp except
+ * those follwed by the invariant \uFE0E ("as text").
+ * Once invoked, parameters will be:
+ *
+ * iconId:string the lower case HEX code point
+ * i.e. "1f4a9"
+ *
+ * options:Object all info for this parsing operation
+ *
+ * variant:char the optional \uFE0F ("as image")
+ * variant, in case this info
+ * is anyhow meaningful.
+ * By default this is ignored.
+ *
+ * If such callback will return a falsy value instead
+ * of a valid `src` to use for the image, nothing will
+ * actually change for that specific emoji.
+ *
+ *
+ * Object if specified, an object containing the following properties
+ *
+ * callback Function the callback to invoke per each found emoji.
+ * base string the base url, by default twemoji.base
+ * ext string the image extension, by default twemoji.ext
+ * size string the assets size, by default twemoji.size
+ *
+ * @example
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!");
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", function(iconId, options) {
+ * return '/assets/' + iconId + '.gif';
+ * });
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", {
+ * size: 72,
+ * callback: function(iconId, options) {
+ * return '/assets/' + options.size + '/' + iconId + options.ext;
+ * }
+ * });
+ * // I
emoji!
+ *
+ */
+ parse: parse,
+
+ /**
+ * Given a string, invokes the callback argument
+ * per each emoji found in such string.
+ * This is the most raw version used by
+ * the .parse(string) method itself.
+ *
+ * @param string generic string to parse
+ * @param Function a generic callback that will be
+ * invoked to replace the content.
+ * This calback wil receive standard
+ * String.prototype.replace(str, callback)
+ * arguments such:
+ * callback(
+ * rawText, // the emoji match
+ * );
+ *
+ * and others commonly received via replace.
+ */
+ replace: replace,
+
+ /**
+ * Simplify string tests against emoji.
+ *
+ * @param string some text that might contain emoji
+ * @return boolean true if any emoji was found, false otherwise.
+ *
+ * @example
+ *
+ * if (twemoji.test(someContent)) {
+ * console.log("emoji All The Things!");
+ * }
+ */
+ test: test
+ },
+
+ // used to escape HTML special chars in attributes
+ escaper = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''',
+ '"': '"'
+ },
+
+ // RegExp based on emoji's official Unicode standards
+ // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
+ re = /(?:[\u0039\u0038\u0037\u0036\u0035\u0034\u0033\u0032\u0031\u0030\u0023])\ufe0f?\u20e3|\ud83c\udde8\ud83c\uddf3|\ud83c\udde9\ud83c\uddea|\ud83c\uddea\ud83c\uddf8|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddec\ud83c\udde7|\ud83c\uddee\ud83c\uddf9|\ud83c\uddef\ud83c\uddf5|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddfa\ud83c\uddf8|\ud83d[\udc00-\udc3e\udc40\udc42-\udcf7\udcf9-\udcfc\udd00-\udd3d\udd50-\udd67\uddfb-\ude42\ude45-\ude4f\ude80-\udec5]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf30-\udf35\udf37-\udf7c\udf80-\udf93\udfa0-\udfc4\udfc6-\udfca\udfe0-\udff0]|[\ue50a\u27bf\u27b0\u2797\u2796\u2795\u2755\u2754\u2753\u274e\u274c\u2728\u270b\u270a\u2705\u26ce\u23f3\u23f0\u23ec\u23eb\u23ea\u23e9]|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37]|[\u3299\u3297\u303d\u3030\u2b55\u2b50\u2b1c\u2b1b\u2b07\u2b06\u2b05\u2935\u2934\u27a1\u2764\u2757\u2747\u2744\u2734\u2733\u2716\u2714\u2712\u270f\u270c\u2709\u2708\u2702\u26fd\u26fa\u26f5\u26f3\u26f2\u26ea\u26d4\u26c5\u26c4\u26be\u26bd\u26ab\u26aa\u26a1\u26a0\u2693\u267f\u267b\u2668\u2666\u2665\u2663\u2660\u2653\u2652\u2651\u2650\u264f\u264e\u264d\u264c\u264b\u264a\u2649\u2648\u263a\u261d\u2615\u2614\u2611\u260e\u2601\u2600\u25fe\u25fd\u25fc\u25fb\u25c0\u25b6\u25ab\u25aa\u24c2\u231b\u231a\u21aa\u21a9\u2199\u2198\u2197\u2196\u2195\u2194\u2139\u2122\u2049\u203c\u00ae\u00a9])(?:\ufe0f|(?!\ufe0e))/g,
+
+ // avoid runtime RegExp creation for not so smart,
+ // not JIT based, and old browsers / engines
+ UFE0Fg = /\uFE0F/g,
+
+ // avoid using a string literal like '\u200D' here because minifiers expand it inline
+ U200D = String.fromCharCode(0x200D),
+
+ // used to find HTML special chars in attributes
+ rescaper = /[&<>'"]/g,
+
+ // nodes with type 1 which should **not** be parsed (including lower case svg)
+ shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,
+
+ // just a private shortcut
+ fromCharCode = String.fromCharCode;
+
+ return twemoji;
+
+
+ /////////////////////////
+ // private functions //
+ // declaration //
+ /////////////////////////
+
+ /**
+ * Shortcut to create text nodes
+ * @param string text used to create DOM text node
+ * @return Node a DOM node with that text
+ */
+ function createText(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
+ * based on Twitter CDN
+ * @param string the emoji codepoint string
+ * @param string the default size to use, i.e. "36x36"
+ * @return string the image source to use
+ */
+ function defaultImageSrcGenerator(icon, options) {
+ return ''.concat(options.base, options.size, '/', icon, options.ext);
+ }
+
+ /**
+ * Given a generic DOM nodeType 1, walk through all children
+ * and store every nodeType 3 (#text) found in the tree.
+ * @param Element a DOM Element with probably some text in it
+ * @param Array the list of previously discovered text nodes
+ * @return Array same list with new discovered nodes, if any
+ */
+ function grabAllTextNodes(node, allText) {
+ var
+ childNodes = node.childNodes,
+ length = childNodes.length,
+ subnode,
+ nodeType;
+ while (length--) {
+ subnode = childNodes[length];
+ nodeType = subnode.nodeType;
+ // parse emoji only in text nodes
+ if (nodeType === 3) {
+ // collect them to process emoji later
+ allText.push(subnode);
+ }
+ // ignore all nodes that are not type 1 or that
+ // should not be parsed as script, style, and others
+ else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) {
+ grabAllTextNodes(subnode, allText);
+ }
+ }
+ return allText;
+ }
+
+ /**
+ * Used to both remove the possible variant
+ * and to convert utf16 into code points.
+ * If there is a zero-width-joiner (U+200D), leave the variants in.
+ * @param string the raw text of the emoji match
+ */
+ function grabTheRightIcon(rawText) {
+ // if variant is present as \uFE0F
+ return toCodePoint(rawText.indexOf(U200D) < 0 ?
+ rawText.replace(UFE0Fg, '') :
+ rawText
+ );
+ }
+
+ /**
+ * DOM version of the same logic / parser:
+ * emojify all found sub-text nodes placing images node instead.
+ * @param Element generic DOM node with some text in some child node
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return Element same generic node with emoji in place, if any.
+ */
+ function parseNode(node, options) {
+ var
+ allText = grabAllTextNodes(node, []),
+ length = allText.length,
+ attrib,
+ attrname,
+ modified,
+ fragment,
+ subnode,
+ text,
+ match,
+ i,
+ index,
+ img,
+ rawText,
+ iconId,
+ src;
+ while (length--) {
+ modified = false;
+ fragment = document.createDocumentFragment();
+ subnode = allText[length];
+ text = subnode.nodeValue;
+ i = 0;
+ while ((match = re.exec(text))) {
+ index = match.index;
+ if (index !== i) {
+ fragment.appendChild(
+ createText(text.slice(i, index))
+ );
+ }
+ rawText = match[0];
+ iconId = grabTheRightIcon(rawText);
+ i = index + rawText.length;
+ src = options.callback(iconId, options);
+ if (src) {
+ img = new Image();
+ img.onerror = options.onerror;
+ img.setAttribute('draggable', 'false');
+ attrib = options.attributes(rawText, iconId);
+ 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 = rawText;
+ img.src = src;
+ modified = true;
+ fragment.appendChild(img);
+ }
+ if (!img) fragment.appendChild(createText(rawText));
+ img = null;
+ }
+ // is there actually anything to replace in here ?
+ if (modified) {
+ // any text left to be added ?
+ if (i < text.length) {
+ fragment.appendChild(
+ createText(text.slice(i))
+ );
+ }
+ // replace the text node only, leave intact
+ // anything else surrounding such text
+ subnode.parentNode.replaceChild(fragment, subnode);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * String/HTML version of the same logic / parser:
+ * emojify a generic text placing images tags instead of surrogates pair.
+ * @param string generic string with possibly some emoji in it
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return the string with
replacing all found and parsed emoji
+ */
+ function parseString(str, options) {
+ return replace(str, function (rawText) {
+ var
+ ret = rawText,
+ iconId = grabTheRightIcon(rawText),
+ src = options.callback(iconId, options),
+ attrib,
+ attrname;
+ if (src) {
+ // recycle the match string replacing the emoji
+ // with its image counter part
+ ret = '
');
+ }
+ 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.
+ * As example, number 36 will return '36x36'.
+ * @param any a generic value.
+ * @return any a string representing asset size, i.e. "36x36"
+ * only in case the value was a number.
+ * Returns initial value otherwise.
+ */
+ function toSizeSquaredAsset(value) {
+ return typeof value === 'number' ?
+ value + 'x' + value :
+ value;
+ }
+
+
+ /////////////////////////
+ // exported functions //
+ // declaration //
+ /////////////////////////
+
+ function fromCodePoint(codepoint) {
+ var code = typeof codepoint === 'string' ?
+ parseInt(codepoint, 16) : codepoint;
+ if (code < 0x10000) {
+ return fromCharCode(code);
+ }
+ code -= 0x10000;
+ return fromCharCode(
+ 0xD800 + (code >> 10),
+ 0xDC00 + (code & 0x3FF)
+ );
+ }
+
+ function parse(what, how) {
+ if (!how || typeof how === 'function') {
+ how = {callback: how};
+ }
+ // if first argument is string, inject html
tags
+ // otherwise use the DOM tree and parse text nodes only
+ return (typeof what === 'string' ? parseString : parseNode)(what, {
+ callback: how.callback || defaultImageSrcGenerator,
+ attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
+ base: typeof how.base === 'string' ? how.base : twemoji.base,
+ ext: how.ext || twemoji.ext,
+ size: how.folder || toSizeSquaredAsset(how.size || twemoji.size),
+ className: how.className || twemoji.className,
+ onerror: how.onerror || twemoji.onerror
+ });
+ }
+
+ function replace(text, callback) {
+ return String(text).replace(re, callback);
+ }
+
+ function test(text) {
+ // IE6 needs a reset before too
+ re.lastIndex = 0;
+ var result = re.test(text);
+ re.lastIndex = 0;
+ return result;
+ }
+
+ function toCodePoint(unicodeSurrogates, sep) {
+ var
+ r = [],
+ c = 0,
+ p = 0,
+ i = 0;
+ while (i < unicodeSurrogates.length) {
+ c = unicodeSurrogates.charCodeAt(i++);
+ if (p) {
+ r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
+ p = 0;
+ } else if (0xD800 <= c && c <= 0xDBFF) {
+ p = c;
+ } else {
+ r.push(c.toString(16));
+ }
+ }
+ return r.join(sep || '-');
+ }
+
+ }());
+ return twemoji;
+});
\ No newline at end of file
diff --git a/1/twemoji.js b/1/twemoji.js
new file mode 100644
index 00000000..1a5518a6
--- /dev/null
+++ b/1/twemoji.js
@@ -0,0 +1,566 @@
+/*jslint indent: 2, browser: true, bitwise: true, plusplus: true */
+var twemoji = (function (
+ /*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
+ https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
+ */
+
+ // WARNING: this file is generated automatically via
+ // `node twemoji-generator.js`
+ // please update its `createTwemoji` function
+ // at the bottom of the same file instead.
+
+) {
+ 'use strict';
+
+ /*jshint maxparams:4 */
+
+ var
+ // the exported module object
+ twemoji = {
+
+
+ /////////////////////////
+ // properties //
+ /////////////////////////
+
+ // default assets url, by default will be Twitter Inc. CDN
+ base: 'https://twemoji.maxcdn.com/1/',
+
+ // default assets file extensions, by default '.png'
+ ext: '.png',
+
+ // default assets/folder size, by default "72x72"
+ // available via Twitter CDN: 72
+ size: '72x72',
+
+ // default class name, by default 'emoji'
+ className: 'emoji',
+
+ // basic utilities / helpers to convert code points
+ // to JavaScript surrogates and vice versa
+ convert: {
+
+ /**
+ * Given an HEX codepoint, returns UTF16 surrogate pairs.
+ *
+ * @param string generic codepoint, i.e. '1F4A9'
+ * @return string codepoint transformed into utf16 surrogates pair,
+ * i.e. \uD83D\uDCA9
+ *
+ * @example
+ * twemoji.convert.fromCodePoint('1f1e8');
+ * // "\ud83c\udde8"
+ *
+ * '1f1e8-1f1f3'.split('-').map(twemoji.convert.fromCodePoint).join('')
+ * // "\ud83c\udde8\ud83c\uddf3"
+ */
+ fromCodePoint: fromCodePoint,
+
+ /**
+ * Given UTF16 surrogate pairs, returns the equivalent HEX codepoint.
+ *
+ * @param string generic utf16 surrogates pair, i.e. \uD83D\uDCA9
+ * @param string optional separator for double code points, default='-'
+ * @return string utf16 transformed into codepoint, i.e. '1F4A9'
+ *
+ * @example
+ * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3');
+ * // "1f1e8-1f1f3"
+ *
+ * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3', '~');
+ * // "1f1e8~1f1f3"
+ */
+ toCodePoint: toCodePoint
+ },
+
+
+ /////////////////////////
+ // methods //
+ /////////////////////////
+
+ /**
+ * User first: used to remove missing images
+ * preserving the original text intent when
+ * a fallback for network problems is desired.
+ * Automatically added to Image nodes via DOM
+ * It could be recycled for string operations via:
+ * $('img.emoji').on('error', twemoji.onerror)
+ */
+ onerror: function onerror() {
+ if (this.parentNode) {
+ this.parentNode.replaceChild(createText(this.alt), this);
+ }
+ },
+
+ /**
+ * Main method/logic to generate either
tags or HTMLImage nodes.
+ * "emojify" a generic text or DOM Element.
+ *
+ * @overloads
+ *
+ * String replacement for `innerHTML` or server side operations
+ * twemoji.parse(string);
+ * twemoji.parse(string, Function);
+ * twemoji.parse(string, Object);
+ *
+ * HTMLElement tree parsing for safer operations over existing DOM
+ * twemoji.parse(HTMLElement);
+ * twemoji.parse(HTMLElement, Function);
+ * twemoji.parse(HTMLElement, Object);
+ *
+ * @param string|HTMLElement the source to parse and enrich with emoji.
+ *
+ * string replace emoji matches with
tags.
+ * Mainly used to inject emoji via `innerHTML`
+ * It does **not** parse the string or validate it,
+ * it simply replaces found emoji with a tag.
+ * NOTE: be sure this won't affect security.
+ *
+ * HTMLElement walk through the DOM tree and find emoji
+ * that are inside **text node only** (nodeType === 3)
+ * Mainly used to put emoji in already generated DOM
+ * without compromising surrounding nodes and
+ * **avoiding** the usage of `innerHTML`.
+ * NOTE: Using DOM elements instead of strings should
+ * improve security without compromising too much
+ * performance compared with a less safe `innerHTML`.
+ *
+ * @param Function|Object [optional]
+ * either the callback that will be invoked or an object
+ * with all properties to use per each found emoji.
+ *
+ * Function if specified, this will be invoked per each emoji
+ * that has been found through the RegExp except
+ * those follwed by the invariant \uFE0E ("as text").
+ * Once invoked, parameters will be:
+ *
+ * iconId:string the lower case HEX code point
+ * i.e. "1f4a9"
+ *
+ * options:Object all info for this parsing operation
+ *
+ * variant:char the optional \uFE0F ("as image")
+ * variant, in case this info
+ * is anyhow meaningful.
+ * By default this is ignored.
+ *
+ * If such callback will return a falsy value instead
+ * of a valid `src` to use for the image, nothing will
+ * actually change for that specific emoji.
+ *
+ *
+ * Object if specified, an object containing the following properties
+ *
+ * callback Function the callback to invoke per each found emoji.
+ * base string the base url, by default twemoji.base
+ * ext string the image extension, by default twemoji.ext
+ * size string the assets size, by default twemoji.size
+ *
+ * @example
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!");
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", function(iconId, options) {
+ * return '/assets/' + iconId + '.gif';
+ * });
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", {
+ * size: 72,
+ * callback: function(iconId, options) {
+ * return '/assets/' + options.size + '/' + iconId + options.ext;
+ * }
+ * });
+ * // I
emoji!
+ *
+ */
+ parse: parse,
+
+ /**
+ * Given a string, invokes the callback argument
+ * per each emoji found in such string.
+ * This is the most raw version used by
+ * the .parse(string) method itself.
+ *
+ * @param string generic string to parse
+ * @param Function a generic callback that will be
+ * invoked to replace the content.
+ * This calback wil receive standard
+ * String.prototype.replace(str, callback)
+ * arguments such:
+ * callback(
+ * rawText, // the emoji match
+ * );
+ *
+ * and others commonly received via replace.
+ */
+ replace: replace,
+
+ /**
+ * Simplify string tests against emoji.
+ *
+ * @param string some text that might contain emoji
+ * @return boolean true if any emoji was found, false otherwise.
+ *
+ * @example
+ *
+ * if (twemoji.test(someContent)) {
+ * console.log("emoji All The Things!");
+ * }
+ */
+ test: test
+ },
+
+ // used to escape HTML special chars in attributes
+ escaper = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''',
+ '"': '"'
+ },
+
+ // RegExp based on emoji's official Unicode standards
+ // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
+ re = /(?:[\u0039\u0038\u0037\u0036\u0035\u0034\u0033\u0032\u0031\u0030\u0023])\ufe0f?\u20e3|\ud83c\udde8\ud83c\uddf3|\ud83c\udde9\ud83c\uddea|\ud83c\uddea\ud83c\uddf8|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddec\ud83c\udde7|\ud83c\uddee\ud83c\uddf9|\ud83c\uddef\ud83c\uddf5|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddfa\ud83c\uddf8|\ud83d[\udc00-\udc3e\udc40\udc42-\udcf7\udcf9-\udcfc\udd00-\udd3d\udd50-\udd67\uddfb-\ude42\ude45-\ude4f\ude80-\udec5]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf30-\udf35\udf37-\udf7c\udf80-\udf93\udfa0-\udfc4\udfc6-\udfca\udfe0-\udff0]|[\ue50a\u27bf\u27b0\u2797\u2796\u2795\u2755\u2754\u2753\u274e\u274c\u2728\u270b\u270a\u2705\u26ce\u23f3\u23f0\u23ec\u23eb\u23ea\u23e9]|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37]|[\u3299\u3297\u303d\u3030\u2b55\u2b50\u2b1c\u2b1b\u2b07\u2b06\u2b05\u2935\u2934\u27a1\u2764\u2757\u2747\u2744\u2734\u2733\u2716\u2714\u2712\u270f\u270c\u2709\u2708\u2702\u26fd\u26fa\u26f5\u26f3\u26f2\u26ea\u26d4\u26c5\u26c4\u26be\u26bd\u26ab\u26aa\u26a1\u26a0\u2693\u267f\u267b\u2668\u2666\u2665\u2663\u2660\u2653\u2652\u2651\u2650\u264f\u264e\u264d\u264c\u264b\u264a\u2649\u2648\u263a\u261d\u2615\u2614\u2611\u260e\u2601\u2600\u25fe\u25fd\u25fc\u25fb\u25c0\u25b6\u25ab\u25aa\u24c2\u231b\u231a\u21aa\u21a9\u2199\u2198\u2197\u2196\u2195\u2194\u2139\u2122\u2049\u203c\u00ae\u00a9])(?:\ufe0f|(?!\ufe0e))/g,
+
+ // avoid runtime RegExp creation for not so smart,
+ // not JIT based, and old browsers / engines
+ UFE0Fg = /\uFE0F/g,
+
+ // avoid using a string literal like '\u200D' here because minifiers expand it inline
+ U200D = String.fromCharCode(0x200D),
+
+ // used to find HTML special chars in attributes
+ rescaper = /[&<>'"]/g,
+
+ // nodes with type 1 which should **not** be parsed (including lower case svg)
+ shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,
+
+ // just a private shortcut
+ fromCharCode = String.fromCharCode;
+
+ return twemoji;
+
+
+ /////////////////////////
+ // private functions //
+ // declaration //
+ /////////////////////////
+
+ /**
+ * Shortcut to create text nodes
+ * @param string text used to create DOM text node
+ * @return Node a DOM node with that text
+ */
+ function createText(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
+ * based on Twitter CDN
+ * @param string the emoji codepoint string
+ * @param string the default size to use, i.e. "36x36"
+ * @return string the image source to use
+ */
+ function defaultImageSrcGenerator(icon, options) {
+ return ''.concat(options.base, options.size, '/', icon, options.ext);
+ }
+
+ /**
+ * Given a generic DOM nodeType 1, walk through all children
+ * and store every nodeType 3 (#text) found in the tree.
+ * @param Element a DOM Element with probably some text in it
+ * @param Array the list of previously discovered text nodes
+ * @return Array same list with new discovered nodes, if any
+ */
+ function grabAllTextNodes(node, allText) {
+ var
+ childNodes = node.childNodes,
+ length = childNodes.length,
+ subnode,
+ nodeType;
+ while (length--) {
+ subnode = childNodes[length];
+ nodeType = subnode.nodeType;
+ // parse emoji only in text nodes
+ if (nodeType === 3) {
+ // collect them to process emoji later
+ allText.push(subnode);
+ }
+ // ignore all nodes that are not type 1 or that
+ // should not be parsed as script, style, and others
+ else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) {
+ grabAllTextNodes(subnode, allText);
+ }
+ }
+ return allText;
+ }
+
+ /**
+ * Used to both remove the possible variant
+ * and to convert utf16 into code points.
+ * If there is a zero-width-joiner (U+200D), leave the variants in.
+ * @param string the raw text of the emoji match
+ */
+ function grabTheRightIcon(rawText) {
+ // if variant is present as \uFE0F
+ return toCodePoint(rawText.indexOf(U200D) < 0 ?
+ rawText.replace(UFE0Fg, '') :
+ rawText
+ );
+ }
+
+ /**
+ * DOM version of the same logic / parser:
+ * emojify all found sub-text nodes placing images node instead.
+ * @param Element generic DOM node with some text in some child node
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return Element same generic node with emoji in place, if any.
+ */
+ function parseNode(node, options) {
+ var
+ allText = grabAllTextNodes(node, []),
+ length = allText.length,
+ attrib,
+ attrname,
+ modified,
+ fragment,
+ subnode,
+ text,
+ match,
+ i,
+ index,
+ img,
+ rawText,
+ iconId,
+ src;
+ while (length--) {
+ modified = false;
+ fragment = document.createDocumentFragment();
+ subnode = allText[length];
+ text = subnode.nodeValue;
+ i = 0;
+ while ((match = re.exec(text))) {
+ index = match.index;
+ if (index !== i) {
+ fragment.appendChild(
+ createText(text.slice(i, index))
+ );
+ }
+ rawText = match[0];
+ iconId = grabTheRightIcon(rawText);
+ i = index + rawText.length;
+ src = options.callback(iconId, options);
+ if (src) {
+ img = new Image();
+ img.onerror = options.onerror;
+ img.setAttribute('draggable', 'false');
+ attrib = options.attributes(rawText, iconId);
+ 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 = rawText;
+ img.src = src;
+ modified = true;
+ fragment.appendChild(img);
+ }
+ if (!img) fragment.appendChild(createText(rawText));
+ img = null;
+ }
+ // is there actually anything to replace in here ?
+ if (modified) {
+ // any text left to be added ?
+ if (i < text.length) {
+ fragment.appendChild(
+ createText(text.slice(i))
+ );
+ }
+ // replace the text node only, leave intact
+ // anything else surrounding such text
+ subnode.parentNode.replaceChild(fragment, subnode);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * String/HTML version of the same logic / parser:
+ * emojify a generic text placing images tags instead of surrogates pair.
+ * @param string generic string with possibly some emoji in it
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return the string with
replacing all found and parsed emoji
+ */
+ function parseString(str, options) {
+ return replace(str, function (rawText) {
+ var
+ ret = rawText,
+ iconId = grabTheRightIcon(rawText),
+ src = options.callback(iconId, options),
+ attrib,
+ attrname;
+ if (src) {
+ // recycle the match string replacing the emoji
+ // with its image counter part
+ ret = '
');
+ }
+ 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.
+ * As example, number 36 will return '36x36'.
+ * @param any a generic value.
+ * @return any a string representing asset size, i.e. "36x36"
+ * only in case the value was a number.
+ * Returns initial value otherwise.
+ */
+ function toSizeSquaredAsset(value) {
+ return typeof value === 'number' ?
+ value + 'x' + value :
+ value;
+ }
+
+
+ /////////////////////////
+ // exported functions //
+ // declaration //
+ /////////////////////////
+
+ function fromCodePoint(codepoint) {
+ var code = typeof codepoint === 'string' ?
+ parseInt(codepoint, 16) : codepoint;
+ if (code < 0x10000) {
+ return fromCharCode(code);
+ }
+ code -= 0x10000;
+ return fromCharCode(
+ 0xD800 + (code >> 10),
+ 0xDC00 + (code & 0x3FF)
+ );
+ }
+
+ function parse(what, how) {
+ if (!how || typeof how === 'function') {
+ how = {callback: how};
+ }
+ // if first argument is string, inject html
tags
+ // otherwise use the DOM tree and parse text nodes only
+ return (typeof what === 'string' ? parseString : parseNode)(what, {
+ callback: how.callback || defaultImageSrcGenerator,
+ attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
+ base: typeof how.base === 'string' ? how.base : twemoji.base,
+ ext: how.ext || twemoji.ext,
+ size: how.folder || toSizeSquaredAsset(how.size || twemoji.size),
+ className: how.className || twemoji.className,
+ onerror: how.onerror || twemoji.onerror
+ });
+ }
+
+ function replace(text, callback) {
+ return String(text).replace(re, callback);
+ }
+
+ function test(text) {
+ // IE6 needs a reset before too
+ re.lastIndex = 0;
+ var result = re.test(text);
+ re.lastIndex = 0;
+ return result;
+ }
+
+ function toCodePoint(unicodeSurrogates, sep) {
+ var
+ r = [],
+ c = 0,
+ p = 0,
+ i = 0;
+ while (i < unicodeSurrogates.length) {
+ c = unicodeSurrogates.charCodeAt(i++);
+ if (p) {
+ r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
+ p = 0;
+ } else if (0xD800 <= c && c <= 0xDBFF) {
+ p = c;
+ } else {
+ r.push(c.toString(16));
+ }
+ }
+ return r.join(sep || '-');
+ }
+
+}());
\ No newline at end of file
diff --git a/1/twemoji.min.js b/1/twemoji.min.js
new file mode 100644
index 00000000..8e06bc85
--- /dev/null
+++ b/1/twemoji.min.js
@@ -0,0 +1,2 @@
+/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */
+var twemoji=function(){"use strict";var twemoji={base:"https://twemoji.maxcdn.com/1/",ext:".png",size:"72x72",className:"emoji",convert:{fromCodePoint:fromCodePoint,toCodePoint:toCodePoint},onerror:function onerror(){if(this.parentNode){this.parentNode.replaceChild(createText(this.alt),this)}},parse:parse,replace:replace,test:test},escaper={"&":"&","<":"<",">":">","'":"'",'"':"""},re=/(?:[\u0039\u0038\u0037\u0036\u0035\u0034\u0033\u0032\u0031\u0030\u0023])\ufe0f?\u20e3|\ud83c\udde8\ud83c\uddf3|\ud83c\udde9\ud83c\uddea|\ud83c\uddea\ud83c\uddf8|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddec\ud83c\udde7|\ud83c\uddee\ud83c\uddf9|\ud83c\uddef\ud83c\uddf5|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddfa\ud83c\uddf8|\ud83d[\udc00-\udc3e\udc40\udc42-\udcf7\udcf9-\udcfc\udd00-\udd3d\udd50-\udd67\uddfb-\ude42\ude45-\ude4f\ude80-\udec5]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf30-\udf35\udf37-\udf7c\udf80-\udf93\udfa0-\udfc4\udfc6-\udfca\udfe0-\udff0]|[\ue50a\u27bf\u27b0\u2797\u2796\u2795\u2755\u2754\u2753\u274e\u274c\u2728\u270b\u270a\u2705\u26ce\u23f3\u23f0\u23ec\u23eb\u23ea\u23e9]|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37]|[\u3299\u3297\u303d\u3030\u2b55\u2b50\u2b1c\u2b1b\u2b07\u2b06\u2b05\u2935\u2934\u27a1\u2764\u2757\u2747\u2744\u2734\u2733\u2716\u2714\u2712\u270f\u270c\u2709\u2708\u2702\u26fd\u26fa\u26f5\u26f3\u26f2\u26ea\u26d4\u26c5\u26c4\u26be\u26bd\u26ab\u26aa\u26a1\u26a0\u2693\u267f\u267b\u2668\u2666\u2665\u2663\u2660\u2653\u2652\u2651\u2650\u264f\u264e\u264d\u264c\u264b\u264a\u2649\u2648\u263a\u261d\u2615\u2614\u2611\u260e\u2601\u2600\u25fe\u25fd\u25fc\u25fb\u25c0\u25b6\u25ab\u25aa\u24c2\u231b\u231a\u21aa\u21a9\u2199\u2198\u2197\u2196\u2195\u2194\u2139\u2122\u2049\u203c\u00ae\u00a9])(?:\ufe0f|(?!\ufe0e))/g,UFE0Fg=/\uFE0F/g,U200D=String.fromCharCode(8205),rescaper=/[&<>'"]/g,shouldntBeParsed=/IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,fromCharCode=String.fromCharCode;return twemoji;function createText(text){return document.createTextNode(text)}function escapeHTML(s){return s.replace(rescaper,replacer)}function defaultImageSrcGenerator(icon,options){return"".concat(options.base,options.size,"/",icon,options.ext)}function grabAllTextNodes(node,allText){var childNodes=node.childNodes,length=childNodes.length,subnode,nodeType;while(length--){subnode=childNodes[length];nodeType=subnode.nodeType;if(nodeType===3){allText.push(subnode)}else if(nodeType===1&&!shouldntBeParsed.test(subnode.nodeName)){grabAllTextNodes(subnode,allText)}}return allText}function grabTheRightIcon(rawText){return toCodePoint(rawText.indexOf(U200D)<0?rawText.replace(UFE0Fg,""):rawText)}function parseNode(node,options){var allText=grabAllTextNodes(node,[]),length=allText.length,attrib,attrname,modified,fragment,subnode,text,match,i,index,img,rawText,iconId,src;while(length--){modified=false;fragment=document.createDocumentFragment();subnode=allText[length];text=subnode.nodeValue;i=0;while(match=re.exec(text)){index=match.index;if(index!==i){fragment.appendChild(createText(text.slice(i,index)))}rawText=match[0];iconId=grabTheRightIcon(rawText);i=index+rawText.length;src=options.callback(iconId,options);if(src){img=new Image;img.onerror=options.onerror;img.setAttribute("draggable","false");attrib=options.attributes(rawText,iconId);for(attrname in attrib){if(attrib.hasOwnProperty(attrname)&&attrname.indexOf("on")!==0&&!img.hasAttribute(attrname)){img.setAttribute(attrname,attrib[attrname])}}img.className=options.className;img.alt=rawText;img.src=src;modified=true;fragment.appendChild(img)}if(!img)fragment.appendChild(createText(rawText));img=null}if(modified){if(i")}return ret})}function replacer(m){return escaper[m]}function returnNull(){return null}function toSizeSquaredAsset(value){return typeof value==="number"?value+"x"+value:value}function fromCodePoint(codepoint){var code=typeof codepoint==="string"?parseInt(codepoint,16):codepoint;if(code<65536){return fromCharCode(code)}code-=65536;return fromCharCode(55296+(code>>10),56320+(code&1023))}function parse(what,how){if(!how||typeof how==="function"){how={callback:how}}return(typeof what==="string"?parseString:parseNode)(what,{callback:how.callback||defaultImageSrcGenerator,attributes:typeof how.attributes==="function"?how.attributes:returnNull,base:typeof how.base==="string"?how.base:twemoji.base,ext:how.ext||twemoji.ext,size:how.folder||toSizeSquaredAsset(how.size||twemoji.size),className:how.className||twemoji.className,onerror:how.onerror||twemoji.onerror})}function replace(text,callback){return String(text).replace(re,callback)}function test(text){re.lastIndex=0;var result=re.test(text);re.lastIndex=0;return result}function toCodePoint(unicodeSurrogates,sep){var r=[],c=0,p=0,i=0;while(i tags or HTMLImage nodes.
+ * "emojify" a generic text or DOM Element.
+ *
+ * @overloads
+ *
+ * String replacement for `innerHTML` or server side operations
+ * twemoji.parse(string);
+ * twemoji.parse(string, Function);
+ * twemoji.parse(string, Object);
+ *
+ * HTMLElement tree parsing for safer operations over existing DOM
+ * twemoji.parse(HTMLElement);
+ * twemoji.parse(HTMLElement, Function);
+ * twemoji.parse(HTMLElement, Object);
+ *
+ * @param string|HTMLElement the source to parse and enrich with emoji.
+ *
+ * string replace emoji matches with
tags.
+ * Mainly used to inject emoji via `innerHTML`
+ * It does **not** parse the string or validate it,
+ * it simply replaces found emoji with a tag.
+ * NOTE: be sure this won't affect security.
+ *
+ * HTMLElement walk through the DOM tree and find emoji
+ * that are inside **text node only** (nodeType === 3)
+ * Mainly used to put emoji in already generated DOM
+ * without compromising surrounding nodes and
+ * **avoiding** the usage of `innerHTML`.
+ * NOTE: Using DOM elements instead of strings should
+ * improve security without compromising too much
+ * performance compared with a less safe `innerHTML`.
+ *
+ * @param Function|Object [optional]
+ * either the callback that will be invoked or an object
+ * with all properties to use per each found emoji.
+ *
+ * Function if specified, this will be invoked per each emoji
+ * that has been found through the RegExp except
+ * those follwed by the invariant \uFE0E ("as text").
+ * Once invoked, parameters will be:
+ *
+ * iconId:string the lower case HEX code point
+ * i.e. "1f4a9"
+ *
+ * options:Object all info for this parsing operation
+ *
+ * variant:char the optional \uFE0F ("as image")
+ * variant, in case this info
+ * is anyhow meaningful.
+ * By default this is ignored.
+ *
+ * If such callback will return a falsy value instead
+ * of a valid `src` to use for the image, nothing will
+ * actually change for that specific emoji.
+ *
+ *
+ * Object if specified, an object containing the following properties
+ *
+ * callback Function the callback to invoke per each found emoji.
+ * base string the base url, by default twemoji.base
+ * ext string the image extension, by default twemoji.ext
+ * size string the assets size, by default twemoji.size
+ *
+ * @example
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!");
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", function(iconId, options) {
+ * return '/assets/' + iconId + '.gif';
+ * });
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", {
+ * size: 72,
+ * callback: function(iconId, options) {
+ * return '/assets/' + options.size + '/' + iconId + options.ext;
+ * }
+ * });
+ * // I
emoji!
+ *
+ */
+ parse: parse,
+
+ /**
+ * Given a string, invokes the callback argument
+ * per each emoji found in such string.
+ * This is the most raw version used by
+ * the .parse(string) method itself.
+ *
+ * @param string generic string to parse
+ * @param Function a generic callback that will be
+ * invoked to replace the content.
+ * This calback wil receive standard
+ * String.prototype.replace(str, callback)
+ * arguments such:
+ * callback(
+ * rawText, // the emoji match
+ * );
+ *
+ * and others commonly received via replace.
+ */
+ replace: replace,
+
+ /**
+ * Simplify string tests against emoji.
+ *
+ * @param string some text that might contain emoji
+ * @return boolean true if any emoji was found, false otherwise.
+ *
+ * @example
+ *
+ * if (twemoji.test(someContent)) {
+ * console.log("emoji All The Things!");
+ * }
+ */
+ test: test
+ },
+
+ // used to escape HTML special chars in attributes
+ escaper = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''',
+ '"': '"'
+ },
+
+ // RegExp based on emoji's official Unicode standards
+ // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
+ re = /(?:[\u0039\u0038\u0037\u0036\u0035\u0034\u0033\u0032\u0031\u0030\u0023])\ufe0f?\u20e3|\ud83c\udde8\ud83c\uddf3|\ud83c\udde9\ud83c\uddea|\ud83c\uddea\ud83c\uddf8|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddec\ud83c\udde7|\ud83c\uddee\ud83c\uddf9|\ud83c\uddef\ud83c\uddf5|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddfa\ud83c\uddf8|\ud83d[\udc00-\udc3e\udc40\udc42-\udcf7\udcf9-\udcfc\udd00-\udd3d\udd50-\udd67\uddfb-\ude42\ude45-\ude4f\ude80-\udec5]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf30-\udf35\udf37-\udf7c\udf80-\udf93\udfa0-\udfc4\udfc6-\udfca\udfe0-\udff0]|[\ue50a\u27bf\u27b0\u2797\u2796\u2795\u2755\u2754\u2753\u274e\u274c\u2728\u270b\u270a\u2705\u26ce\u23f3\u23f0\u23ec\u23eb\u23ea\u23e9]|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37]|[\u3299\u3297\u303d\u3030\u2b55\u2b50\u2b1c\u2b1b\u2b07\u2b06\u2b05\u2935\u2934\u27a1\u2764\u2757\u2747\u2744\u2734\u2733\u2716\u2714\u2712\u270f\u270c\u2709\u2708\u2702\u26fd\u26fa\u26f5\u26f3\u26f2\u26ea\u26d4\u26c5\u26c4\u26be\u26bd\u26ab\u26aa\u26a1\u26a0\u2693\u267f\u267b\u2668\u2666\u2665\u2663\u2660\u2653\u2652\u2651\u2650\u264f\u264e\u264d\u264c\u264b\u264a\u2649\u2648\u263a\u261d\u2615\u2614\u2611\u260e\u2601\u2600\u25fe\u25fd\u25fc\u25fb\u25c0\u25b6\u25ab\u25aa\u24c2\u231b\u231a\u21aa\u21a9\u2199\u2198\u2197\u2196\u2195\u2194\u2139\u2122\u2049\u203c\u00ae\u00a9])(?:\ufe0f|(?!\ufe0e))/g,
+
+ // avoid runtime RegExp creation for not so smart,
+ // not JIT based, and old browsers / engines
+ UFE0Fg = /\uFE0F/g,
+
+ // avoid using a string literal like '\u200D' here because minifiers expand it inline
+ U200D = String.fromCharCode(0x200D),
+
+ // used to find HTML special chars in attributes
+ rescaper = /[&<>'"]/g,
+
+ // nodes with type 1 which should **not** be parsed (including lower case svg)
+ shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,
+
+ // just a private shortcut
+ fromCharCode = String.fromCharCode;
+
+ return twemoji;
+
+
+ /////////////////////////
+ // private functions //
+ // declaration //
+ /////////////////////////
+
+ /**
+ * Shortcut to create text nodes
+ * @param string text used to create DOM text node
+ * @return Node a DOM node with that text
+ */
+ function createText(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
+ * based on Twitter CDN
+ * @param string the emoji codepoint string
+ * @param string the default size to use, i.e. "36x36"
+ * @return string the image source to use
+ */
+ function defaultImageSrcGenerator(icon, options) {
+ return ''.concat(options.base, options.size, '/', icon, options.ext);
+ }
+
+ /**
+ * Given a generic DOM nodeType 1, walk through all children
+ * and store every nodeType 3 (#text) found in the tree.
+ * @param Element a DOM Element with probably some text in it
+ * @param Array the list of previously discovered text nodes
+ * @return Array same list with new discovered nodes, if any
+ */
+ function grabAllTextNodes(node, allText) {
+ var
+ childNodes = node.childNodes,
+ length = childNodes.length,
+ subnode,
+ nodeType;
+ while (length--) {
+ subnode = childNodes[length];
+ nodeType = subnode.nodeType;
+ // parse emoji only in text nodes
+ if (nodeType === 3) {
+ // collect them to process emoji later
+ allText.push(subnode);
+ }
+ // ignore all nodes that are not type 1 or that
+ // should not be parsed as script, style, and others
+ else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) {
+ grabAllTextNodes(subnode, allText);
+ }
+ }
+ return allText;
+ }
+
+ /**
+ * Used to both remove the possible variant
+ * and to convert utf16 into code points.
+ * If there is a zero-width-joiner (U+200D), leave the variants in.
+ * @param string the raw text of the emoji match
+ */
+ function grabTheRightIcon(rawText) {
+ // if variant is present as \uFE0F
+ return toCodePoint(rawText.indexOf(U200D) < 0 ?
+ rawText.replace(UFE0Fg, '') :
+ rawText
+ );
+ }
+
+ /**
+ * DOM version of the same logic / parser:
+ * emojify all found sub-text nodes placing images node instead.
+ * @param Element generic DOM node with some text in some child node
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return Element same generic node with emoji in place, if any.
+ */
+ function parseNode(node, options) {
+ var
+ allText = grabAllTextNodes(node, []),
+ length = allText.length,
+ attrib,
+ attrname,
+ modified,
+ fragment,
+ subnode,
+ text,
+ match,
+ i,
+ index,
+ img,
+ rawText,
+ iconId,
+ src;
+ while (length--) {
+ modified = false;
+ fragment = document.createDocumentFragment();
+ subnode = allText[length];
+ text = subnode.nodeValue;
+ i = 0;
+ while ((match = re.exec(text))) {
+ index = match.index;
+ if (index !== i) {
+ fragment.appendChild(
+ createText(text.slice(i, index))
+ );
+ }
+ rawText = match[0];
+ iconId = grabTheRightIcon(rawText);
+ i = index + rawText.length;
+ src = options.callback(iconId, options);
+ if (src) {
+ img = new Image();
+ img.onerror = options.onerror;
+ img.setAttribute('draggable', 'false');
+ attrib = options.attributes(rawText, iconId);
+ 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 = rawText;
+ img.src = src;
+ modified = true;
+ fragment.appendChild(img);
+ }
+ if (!img) fragment.appendChild(createText(rawText));
+ img = null;
+ }
+ // is there actually anything to replace in here ?
+ if (modified) {
+ // any text left to be added ?
+ if (i < text.length) {
+ fragment.appendChild(
+ createText(text.slice(i))
+ );
+ }
+ // replace the text node only, leave intact
+ // anything else surrounding such text
+ subnode.parentNode.replaceChild(fragment, subnode);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * String/HTML version of the same logic / parser:
+ * emojify a generic text placing images tags instead of surrogates pair.
+ * @param string generic string with possibly some emoji in it
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return the string with
replacing all found and parsed emoji
+ */
+ function parseString(str, options) {
+ return replace(str, function (rawText) {
+ var
+ ret = rawText,
+ iconId = grabTheRightIcon(rawText),
+ src = options.callback(iconId, options),
+ attrib,
+ attrname;
+ if (src) {
+ // recycle the match string replacing the emoji
+ // with its image counter part
+ ret = '
');
+ }
+ 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.
+ * As example, number 36 will return '36x36'.
+ * @param any a generic value.
+ * @return any a string representing asset size, i.e. "36x36"
+ * only in case the value was a number.
+ * Returns initial value otherwise.
+ */
+ function toSizeSquaredAsset(value) {
+ return typeof value === 'number' ?
+ value + 'x' + value :
+ value;
+ }
+
+
+ /////////////////////////
+ // exported functions //
+ // declaration //
+ /////////////////////////
+
+ function fromCodePoint(codepoint) {
+ var code = typeof codepoint === 'string' ?
+ parseInt(codepoint, 16) : codepoint;
+ if (code < 0x10000) {
+ return fromCharCode(code);
+ }
+ code -= 0x10000;
+ return fromCharCode(
+ 0xD800 + (code >> 10),
+ 0xDC00 + (code & 0x3FF)
+ );
+ }
+
+ function parse(what, how) {
+ if (!how || typeof how === 'function') {
+ how = {callback: how};
+ }
+ // if first argument is string, inject html
tags
+ // otherwise use the DOM tree and parse text nodes only
+ return (typeof what === 'string' ? parseString : parseNode)(what, {
+ callback: how.callback || defaultImageSrcGenerator,
+ attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
+ base: typeof how.base === 'string' ? how.base : twemoji.base,
+ ext: how.ext || twemoji.ext,
+ size: how.folder || toSizeSquaredAsset(how.size || twemoji.size),
+ className: how.className || twemoji.className,
+ onerror: how.onerror || twemoji.onerror
+ });
+ }
+
+ function replace(text, callback) {
+ return String(text).replace(re, callback);
+ }
+
+ function test(text) {
+ // IE6 needs a reset before too
+ re.lastIndex = 0;
+ var result = re.test(text);
+ re.lastIndex = 0;
+ return result;
+ }
+
+ function toCodePoint(unicodeSurrogates, sep) {
+ var
+ r = [],
+ c = 0,
+ p = 0,
+ i = 0;
+ while (i < unicodeSurrogates.length) {
+ c = unicodeSurrogates.charCodeAt(i++);
+ if (p) {
+ r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
+ p = 0;
+ } else if (0xD800 <= c && c <= 0xDBFF) {
+ p = c;
+ } else {
+ r.push(c.toString(16));
+ }
+ }
+ return r.join(sep || '-');
+ }
+
+}());
+if (!location.protocol) {
+ twemoji.base = twemoji.base.replace(/^http:/, "");
+}
+module.exports = twemoji;
\ No newline at end of file
diff --git a/1/utils/create-dist b/1/utils/create-dist
new file mode 100755
index 00000000..49c5b496
--- /dev/null
+++ b/1/utils/create-dist
@@ -0,0 +1,52 @@
+#!/usr/bin/env node
+
+/*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
+ https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
+*/
+
+var fs = require('fs');
+var path = require('path');
+
+function file(which) {
+ return path.join(__dirname, '..', which);
+}
+
+fs.writeFileSync(
+ file('twemoji.npm.js'),
+ [
+ 'var location = global.location || {};',
+ fs.readFileSync(file('twemoji.js')),
+ 'if (!location.protocol) {',
+ ' twemoji.base = twemoji.base.replace(/^http:/, "");',
+ '}',
+ 'module.exports = twemoji;'
+ ].join('\n')
+);
+
+fs.writeFileSync(
+ file('twemoji.amd.js'),
+ 'define(function () {\n' +
+ fs.readFileSync(file('twemoji.js')).toString().replace(
+ /^(.)/gm, ' $1'
+ ) +
+ '\n return twemoji;\n});'
+);
+
+require('child_process').spawn(
+ 'node',
+ [
+ path.join(__dirname, '../../', 'node_modules/uglify-js/bin/uglifyjs'),
+ '--verbose',
+ file('twemoji.js'),
+ '-o',
+ file('twemoji.tmp.js')
+ ]
+).on('close', function () {
+ fs.writeFileSync(
+ file('twemoji.min.js'),
+ '/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */\n' +
+ fs.readFileSync(file('twemoji.tmp.js'))
+ );
+ fs.unlink(file('twemoji.tmp.js'));
+ // gzip -c twemoji.min.js | wc -c
+});
\ No newline at end of file
diff --git a/1/utils/generate b/1/utils/generate
new file mode 100755
index 00000000..9f4a6d06
--- /dev/null
+++ b/1/utils/generate
@@ -0,0 +1,995 @@
+#!/usr/bin/env node
+
+ /*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
+ https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
+ */
+
+// dependencies
+var fs = require('fs');
+var http = require('http');
+var path = require('path');
+
+function file(which) {
+ return path.join(__dirname, '../..', which);
+}
+
+// Twitter assets by property name
+var assets = {
+ '72x72': [],
+ 'svg': []
+};
+
+// white spaces we don't want to catch via the RegExp
+// there is no asset equivalent for these
+var ignoreMissing = ['2002', '2003', '2005'];
+
+// basic utility to organize async code
+// see: http://webreflection.blogspot.co.uk/2012/03/tweet-sized-queue-system.html
+// or: http://webreflection.blogspot.co.uk/2012/06/working-with-queues.html
+function Queue(args, f) {
+ setTimeout(args.next = function next() {
+ return (f = args.shift()) ? !!f(args) || !0 : !1;
+ }, 0);
+ return args;
+}
+
+// main task
+Queue([
+
+ // will populate assets arrays
+ function grabAllAssets(q) {
+ console.log('analyzing all assets ... ');
+ // per each path/folder
+ Object.keys(assets).forEach(function (path, i, paths) {
+ // grab all files in that folder
+ fs.readdir(file(path), function (err, files) {
+ // and add them to the assets path
+ assets[path].push.apply(
+ assets[path],
+ files.map(upperCaseWithoutExtension)
+ );
+ // once all assets arrays have been populated
+ if (paths.reduce(completed, true)) {
+ console.log('[INFO] assets contains ' + assets[path].length + ' emoji.');
+ q.next();
+ }
+ });
+ });
+ // drop extension + uppercase
+ function upperCaseWithoutExtension(file) {
+ return file.slice(0, file.lastIndexOf('.')).toUpperCase();
+ }
+ // returns true if all assets have been populated
+ function completed(p, c) {
+ return p && assets[c].length;
+ }
+ },
+
+ // will fetch and store all emoji from unicode.org
+ function fetchEmojiSources(q) {
+ console.log('fetching EmojiSources.txt ... ');
+ // grab all emoji and test them against them
+ http.get("http://www.unicode.org/Public/UNIDATA/EmojiSources.txt", function (res) {
+ var chunks = [];
+ // if all good ...
+ if (res.statusCode === 200) {
+ // grab all data
+ res.on('data', chunks.push.bind(chunks));
+ // once done ...
+ res.on('end', function () {
+ console.log('analyzing EmojiSources VS our assets ... ');
+ // store all missing assets in one object
+ var missing = {};
+ // will be used to store an array with all missing
+ var missingGrouped = {};
+
+ // will be needed later on
+ // parse it, clean it, and store it once
+ q.emojiSource = chunks
+ .join('')
+ .split(/\r\n|\r|\n/)
+ // filter once
+ .filter(function (line) {
+ return this.test(line);
+ }, /^[0-9A-F]/)
+ // take only emoji info
+ .map(function (codePoint) {
+ return codePoint
+ .slice(0, codePoint.indexOf(';'))
+ .toUpperCase()
+ // drop spaces
+ .replace(/\s+/g, '-')
+ // drop 0 padded prefixes
+ .replace(/^0+/g, '');
+ });
+
+ console.log('[INFO] parsed ' + q.emojiSource.length + ' standard emoji.');
+
+ // find out which one is missing from our assets
+ q.emojiSource.forEach(
+ function (emoji) {
+ // do not loop for emoji we know we should ignore
+ if (ignoreMissing.indexOf(emoji) < 0) {
+ // verify all others per each folder
+ this.forEach(function (path) {
+ if (assets[path].indexOf(emoji) < 0) {
+ (missing[path] || (missing[path] = [])).push(emoji);
+ missingGrouped[emoji] = true;
+ }
+ });
+ }
+
+ },
+ // and per each folder
+ Object.keys(assets)
+ );
+
+ // if some missing emoji has been found
+ if (Object.keys(missing).length) {
+ // warn and show which one is missing
+ console.warn('[WARNING] missing assets for:');
+ console.log(missing);
+ }
+ // create the array of all emoji we should ignore
+ q.ignore = ignoreMissing.concat(Object.keys(missingGrouped));
+
+ q.next();
+ });
+ } else {
+ console.error('[ERROR] unable to fetch emoji at unicode.org');
+ process.exit(1);
+ }
+ });
+ },
+
+ // grab the list of emoji that behave differently when
+ // variants such \uFE0E and \uFE0F are in place
+ function grabStandardVariants(q) {
+ console.log('fetching StandardizedVariants.txt ... ');
+ http.get(
+ "http://unicode.org/Public/UNIDATA/StandardizedVariants.txt",
+ function(res) {
+ var chunks = [];
+ if (res.statusCode == 200) {
+ res.on('data', chunks.push.bind(chunks));
+ res.on('end', function () {
+ // cleaning up parsing sensitive emoji
+ q.variantsSensitive = chunks
+ .join('') // all content
+ .split(/\r\n|\r|\n/) // split in lines
+ .filter(function (line) { // containing FE0E; info
+ return this.test(line); // avoiding duplicated with FE0F
+ }, / FE0E; text style/)
+ .map(function (line) { // cleaned up to grab
+ return line.replace(this, '$1') // only first unicode
+ .toUpperCase(); // normalized as uppercase
+ }, /^([0-9A-F]{4,}) FE0E;.+$/) // sensitive char
+ ;
+
+ // iOS keyboard allows U+002A U+FE0F U+20E3 even though not a standardized variant (yet?)
+ q.variantsSensitive.push('002A');
+ // iOS keyboard allows U+2639 U+FE0F even though not a standardized variant (yet?)
+ q.variantsSensitive.push('2639');
+
+ console.log('[INFO] parsed ' + q.variantsSensitive.length + ' variant sensitive emoji.');
+ q.next();
+
+ });
+ } else {
+ console.error('[ERROR] unable to fetch standard variants at unicode.org');
+ process.exit(1);
+ }
+ }
+ );
+ },
+
+ // add our own assets that are not part of the Unicode standard
+ function addMissingEmoji(q) {
+ q.nonStandard = [];
+ Object.keys(assets).forEach(function (path, i) {
+ assets[path].forEach(function (emoji) {
+ if (
+ q.emojiSource.indexOf(emoji) < 0 &&
+ q.nonStandard.indexOf(emoji) < 0
+ ) {
+ q.nonStandard.push(emoji);
+ }
+ });
+ });
+
+ if (q.nonStandard.length) {
+ console.warn('[WARNING] assets contain ' + q.nonStandard.length + ' non standard emoji:');
+ // console.log(q.nonStandard.join(', '));
+ }
+
+ q.emojiSource = q.emojiSource.concat(q.nonStandard)
+ q.next();
+ },
+
+ // detect complete sets of five skin tones and a base
+ function detectDiversityEmoji(q) {
+ var isPresent = {};
+ q.emojiSource.forEach(function (codePoints) {
+ isPresent[codePoints] = true;
+ });
+ q.diversityBase = q.emojiSource.filter(function (codePoints) {
+ // Start with the set of Emoji with the light skin tone
+ return /-1F3FB$/.test(codePoints);
+ }).map(function (codePoints) {
+ // Take the skin tone off
+ return codePoints.replace(/-1F3FB$/, '');
+ }).filter(function (baseCodePoints) {
+ // Verify that all other skin tones + no skin tone are present
+ return ['-1F3FC', '-1F3FD', '-1F3FE', '-1F3FF', ''].every(function (suffix) {
+ return isPresent[baseCodePoints + suffix];
+ });
+ });
+ console.log('[INFO] parsed ' + q.diversityBase.length + ' diversity emoji.');
+ q.next();
+ },
+
+ // with all info, generate a RegExp that will catch
+ // only standard emoji that are present in our assets
+ function generateRegExp(q) {
+ console.log('generating a RegExp for available assets');
+ var zwj = [];
+ var diversity = [];
+ var sensitive = [];
+ var sensitiveKeycaps = [];
+ var diversitySensitive = [];
+ var skinToneOptions = [
+ '\\ud83c\\udffb',
+ '\\ud83c\\udffc',
+ '\\ud83c\\udffd',
+ '\\ud83c\\udffe',
+ '\\ud83c\\udfff'
+ ];
+ var regular = [];
+ q.emojiSource.forEach(function (codePoints) {
+ var u;
+ var codePointsWithoutKeycap;
+ codePoints = codePoints.replace(/\b[A-F0-9]+\b/g, function (hex) {
+ // Pad all hex numbers to have at least 4 digits to match variantsSensitive
+ return hex.length < 4 ? ('000' + hex).slice(-4) : hex;
+ });
+ if (q.ignore.indexOf(codePoints) < 0) {
+ u = toJSON(codePoints);
+ codePointsWithoutKeycap = codePoints.replace(/-20E3$/, '');
+ if (codePoints.indexOf('200D') >= 0) {
+ zwj.push(u);
+ } else if (codePoints != codePointsWithoutKeycap && q.variantsSensitive.indexOf(codePointsWithoutKeycap) >= 0) {
+ sensitiveKeycaps.push(toJSON(codePointsWithoutKeycap));
+ } else if (q.diversityBase.indexOf(codePoints.replace(/-1F3F[B-F]$/, '')) >= 0) {
+ // This is a diversity Emoji with or without a skin tone modifier
+ // Add it to the regex if this is the base without the modifier
+ if (q.diversityBase.indexOf(codePoints) >= 0) {
+ if (q.variantsSensitive.indexOf(codePoints) < 0) {
+ diversity.push(u);
+ } else {
+ diversitySensitive.push(u);
+ }
+ }
+ } else if (q.variantsSensitive.indexOf(codePoints) < 0) {
+ regular.push(u);
+ } else {
+ sensitive.push(u);
+ }
+ }
+ });
+
+ q.re = '';
+
+ // The Zero-width joiner Emojis, if present, need to come first
+ if (zwj.length) {
+ q.re += generateRegexPartial(zwj) + '|';
+ }
+
+ // Group the variant sensitive keycaps
+ if (sensitiveKeycaps.length) {
+ q.re += '(?:' + generateRegexPartial(sensitiveKeycaps) + ')\\ufe0f?\\u20e3|';
+ }
+
+ // Next, add the diversity enabled Emoji that may include a skin tone suffix
+ if (diversity.length + diversitySensitive.length) {
+ q.re += '(?:';
+ if (diversitySensitive.length) {
+ // Some diversity are sensitive to variants
+ q.re += '(?:' + generateRegexPartial(diversitySensitive) + ')(?:\\ufe0f|(?!\\ufe0e))';
+ if (diversity.length) {
+ q.re += '|';
+ }
+ }
+ q.re += generateRegexPartial(diversity) + ')(?:' + generateRegexPartial(skinToneOptions) + '|)|';
+ }
+
+ // Next, the normal Emoji
+ q.re += generateRegexPartial(regular) + '|';
+
+ // Finally, add the rest of the sensitive ones that may be followed by U+FE0F but not U+FE0E
+ q.re += '(?:' + generateRegexPartial(sensitive) + ')(?:\\ufe0f|(?!\\ufe0e))';
+ q.next();
+
+ // basic utilities to convert codepoints to JSON strings
+ function toJSON(codePoints) {
+ return codePoints.split('-').map(function (point) {
+ return UTF162JSON(fromCodePoint(point));
+ }).join('');
+ }
+ function fromCodePoint(codepoint) {
+ var code = typeof codepoint === 'string' ?
+ parseInt(codepoint, 16) : codepoint;
+ if (code < 0x10000) {
+ return String.fromCharCode(code);
+ }
+ code -= 0x10000;
+ return String.fromCharCode(
+ 0xD800 + (code >> 10),
+ 0xDC00 + (code & 0x3FF)
+ );
+ }
+ function UTF162JSON(text) {
+ for (var i = 0, r = []; i < text.length; i++) {
+ r.push('\\u' + ('000' + text.charCodeAt(i).toString(16)).slice(-4));
+ }
+ return r.join('');
+ }
+
+ // Items is an array of unicode sequences with \u escaping, like ["\u2963\ufe0f", "\u263a\ufe0f"]
+ // items get sorted by length (long to short), then unicode hex values (high to low)
+ // output is "or" ed together using | for regex
+ // ouput also combines adjacent items using character classes with ranges when they have common prefixes
+ // Example: "aab", "aac", "aad", "aag", "ba" becomes "aa[b-dg]|ba"
+ function generateRegexPartial(items) {
+ var currentPrefix = null;
+ var result = [];
+ var charClass = [];
+ var charRange = [];
+ items.map(function (item) {
+ // Convert from "\u2963\ufe0f" into ["2963", "fe0f"]
+ return item.split('\\u').slice(1);
+ }).sort(sortMethod).forEach(function (itemParts) {
+ var prefix = itemParts.slice(0, -1).join('\\u');
+ if (prefix) {
+ prefix = '\\u' + prefix;
+ }
+ var suffix = itemParts.slice(-1);
+ if (prefix !== currentPrefix) {
+ flushCharClass();
+ }
+ currentPrefix = prefix;
+ var suffixMinusOne = UTF162JSON(String.fromCharCode(parseInt(suffix, 16) - 1));
+
+ if (charRange.length && charRange.slice(-1)[0] !== suffixMinusOne) {
+ flushCharRange();
+ }
+ charRange.push('\\u' + suffix);
+ });
+
+ flushCharClass();
+ return result.join('|');
+
+ // a and b are arrays of hex UCS-2 units
+ function sortMethod(a, b) {
+ return !a.length ? 0 :
+ b.length - a.length ||
+ parseInt(b[0], 16) - parseInt(a[0], 16) ||
+ sortMethod(b.slice(1), a.slice(1)
+ );
+ }
+
+ function flushCharRange() {
+ charClass = charClass.concat((charRange.length < 3) ?
+ charRange :
+ [ charRange[0], '-', charRange.slice(-1)[0] ]
+ );
+ charRange = [];
+ }
+
+ function flushCharClass() {
+ flushCharRange();
+ if (charClass.length) {
+ result.push(currentPrefix + (charClass.length == 1 ?
+ charClass[0] :
+ '[' + charClass.join('') + ']'
+ ));
+ }
+ charClass = [];
+ currentPrefix = null;
+ }
+ }
+
+ },
+
+ function generateFile(q) {
+ console.log('generating ./twemoji.js');
+ createTwemoji(q.re);
+ require('./create-dist');
+ }
+
+]);
+
+
+
+function createTwemoji(re) {
+ fs.writeFileSync(
+ file('1/twemoji.js'),
+ '/*jslint indent: 2, browser: true, bitwise: true, plusplus: true */\n' +
+ 'var twemoji = (' +
+ function (
+ /*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
+ https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
+ */
+
+ // WARNING: this file is generated automatically via
+ // `node twemoji-generator.js`
+ // please update its `createTwemoji` function
+ // at the bottom of the same file instead.
+
+ ) {
+ 'use strict';
+
+ /*jshint maxparams:4 */
+
+ var
+ // the exported module object
+ twemoji = {
+
+
+ /////////////////////////
+ // properties //
+ /////////////////////////
+
+ // default assets url, by default will be Twitter Inc. CDN
+ base: 'https://twemoji.maxcdn.com/1/',
+
+ // default assets file extensions, by default '.png'
+ ext: '.png',
+
+ // default assets/folder size, by default "72x72"
+ // available via Twitter CDN: 72
+ size: '72x72',
+
+ // default class name, by default 'emoji'
+ className: 'emoji',
+
+ // basic utilities / helpers to convert code points
+ // to JavaScript surrogates and vice versa
+ convert: {
+
+ /**
+ * Given an HEX codepoint, returns UTF16 surrogate pairs.
+ *
+ * @param string generic codepoint, i.e. '1F4A9'
+ * @return string codepoint transformed into utf16 surrogates pair,
+ * i.e. \uD83D\uDCA9
+ *
+ * @example
+ * twemoji.convert.fromCodePoint('1f1e8');
+ * // "\ud83c\udde8"
+ *
+ * '1f1e8-1f1f3'.split('-').map(twemoji.convert.fromCodePoint).join('')
+ * // "\ud83c\udde8\ud83c\uddf3"
+ */
+ fromCodePoint: fromCodePoint,
+
+ /**
+ * Given UTF16 surrogate pairs, returns the equivalent HEX codepoint.
+ *
+ * @param string generic utf16 surrogates pair, i.e. \uD83D\uDCA9
+ * @param string optional separator for double code points, default='-'
+ * @return string utf16 transformed into codepoint, i.e. '1F4A9'
+ *
+ * @example
+ * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3');
+ * // "1f1e8-1f1f3"
+ *
+ * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3', '~');
+ * // "1f1e8~1f1f3"
+ */
+ toCodePoint: toCodePoint
+ },
+
+
+ /////////////////////////
+ // methods //
+ /////////////////////////
+
+ /**
+ * User first: used to remove missing images
+ * preserving the original text intent when
+ * a fallback for network problems is desired.
+ * Automatically added to Image nodes via DOM
+ * It could be recycled for string operations via:
+ * $('img.emoji').on('error', twemoji.onerror)
+ */
+ onerror: function onerror() {
+ if (this.parentNode) {
+ this.parentNode.replaceChild(createText(this.alt), this);
+ }
+ },
+
+ /**
+ * Main method/logic to generate either
tags or HTMLImage nodes.
+ * "emojify" a generic text or DOM Element.
+ *
+ * @overloads
+ *
+ * String replacement for `innerHTML` or server side operations
+ * twemoji.parse(string);
+ * twemoji.parse(string, Function);
+ * twemoji.parse(string, Object);
+ *
+ * HTMLElement tree parsing for safer operations over existing DOM
+ * twemoji.parse(HTMLElement);
+ * twemoji.parse(HTMLElement, Function);
+ * twemoji.parse(HTMLElement, Object);
+ *
+ * @param string|HTMLElement the source to parse and enrich with emoji.
+ *
+ * string replace emoji matches with
tags.
+ * Mainly used to inject emoji via `innerHTML`
+ * It does **not** parse the string or validate it,
+ * it simply replaces found emoji with a tag.
+ * NOTE: be sure this won't affect security.
+ *
+ * HTMLElement walk through the DOM tree and find emoji
+ * that are inside **text node only** (nodeType === 3)
+ * Mainly used to put emoji in already generated DOM
+ * without compromising surrounding nodes and
+ * **avoiding** the usage of `innerHTML`.
+ * NOTE: Using DOM elements instead of strings should
+ * improve security without compromising too much
+ * performance compared with a less safe `innerHTML`.
+ *
+ * @param Function|Object [optional]
+ * either the callback that will be invoked or an object
+ * with all properties to use per each found emoji.
+ *
+ * Function if specified, this will be invoked per each emoji
+ * that has been found through the RegExp except
+ * those follwed by the invariant \uFE0E ("as text").
+ * Once invoked, parameters will be:
+ *
+ * iconId:string the lower case HEX code point
+ * i.e. "1f4a9"
+ *
+ * options:Object all info for this parsing operation
+ *
+ * variant:char the optional \uFE0F ("as image")
+ * variant, in case this info
+ * is anyhow meaningful.
+ * By default this is ignored.
+ *
+ * If such callback will return a falsy value instead
+ * of a valid `src` to use for the image, nothing will
+ * actually change for that specific emoji.
+ *
+ *
+ * Object if specified, an object containing the following properties
+ *
+ * callback Function the callback to invoke per each found emoji.
+ * base string the base url, by default twemoji.base
+ * ext string the image extension, by default twemoji.ext
+ * size string the assets size, by default twemoji.size
+ *
+ * @example
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!");
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", function(iconId, options) {
+ * return '/assets/' + iconId + '.gif';
+ * });
+ * // I
emoji!
+ *
+ *
+ * twemoji.parse("I \u2764\uFE0F emoji!", {
+ * size: 72,
+ * callback: function(iconId, options) {
+ * return '/assets/' + options.size + '/' + iconId + options.ext;
+ * }
+ * });
+ * // I
emoji!
+ *
+ */
+ parse: parse,
+
+ /**
+ * Given a string, invokes the callback argument
+ * per each emoji found in such string.
+ * This is the most raw version used by
+ * the .parse(string) method itself.
+ *
+ * @param string generic string to parse
+ * @param Function a generic callback that will be
+ * invoked to replace the content.
+ * This calback wil receive standard
+ * String.prototype.replace(str, callback)
+ * arguments such:
+ * callback(
+ * rawText, // the emoji match
+ * );
+ *
+ * and others commonly received via replace.
+ */
+ replace: replace,
+
+ /**
+ * Simplify string tests against emoji.
+ *
+ * @param string some text that might contain emoji
+ * @return boolean true if any emoji was found, false otherwise.
+ *
+ * @example
+ *
+ * if (twemoji.test(someContent)) {
+ * console.log("emoji All The Things!");
+ * }
+ */
+ test: test
+ },
+
+ // used to escape HTML special chars in attributes
+ escaper = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''',
+ '"': '"'
+ },
+
+ // RegExp based on emoji's official Unicode standards
+ // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
+ re = /twemoji/,
+
+ // avoid runtime RegExp creation for not so smart,
+ // not JIT based, and old browsers / engines
+ UFE0Fg = /\uFE0F/g,
+
+ // avoid using a string literal like '\u200D' here because minifiers expand it inline
+ U200D = String.fromCharCode(0x200D),
+
+ // used to find HTML special chars in attributes
+ rescaper = /[&<>'"]/g,
+
+ // nodes with type 1 which should **not** be parsed (including lower case svg)
+ shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,
+
+ // just a private shortcut
+ fromCharCode = String.fromCharCode;
+
+ return twemoji;
+
+
+ /////////////////////////
+ // private functions //
+ // declaration //
+ /////////////////////////
+
+ /**
+ * Shortcut to create text nodes
+ * @param string text used to create DOM text node
+ * @return Node a DOM node with that text
+ */
+ function createText(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
+ * based on Twitter CDN
+ * @param string the emoji codepoint string
+ * @param string the default size to use, i.e. "36x36"
+ * @return string the image source to use
+ */
+ function defaultImageSrcGenerator(icon, options) {
+ return ''.concat(options.base, options.size, '/', icon, options.ext);
+ }
+
+ /**
+ * Given a generic DOM nodeType 1, walk through all children
+ * and store every nodeType 3 (#text) found in the tree.
+ * @param Element a DOM Element with probably some text in it
+ * @param Array the list of previously discovered text nodes
+ * @return Array same list with new discovered nodes, if any
+ */
+ function grabAllTextNodes(node, allText) {
+ var
+ childNodes = node.childNodes,
+ length = childNodes.length,
+ subnode,
+ nodeType;
+ while (length--) {
+ subnode = childNodes[length];
+ nodeType = subnode.nodeType;
+ // parse emoji only in text nodes
+ if (nodeType === 3) {
+ // collect them to process emoji later
+ allText.push(subnode);
+ }
+ // ignore all nodes that are not type 1 or that
+ // should not be parsed as script, style, and others
+ else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) {
+ grabAllTextNodes(subnode, allText);
+ }
+ }
+ return allText;
+ }
+
+ /**
+ * Used to both remove the possible variant
+ * and to convert utf16 into code points.
+ * If there is a zero-width-joiner (U+200D), leave the variants in.
+ * @param string the raw text of the emoji match
+ */
+ function grabTheRightIcon(rawText) {
+ // if variant is present as \uFE0F
+ return toCodePoint(rawText.indexOf(U200D) < 0 ?
+ rawText.replace(UFE0Fg, '') :
+ rawText
+ );
+ }
+
+ /**
+ * DOM version of the same logic / parser:
+ * emojify all found sub-text nodes placing images node instead.
+ * @param Element generic DOM node with some text in some child node
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return Element same generic node with emoji in place, if any.
+ */
+ function parseNode(node, options) {
+ var
+ allText = grabAllTextNodes(node, []),
+ length = allText.length,
+ attrib,
+ attrname,
+ modified,
+ fragment,
+ subnode,
+ text,
+ match,
+ i,
+ index,
+ img,
+ rawText,
+ iconId,
+ src;
+ while (length--) {
+ modified = false;
+ fragment = document.createDocumentFragment();
+ subnode = allText[length];
+ text = subnode.nodeValue;
+ i = 0;
+ while ((match = re.exec(text))) {
+ index = match.index;
+ if (index !== i) {
+ fragment.appendChild(
+ createText(text.slice(i, index))
+ );
+ }
+ rawText = match[0];
+ iconId = grabTheRightIcon(rawText);
+ i = index + rawText.length;
+ src = options.callback(iconId, options);
+ if (src) {
+ img = new Image();
+ img.onerror = options.onerror;
+ img.setAttribute('draggable', 'false');
+ attrib = options.attributes(rawText, iconId);
+ 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 = rawText;
+ img.src = src;
+ modified = true;
+ fragment.appendChild(img);
+ }
+ if (!img) fragment.appendChild(createText(rawText));
+ img = null;
+ }
+ // is there actually anything to replace in here ?
+ if (modified) {
+ // any text left to be added ?
+ if (i < text.length) {
+ fragment.appendChild(
+ createText(text.slice(i))
+ );
+ }
+ // replace the text node only, leave intact
+ // anything else surrounding such text
+ subnode.parentNode.replaceChild(fragment, subnode);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * String/HTML version of the same logic / parser:
+ * emojify a generic text placing images tags instead of surrogates pair.
+ * @param string generic string with possibly some emoji in it
+ * @param Object options containing info about how to parse
+ *
+ * .callback Function the callback to invoke per each found emoji.
+ * .base string the base url, by default twemoji.base
+ * .ext string the image extension, by default twemoji.ext
+ * .size string the assets size, by default twemoji.size
+ *
+ * @return the string with
replacing all found and parsed emoji
+ */
+ function parseString(str, options) {
+ return replace(str, function (rawText) {
+ var
+ ret = rawText,
+ iconId = grabTheRightIcon(rawText),
+ src = options.callback(iconId, options),
+ attrib,
+ attrname;
+ if (src) {
+ // recycle the match string replacing the emoji
+ // with its image counter part
+ ret = '
');
+ }
+ 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.
+ * As example, number 36 will return '36x36'.
+ * @param any a generic value.
+ * @return any a string representing asset size, i.e. "36x36"
+ * only in case the value was a number.
+ * Returns initial value otherwise.
+ */
+ function toSizeSquaredAsset(value) {
+ return typeof value === 'number' ?
+ value + 'x' + value :
+ value;
+ }
+
+
+ /////////////////////////
+ // exported functions //
+ // declaration //
+ /////////////////////////
+
+ function fromCodePoint(codepoint) {
+ var code = typeof codepoint === 'string' ?
+ parseInt(codepoint, 16) : codepoint;
+ if (code < 0x10000) {
+ return fromCharCode(code);
+ }
+ code -= 0x10000;
+ return fromCharCode(
+ 0xD800 + (code >> 10),
+ 0xDC00 + (code & 0x3FF)
+ );
+ }
+
+ function parse(what, how) {
+ if (!how || typeof how === 'function') {
+ how = {callback: how};
+ }
+ // if first argument is string, inject html
tags
+ // otherwise use the DOM tree and parse text nodes only
+ return (typeof what === 'string' ? parseString : parseNode)(what, {
+ callback: how.callback || defaultImageSrcGenerator,
+ attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
+ base: typeof how.base === 'string' ? how.base : twemoji.base,
+ ext: how.ext || twemoji.ext,
+ size: how.folder || toSizeSquaredAsset(how.size || twemoji.size),
+ className: how.className || twemoji.className,
+ onerror: how.onerror || twemoji.onerror
+ });
+ }
+
+ function replace(text, callback) {
+ return String(text).replace(re, callback);
+ }
+
+ function test(text) {
+ // IE6 needs a reset before too
+ re.lastIndex = 0;
+ var result = re.test(text);
+ re.lastIndex = 0;
+ return result;
+ }
+
+ function toCodePoint(unicodeSurrogates, sep) {
+ var
+ r = [],
+ c = 0,
+ p = 0,
+ i = 0;
+ while (i < unicodeSurrogates.length) {
+ c = unicodeSurrogates.charCodeAt(i++);
+ if (p) {
+ r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
+ p = 0;
+ } else if (0xD800 <= c && c <= 0xDBFF) {
+ p = c;
+ } else {
+ r.push(c.toString(16));
+ }
+ }
+ return r.join(sep || '-');
+ }
+
+ }.toString()
+ // drop current indentation
+ .replace(/^ /gm, '')
+ // add the RegExp in the right place
+ .replace('re = /twemoji/', 're = /' + re + '/g')
+ // add the full license
+ .replace('/*! (C) Twitter Inc. */',
+ '/*! (C) Twitter Inc. *//*\n' +
+ fs.readFileSync(path.join(__dirname, '../../', 'LICENSE')).toString().replace(
+ /^./gm, ' '
+ ) +
+ '\n */'
+ ) + '());');
+}
diff --git a/1/utils/preview b/1/utils/preview
new file mode 100755
index 00000000..40a6e4d3
--- /dev/null
+++ b/1/utils/preview
@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+
+/*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
+ https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
+*/
+
+// dependencies
+var fs = require('fs');
+var path = require('path');
+
+function file(which) {
+ return path.join(__dirname, '..', which);
+}
+
+fs.readdir(file('../assets'), function (err, files) {
+ var page = fs.readFileSync(file('templates/preview.html')).toString().replace(
+ '{{emoji-list}}',
+ '' + files.map(function (file) {
+ return file.replace('.ai', '').split('-').map(function (hex) {
+ return '' + hex.toUpperCase() + ';';
+ }).join('');
+ }).join('\n ')+ ''
+ );
+ fs.writeFileSync(
+ file('test/preview.html'),
+ page.replace(
+ '{{emoji-options}}',
+ JSON.stringify({
+ size: 72
+ })
+ )
+ );
+ fs.writeFileSync(
+ file('test/preview-svg.html'),
+ page.replace(
+ '{{emoji-options}}',
+ JSON.stringify({
+ folder: '../svg',
+ ext: '.svg',
+ base: ''
+ })
+ )
+ );
+});
\ No newline at end of file
diff --git a/1/utils/size b/1/utils/size
new file mode 100755
index 00000000..4dd4ca67
--- /dev/null
+++ b/1/utils/size
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+echo "twemoji.js $(cat 1/twemoji.js | wc -c)"
+echo "twemoji.js gzipped $(gzip -c 1/twemoji.js | wc -c)"
+echo "twemoji.min.js $(cat 1/twemoji.min.js | wc -c)"
+echo "twemoji.min.js gzipped $(gzip -c 1/twemoji.min.js | wc -c)"