mirror of
https://github.com/mdn/webextensions-examples.git
synced 2026-04-16 06:18:35 +02:00
Adds an Emoji Substitution example (#110)
* Adds an Emoji Substitution example * Monitors DOM for additions after initial load * Fixes typos * Fixes substitution ordering bug * <textarea> bug fix. Also removes confusion between Elements and Nodes.
This commit is contained in:
committed by
wbamberg
parent
6bb818e62a
commit
07375ca50a
9
emoji-substitution/README.md
Normal file
9
emoji-substitution/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Emoji Substitution
|
||||
|
||||
## What it does
|
||||
|
||||
Replaces words that describe an emoji with the emoji itself.
|
||||
|
||||
## What it shows
|
||||
|
||||
A good example for beginners that can be used as a "make your first add-on" tutorial and / or referenced to create other add-ons.
|
||||
115
emoji-substitution/emojiMap.js
Normal file
115
emoji-substitution/emojiMap.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* This file contains the Map of word --> emoji substitutions.
|
||||
*/
|
||||
|
||||
let dictionary = new Map();
|
||||
dictionary.set('apple', '🍎');
|
||||
dictionary.set('banana', '🍌');
|
||||
dictionary.set('bang', '💥');
|
||||
dictionary.set('baseball', '⚾');
|
||||
dictionary.set('basketball', '🏀');
|
||||
dictionary.set('beer', '🍺');
|
||||
dictionary.set('bicycle', '🚴');
|
||||
dictionary.set('bike', '🚴');
|
||||
dictionary.set('bomb', '💣');
|
||||
dictionary.set('boy', '👦');
|
||||
dictionary.set('bug', '🐛');
|
||||
dictionary.set('burger', '🍔');
|
||||
dictionary.set('burn', '🔥');
|
||||
dictionary.set('cake', '🎂');
|
||||
dictionary.set('candy', '🍬');
|
||||
dictionary.set('cat', '🐱');
|
||||
dictionary.set('celebration', '🎉');
|
||||
dictionary.set('cheeseburger', '🍔');
|
||||
dictionary.set('cookie', '🍪');
|
||||
dictionary.set('cool', '😎');
|
||||
dictionary.set('cry', '😢');
|
||||
dictionary.set('dog', '🐶');
|
||||
dictionary.set('doge', '🐕');
|
||||
dictionary.set('earth', '🌎');
|
||||
dictionary.set('explode', '💥');
|
||||
dictionary.set('fart', '💨');
|
||||
dictionary.set('fast', '💨');
|
||||
dictionary.set('female', '👩');
|
||||
dictionary.set('fire', '🔥');
|
||||
dictionary.set('fish', '🐟');
|
||||
dictionary.set('flame', '🔥');
|
||||
dictionary.set('flower', '🌹');
|
||||
dictionary.set('food', '🍕');
|
||||
dictionary.set('football', '🏈');
|
||||
dictionary.set('girl', '👧');
|
||||
dictionary.set('golf', '⛳');
|
||||
dictionary.set('hamburger', '🍔');
|
||||
dictionary.set('happy', '😀');
|
||||
dictionary.set('horse', '🐴');
|
||||
dictionary.set('hot', '🔥');
|
||||
dictionary.set('kiss', '😘');
|
||||
dictionary.set('laugh', '😂');
|
||||
dictionary.set('lit', '🔥');
|
||||
dictionary.set('lock', '🔒');
|
||||
dictionary.set('lol', '😂');
|
||||
dictionary.set('love', '😍');
|
||||
dictionary.set('male', '👨');
|
||||
dictionary.set('man', '👨');
|
||||
dictionary.set('monkey', '🐵');
|
||||
dictionary.set('moon', '🌙');
|
||||
dictionary.set('note', '📝');
|
||||
dictionary.set('paint', '🎨');
|
||||
dictionary.set('panda', '🐼');
|
||||
dictionary.set('party', '🎉');
|
||||
dictionary.set('pig', '🐷');
|
||||
dictionary.set('pizza', '🍕');
|
||||
dictionary.set('planet', '🌎');
|
||||
dictionary.set('rose', '🌹');
|
||||
dictionary.set('sad', '😢');
|
||||
dictionary.set('sleep', '😴');
|
||||
dictionary.set('smile', '😀');
|
||||
dictionary.set('smiley', '😀');
|
||||
dictionary.set('soccer', '⚽');
|
||||
dictionary.set('star', '⭐');
|
||||
dictionary.set('sun', '☀️');
|
||||
dictionary.set('sunglasses', '😎');
|
||||
dictionary.set('surprised', '😮');
|
||||
dictionary.set('tree', '🌲');
|
||||
dictionary.set('trophy', '🏆');
|
||||
dictionary.set('win', '🏆');
|
||||
dictionary.set('wind', '💨');
|
||||
dictionary.set('wine', '🍷');
|
||||
dictionary.set('wink', '😉');
|
||||
dictionary.set('woman', '👩');
|
||||
dictionary.set('world', '🌎');
|
||||
dictionary.set('wow', '😮');
|
||||
|
||||
/*
|
||||
* After all the dictionary entries have been set, sort them by length.
|
||||
*
|
||||
* Because iteration over Maps happens by insertion order, this avoids
|
||||
* scenarios where words that are substrings of other words get substituted
|
||||
* first, leading to the longer word's substitution never triggering.
|
||||
*
|
||||
* For example, the 'woman' substitution would never get triggered
|
||||
* if the 'man' substitution happens first because the input term 'woman'
|
||||
* would become 'wo👨', and the search for 'woman' would not find any matches.
|
||||
*/
|
||||
let tempArray = Array.from(dictionary);
|
||||
tempArray.sort((pair1, pair2) => {
|
||||
// Each pair is an array with two entries: a word, and its emoji.
|
||||
// Ex: ['woman', '👩']
|
||||
const firstWord = pair1[0];
|
||||
const secondWord = pair2[0];
|
||||
|
||||
if (firstWord.length > secondWord.length) {
|
||||
// The first word should come before the second word.
|
||||
return -1;
|
||||
}
|
||||
if (secondWord.length > firstWord.length) {
|
||||
// The second word should come before the first word.
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The words have the same length, it doesn't matter which comes first.
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Now that the entries are sorted, put them back into a Map.
|
||||
let sortedEmojiMap = new Map(tempArray);
|
||||
BIN
emoji-substitution/icons/icon.png
Normal file
BIN
emoji-substitution/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
emoji-substitution/icons/icon@2x.png
Normal file
BIN
emoji-substitution/icons/icon@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
24
emoji-substitution/manifest.json
Normal file
24
emoji-substitution/manifest.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"strict_min_version": "49.0.1"
|
||||
}
|
||||
},
|
||||
|
||||
"name": "Emoji Substitution",
|
||||
"description": "Replaces words with emojis.",
|
||||
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/emoji-substitution",
|
||||
"version": "1.0",
|
||||
"icons": {
|
||||
"48": "icons/icon.png",
|
||||
"96": "icons/icon@2x.png"
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["./emojiMap.js", "./substitute.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
93
emoji-substitution/substitute.js
Normal file
93
emoji-substitution/substitute.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* This file is responsible for performing the logic of replacing
|
||||
* all occurrences of each mapped word with its emoji counterpart.
|
||||
*/
|
||||
|
||||
// emojiMap.js defines the 'sortedEmojiMap' variable.
|
||||
// Referenced here to reduce confusion.
|
||||
const emojiMap = sortedEmojiMap;
|
||||
|
||||
/*
|
||||
* For efficiency, create a word --> search RegEx Map too.
|
||||
*/
|
||||
let regexs = new Map();
|
||||
for (let word of emojiMap.keys()) {
|
||||
// We want a global, case-insensitive replacement.
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
||||
regexs.set(word, new RegExp(word, 'gi'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitutes emojis into text nodes.
|
||||
* If the node contains more than just text (ex: it has child nodes),
|
||||
* call replaceText() on each of its children.
|
||||
*
|
||||
* @param {Node} node - The target DOM Node.
|
||||
* @return {void} - Note: the emoji substitution is done inline.
|
||||
*/
|
||||
function replaceText (node) {
|
||||
// Setting textContent on a node removes all of its children and replaces
|
||||
// them with a single text node. Since we don't want to alter the DOM aside
|
||||
// from substituting text, we only substitute on single text nodes.
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
// This node only contains text.
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType.
|
||||
|
||||
// Skip textarea nodes due to the potential for accidental submission
|
||||
// of substituted emoji where none was intended.
|
||||
if (node.parentNode &&
|
||||
node.parentNode.nodeName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because DOM manipulation is slow, we don't want to keep setting
|
||||
// textContent after every replacement. Instead, manipulate a copy of
|
||||
// this string outside of the DOM and then perform the manipulation
|
||||
// once, at the end.
|
||||
let content = node.textContent;
|
||||
|
||||
// Replace every occurrence of 'word' in 'content' with its emoji.
|
||||
// Use the emojiMap for replacements.
|
||||
for (let [word, emoji] of emojiMap) {
|
||||
// Grab the search regex for this word.
|
||||
const regex = regexs.get(word);
|
||||
|
||||
// Actually do the replacement / substitution.
|
||||
// Note: if 'word' does not appear in 'content', nothing happens.
|
||||
content = content.replace(regex, emoji);
|
||||
}
|
||||
|
||||
// Now that all the replacements are done, perform the DOM manipulation.
|
||||
node.textContent = content;
|
||||
}
|
||||
else {
|
||||
// This node contains more than just text, call replaceText() on each
|
||||
// of its children.
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
replaceText(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the recursion from the body tag.
|
||||
replaceText(document.body);
|
||||
|
||||
// Now monitor the DOM for additions and substitute emoji into new nodes.
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver.
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
|
||||
// This DOM change was new nodes being added. Run our substitution
|
||||
// algorithm on each newly added node.
|
||||
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
||||
const newNode = mutation.addedNodes[i];
|
||||
replaceText(newNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
@@ -207,6 +207,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Emoji Substitution",
|
||||
"description": "Replaces words with emojis.",
|
||||
"url": "https://github.com/mdn/webextensions-examples/tree/master/emoji-substitution",
|
||||
"manifest_keys": [
|
||||
"content_scripts"
|
||||
],
|
||||
"javascript_modules": []
|
||||
},
|
||||
{
|
||||
"name": "favourite-colour",
|
||||
"description": "An example options ui",
|
||||
|
||||
Reference in New Issue
Block a user