Skip to content

Commit

Permalink
Merge pull request #38 from Glennmen/feature/skin-tone
Browse files Browse the repository at this point in the history
Added skin tone option
  • Loading branch information
jsumners authored Apr 18, 2020
2 parents 6aa9e8e + a17685a commit e612d9b
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 11 deletions.
19 changes: 19 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,28 @@ your clipboard.
Press <kbd>alt</kbd>+<kbd>return</kbd> (βŒ₯↡): **Copy the code** of the selected emoji)
(e.g. `:rofl:`) to your clipboard.

Press <kbd>shift</kbd>+<kbd>return</kbd> (⇧↡): **Copy the default symbol** of the selected emoji)
(e.g. 🀣) to your clipboard without skin tone modifier.

Press <kbd>cmd</kbd>+<kbd>return</kbd> (βŒ˜β†΅): **Paste the symbol** of the selected
emoji (e.g. 🀣) directly to your frontmost application.

### Set skin tone

To change the emoji skin tone for supported emoji set the `skin_tone` environment variable in Alfred:

![screenshot skin tone settings](images/screenshot-skin-tone-setting.png)

Options:
- No value => πŸ‘
- `0` => πŸ‘πŸ»
- `1` => πŸ‘πŸΌ
- `2` => πŸ‘πŸ½
- `3` => πŸ‘πŸΎ
- `4` => πŸ‘πŸΏ

After setting skin tone you can still quickly copy the default emoji with the <kbd>shift</kbd> modifier.

## Automatic Updates

This workflow will automatically check for updates at most once per day. If a
Expand Down
Binary file added images/screenshot-skin-tone-setting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 20 additions & 2 deletions lib/genicons.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@ const fs = require('fs')
const fontkit = require('fontkit')
const emojilib = require('emojilib')

const modifiers = emojilib.fitzpatrick_scale_modifiers
const font = fontkit.openSync('/System/Library/Fonts/Apple Color Emoji.ttc').fonts[0]

const saveIcon = (emoji, name) => {
const glyph = emoji.glyphs[0].getImageForSize(64)
fs.writeFileSync(`${process.env['PWD']}/${name}.png`, glyph.data)
}

const addModifier = (emoji, modifier) => {
if (!modifier || !emoji.fitzpatrick_scale) return emoji.char
const zwj = new RegExp('‍', 'g')
return emoji.char.match(zwj) ? emoji.char.replace(zwj, modifier + '‍') : emoji.char + modifier
}

