Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use packed emoji #69

Merged
merged 5 commits into from
Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
{
"extends": "standard"
"extends": [
"standard"
],
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module"
},
"env": {
"node": true,
"es6": true,
"es2017": true,
"es2020": true,
"es2021": true
},
"rules": {
"no-unused-vars": [
"error",
{
"ignoreRestSiblings": true
}
]
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
sandbox.js

src/emoji.pack.js

output/
*.alfredworkflow
*.sig
Expand Down
3 changes: 3 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ cd ..

cp icons/beer_mug.png ./icon.png

echo "Creating emoji pack ..."
node ../lib/genpack.js

echo "Updating version ..."
curVersion=$(node -e "console.log(require('${parentDir}/package.json').version)")
sed -i '' 's/{{version}}/'${curVersion}'/' info.plist
Expand Down
66 changes: 66 additions & 0 deletions lib/genpack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict'

const fs = require('fs')
const path = require('path')
const { encode, decode, ExtensionCodec } = require('@msgpack/msgpack')
const emojiKeywords = require('emojilib')
const emojiData = require('unicode-emoji-json')
const orderedEmoji = require('unicode-emoji-json/data-ordered-emoji')
const emojiComponents = require('unicode-emoji-json/data-emoji-components')

const keywordsMap = new Map()
const emmojiInfo = new Map()

const emojiSymbols = Object.keys(emojiKeywords)
for (const emojiSymbol of emojiSymbols) {
emojiKeywords[emojiSymbol].forEach(keyword => {
if (keywordsMap.has(keyword) === false) {
keywordsMap.set(keyword, [])
}
keywordsMap.get(keyword).push(emojiSymbol)
})
emmojiInfo.set(emojiSymbol, {
...emojiData[emojiSymbol],
keywords: emojiKeywords[emojiSymbol]
})
}

const extensionCodec = new ExtensionCodec()
const MAP_EXT_TYPE = 1 // Any in 0-127
extensionCodec.register({
type: MAP_EXT_TYPE,
encode: (object) => {
if (object instanceof Map) {
return encode([...object])
} else {
return null
}
},
decode: (data) => {
const array = decode(data)
return new Map(array)
}
})

const packedEmoji = {
keywords: keywordsMap,
searchTerms: Array.from(keywordsMap.keys()),
emoji: emmojiInfo,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we can pre-build all of the "alfredItems" and store them here. The benefit would be that we don't need to generate item data during search. The down side would be that we have to load an even larger data object on every search term. Alfred doesn't keep the script in memory. It's re-invoking it for every search term (i.e. every key press).

orderedEmoji,
emojiComponents
}
const encodedData = encode(packedEmoji, { extensionCodec })

const packBuffer = Buffer.from(encodedData.buffer, encodedData.byteOffset, encodedData.byteLength)
const emojiPack = `module.exports = '${packBuffer.toString('base64')}';`

fs.writeFile(
path.join(__dirname, '..', 'src', 'emoji.pack.js'),
Buffer.from(emojiPack),
(err) => {
if (err) {
console.error(err)
process.exit(1)
}
}
)
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"webpack": "webpack",
"clean": "rm -f *.alfredworkflow; rm -rf output/*",
"build": "./build.sh",
"genpack": "node lib/genpack.js",
"load": "npm run build && open alfred-emoji.alfredworkflow",
"pretest": "npm run genpack",
"test": "tap 'test/**/*.test.js' -R terse",
"test:watch": "npm test -- --watch --no-coverage-report",
"test-ci": "tap --cov 'test/**/*.test.js' -R terse",
Expand All @@ -16,16 +18,17 @@
"test"
],
"dependencies": {
"emojilib": "^3.0.2",
"fontkit": "^1.7.7",
"unicode-emoji-json": "^0.3.0"
"@msgpack/msgpack": "^2.7.1"
},
"devDependencies": {
"mock-require": "^3.0.3",
"buffer": "^6.0.3",
"emojilib": "^3.0.2",
"fontkit": "^1.8.1",
"pre-commit": "^1.2.2",
"snazzy": "^9.0.0",
"standard": "^16.0.4",
"tap": "^15.1.6",
"unicode-emoji-json": "^0.3.0",
"webpack": "^5.67.0",
"webpack-cli": "^4.9.2"
}
Expand Down
40 changes: 26 additions & 14 deletions src/search.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
'use strict'

const emojiData = require('unicode-emoji-json')
const emojiComponents = require('unicode-emoji-json/data-emoji-components')
const orderedEmoji = require('unicode-emoji-json/data-ordered-emoji')
const emojiKeywords = require('emojilib')
const emojiData = require('./unpack-emoji')()
const {
keywords: emojiKeywords,
emoji: emojiInfo,
searchTerms,
orderedEmoji,
emojiComponents
} = emojiData

