On emoji
This blog uses Emoji for favicons, page icons, and when displaying relative links (e.g. have you checked out my post on
Topological sort yet?). It's important to me that they are consistent across browsers.
I thought it might be interesting to talk about that code.
🔗 From Notion
The Page object in the Notion API provides us with an
icon
field that can contain either an image (one which I've uploaded to Notion) or an emoji.
{
object: 'page',
id: '81c174cf-b876-4ade-81ec-ea6927743413',
created_time: '2021-11-24T21:10:00.000Z',
last_edited_time: '2021-11-24T21:13:00.000Z',
cover: null,
icon: { type: 'emoji', emoji: '🪴' },
...
}
The emoji returned from the API is a unicode character so depending on your browser/OS combo, you may not actually see the potted plant in the JSON object above. Sure enough, though, you will see Apple's version of it in both the favicon of this page and next to the title at the top of the screen. How?
🔗 Image files
The emoji-datasource-apple package contains (among other things) 64x64 pixels of all Apple emoji (Twitter and Google emoji are also available and linked from that page). We can list 'em out if we want.
$ ls node_modules/emoji-datasource-apple/img/apple/64 | head
0023-fe0f-20e3.png
002a-fe0f-20e3.png
0030-fe0f-20e3.png
0031-fe0f-20e3.png
0032-fe0f-20e3.png
0033-fe0f-20e3.png
0034-fe0f-20e3.png
0035-fe0f-20e3.png
0036-fe0f-20e3.png
0037-fe0f-20e3.png
We quickly notice, however, that our filenames consist of hexadecimal numbers. These are the unicode characters which eventually cause our emoji to appear on the screen, written out in base 16. (For more info on this, Monica Dinculescu has a fantastic write-up which includes all the history and corner cases). I’ve also written a bit about emoji code points in
Handcrafted emoji.
To view these characters, we can leverage the emoji-unicode package.
emojiUnicode('🥰')
// => '1f970'
emojiUnicode('🇺🇸')
// => '1f1fa 1f1f8'
And looking once again in our emoji-datasource-apple folder, we can find the emoji without issue.
$ ls node_modules/emoji-datasource-apple/img/apple/64 | grep 1f970
1f970.png
The flag is a little harder to find, but sure enough it's there. We just need some dashes. (Exercise for the reader: what might those other
1f1fa
's be?)
$ ls node_modules/emoji-datasource-apple/img/apple/64 | grep 1f1fa
1f1e6-1f1fa.png
1f1e8-1f1fa.png
1f1ea-1f1fa.png
1f1ec-1f1fa.png
1f1ed-1f1fa.png
1f1f1-1f1fa.png
1f1f2-1f1fa.png
1f1f3-1f1fa.png
1f1f7-1f1fa.png
1f1fa-1f1e6.png
1f1fa-1f1ec.png
1f1fa-1f1f2.png
1f1fa-1f1f3.png
1f1fa-1f1f8.png # (we found it)
1f1fa-1f1fe.png
1f1fa-1f1ff.png
1f1fb-1f1fa.png
🔗 All together now
We've now determined our code must do the following:
-
Get the emoji from the Notion API
-
Convert the emoji into its unicode characters (in hexadecimal) using emoji-unicode
-
Massage the hexadecimal a bit to grab the image from emoji-datasource-apple
Then we just need to place the file somewhere. The code in its entirety can be found below and on Github.
async function saveFavicon(emoji) {
const codepoints = emojiUnicode(emoji)
.split(" ")
.join("-");
const basename = `${codepoints}.png`;
const filename = path.join(
__dirname,
"node_modules/emoji-datasource-apple/img/apple/64",
basename
);
if (!fs.existsSync(filename)) {
console.log(
"Unknown emoji --",
emoji,
codepoints
);
}
const dest = path.join(
outputDir,
basename
);
// Some light optimization, no need to copy
// files which already exist in the output
if (!fs.existsSync(dest)) {
await fsPromises.copyFile(
filename,
dest
);
}
return basename;
}
saveFavicon
gives us the filename, which I can happily place in any html template knowing that the image file has safely arrived at its destination.
const headingIcon = icon
? `<img
width="32"
height="32"
alt="${icon.emoji}"
src="${favicon}"
/>`
: null;
And voilà, emoji!