emojilib.ordered.forEach((name) => {
try {
const emoji = font.layout(emojilib.lib[name].char)
const glyph = emoji.glyphs[0].getImageForSize(64)
fs.writeFileSync(`${process.env['PWD']}/${name}.png`, glyph.data)
saveIcon(emoji, name)

if (emojilib.lib[name].fitzpatrick_scale) {
modifiers.forEach((modifier, index) => {
const emoji = font.layout(addModifier(emojilib.lib[name], modifier))
saveIcon(emoji, `${name}_${index}`)
})
}
} catch (e) {
console.error('Could not generate icon for "%s": %s', name, e.message)
}
Expand Down
5 changes: 3 additions & 2 deletions src/emoji.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ function getenv (name) {

ObjC.import('stdlib')
try {
return $.getenv('snippetapp')
return $.getenv(name)
} catch (e) {
return null
}
}

const search = require('./search')
const pasteByDefault = getenv('snippetapp')
const skinTone = getenv('skin_tone')

// JXA: JavaScript for Automation Interface (`osascript -l JavaScript`)
// Note: In JXA, console.log writes to stderr instead of stdout
function run (argv) {
const query = argv[0]
const found = search(query, pasteByDefault)
const found = search(query, skinTone, pasteByDefault)
console.log(JSON.stringify(found))
}

Expand Down
5 changes: 5 additions & 0 deletions src/info.plist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ osascript -l JavaScript emoji.js "$query" 2&gt;&amp;1</string>
<integer>180</integer>
</dict>
</dict>
<key>variables</key>
<dict>
<key>skin_tone</key>
<string></string>
</dict>
<key>version</key>
<string>{{version}}</string>
<key>webaddress</key>
Expand Down
46 changes: 39 additions & 7 deletions src/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

const emojilib = require('emojilib')
const emojiNames = emojilib.ordered
const modifiers = emojilib.fitzpatrick_scale_modifiers

let skinTone
let modifier

let verb = 'Copy'
let preposition = 'to clipboard'
Expand All @@ -11,18 +15,44 @@ const resetWordsForPasteByDefault = () => {
preposition = 'as snippet'
}

const setSkinToneModifier = (tone) => {
skinTone = tone
modifier = skinTone ? modifiers[skinTone] : null
}

const addModifier = (emoji, modifier) => {
if (!modifier || !emoji.fitzpatrick_scale) return emoji.char
const zwj = new RegExp('‍', 'g')
return emoji.char.match(zwj) ? emoji.char.replace(zwj, modifier + '‍') : emoji.char + modifier
}

const getIconName = (emoji, name) => {
if (emoji.fitzpatrick_scale && skinTone && skinTone >= 0 && skinTone < 5) {
return `${name}_${skinTone}`
}
return name
}

const alfredItem = (emoji, name) => {
const modifiedEmoji = addModifier(emoji, modifier)
const icon = getIconName(emoji, name)
return {
uid: name,
title: name,
subtitle: `${verb} "${emoji}" (${name}) ${preposition}`,
arg: emoji,
subtitle: `${verb} "${modifiedEmoji}" (${name}) ${preposition}`,
arg: modifiedEmoji,
autocomplete: name,
icon: { path: `./icons/${name}.png` },
icon: { path: `./icons/${icon}.png` },
mods: {
alt: {
subtitle: `${verb} ":${name}:" (${emoji}) ${preposition}`,
arg: `:${name}:`
subtitle: `${verb} ":${name}:" (${emoji.char}) ${preposition}`,
arg: `:${name}:`,
icon: { path: `./icons/${name}.png` }
},
shift: {
subtitle: `${verb} "${emoji.char}" (${name}) ${preposition}`,
arg: emoji.char,
icon: { path: `./icons/${name}.png` }
}
}
}
Expand All @@ -31,7 +61,7 @@ const alfredItem = (emoji, name) => {
const alfredItems = (names) => {
const items = []
names.forEach((name) => {
const emoji = emojilib.lib[name].char
const emoji = emojilib.lib[name]
if (!emoji) return
items.push(alfredItem(emoji, name))
})
Expand All @@ -52,9 +82,11 @@ const matches = (terms) => {
// :thumbs up: => ['thumbs', 'up']
const parse = query => query.replace(/[:]/g, '').split(/\s+/)

module.exports = function search (query, pasteByDefault = false) {
module.exports = function search (query, skinTone, pasteByDefault = false) {
if (pasteByDefault) resetWordsForPasteByDefault()

setSkinToneModifier(skinTone)

if (!query) return all()

const terms = parse(query)
Expand Down
30 changes: 30 additions & 0 deletions test/search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,33 @@ test('finds "teddy bear (macOS 10.14.1 / iOS 12.1)"', (t) => {
const found = search('teddy bear')
t.ok(Object.keys(found.items).length > 0)
})

test('finds "+1" null skin tone', (t) => {
t.plan(1)
const found = search('+1', null)
t.ok(found.items[0].arg === 'πŸ‘')
})

test('finds "+1" medium skin tone', (t) => {
t.plan(1)
const found = search('+1', 2)
t.ok(found.items[0].arg === 'πŸ‘πŸ½')
})

test('finds "+1" invalid skin tone', (t) => {
t.plan(1)
const found = search('+1', 5)
t.ok(found.items[0].arg === 'πŸ‘')
})

test('enables "+1" shift-modifier neutral skin tone', (t) => {
t.plan(1)
const found = search('+1', 2)
t.ok(found.items[0].mods.shift.arg === 'πŸ‘')
})

test('finds "unicorn" (ignore skin tone)', (t) => {
t.plan(1)
const found = search('unicorn', 2)
t.ok(found.items[0].arg === 'πŸ¦„')
})

0 comments on commit e612d9b

Please sign in to comment.