// compatability layer:
const modifiers = [
Expand Down Expand Up @@ -53,6 +57,12 @@ const getIconName = (emoji) => {
}

const alfredItem = (emoji, char) => {
if (emoji === undefined) {
// Can happen when `char` references an emoji the system does not
// recognize. This happens with newer Unicode data sets being used on
// older macOS releases.
return
}
const modifiedEmoji = addModifier(emoji, char, modifier)
const icon = getIconName(emoji)
const name = emoji.name
Expand Down Expand Up @@ -83,24 +93,26 @@ const alfredItem = (emoji, char) => {
const alfredItems = (chars) => {
const items = []
chars.forEach((char) => {
items.push(alfredItem(emojiData[char], char))
items.push(alfredItem(emojiInfo.get(char), char))
})
return { items }
}

const all = () => alfredItems(orderedEmoji)

const libHasEmoji = (char, term) => {
return emojiKeywords[char] &&
emojiKeywords[char].some((keyword) => keyword.includes(term))
}
const matches = (terms) => {
return orderedEmoji.filter((char) => {
const name = emojiData[char].name
return terms.every((term) => {
return name.includes(term) || libHasEmoji(char, term)
const result = []
for (const term of terms) {
if (emojiKeywords.has(term)) {
Array.prototype.push.apply(result, emojiKeywords.get(term))
continue
}
const foundTerms = searchTerms.filter(searchTerm => searchTerm.includes(term))
foundTerms.forEach(foundTerm => {
Array.prototype.push.apply(result, emojiKeywords.get(foundTerm))
})
})
}
return new Set(result)
}

// :thumbs up: => ['thumbs', 'up']
Expand Down
21 changes: 21 additions & 0 deletions src/unpack-emoji.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

const packedEmoji = require('./emoji.pack')
const Buffer = require('buffer/').Buffer

module.exports = function unpackEmoji () {
const dataArray = Buffer.from(packedEmoji, 'base64')

const { decode, ExtensionCodec } = require('@msgpack/msgpack')
const extensionCodec = new ExtensionCodec()
const MAP_EXT_TYPE = 1
extensionCodec.register({
type: MAP_EXT_TYPE,
decode: (data) => {
const array = decode(data)
return new Map(array)
}
})

return decode(dataArray, { extensionCodec })
}
40 changes: 0 additions & 40 deletions test/emojiKeywordsMock.json

This file was deleted.

16 changes: 5 additions & 11 deletions test/search.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
'use strict'

const { test } = require('tap')
const mock = require('mock-require')
mock('unicode-emoji-json', './unicodeEmojiMock.json')
mock('unicode-emoji-json/data-emoji-components', './unicodeEmojiComponentsMock.json')
mock('unicode-emoji-json/data-ordered-emoji', './unicodeEmojiOrderedMock.json')
mock('emojilib', './emojiKeywordsMock.json')

const search = require('../src/search')

test('finds "thumbs up"', (t) => {
Expand Down Expand Up @@ -74,19 +68,19 @@ test('applies modifier if possible', (t) => {
test('enables uid', (t) => {
t.plan(1)
const found = search('grimacing')
t.ok(found.items[0].uid === 'grimacing')
t.ok(found.items[0].uid === 'grimacing face')
})

test('enables autocomplete', (t) => {
t.plan(1)
const found = search('think')
t.ok(found.items[0].autocomplete === 'thinking')
t.ok(found.items[0].autocomplete === 'thinking face')
})

test('enables alt-modifier', (t) => {
t.plan(1)
const found = search('hear_no_evil')
t.ok(found.items[0].mods.alt.arg === ':hear_no_evil:')
t.ok(found.items[0].mods.alt.arg === ':hear_no_evil_monkey:')
})

test('unique results', (t) => {
Expand All @@ -100,13 +94,13 @@ test('unique results', (t) => {
test('finds "open book"', (t) => {
t.plan(1)
const found = search('open book')
t.ok(found.items[0].title === 'open_book')
t.equal(found.items.filter(i => i.title === 'open book').length, 1)
})

test('finds "book open"', (t) => {
t.plan(1)
const found = search('book open')
t.ok(found.items[0].title === 'open_book')
t.ok(found.items[0].title === 'open book')
})

test('finds "plant nature"', (t) => {
Expand Down
11 changes: 0 additions & 11 deletions test/unicodeEmojiComponentsMock.json

This file was deleted.

Loading