Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
4f1babeb
Commit
4f1babeb
authored
Sep 15, 2020
by
Ethan Reesor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve award/reaction emoji search
parent
3a3e135e
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
120 additions
and
47 deletions
+120
-47
app/assets/javascripts/awards_handler.js
app/assets/javascripts/awards_handler.js
+1
-1
app/assets/javascripts/emoji/index.js
app/assets/javascripts/emoji/index.js
+72
-37
changelogs/unreleased/improve-emoji-support.yml
changelogs/unreleased/improve-emoji-support.yml
+5
-0
spec/frontend/awards_handler_spec.js
spec/frontend/awards_handler_spec.js
+14
-0
spec/frontend/emoji/emoji_spec.js
spec/frontend/emoji/emoji_spec.js
+28
-9
No files found.
app/assets/javascripts/awards_handler.js
View file @
4f1babeb
...
...
@@ -572,7 +572,7 @@ export class AwardsHandler {
}
findMatchingEmojiElements
(
query
)
{
const
emojiMatches
=
this
.
emoji
.
queryEmojiNames
(
query
);
const
emojiMatches
=
this
.
emoji
.
searchEmoji
(
query
).
map
(({
name
})
=>
name
);
const
$emojiElements
=
$
(
'
.emoji-menu-list:not(.frequent-emojis) [data-name]
'
);
const
$matchingElements
=
$emojiElements
.
filter
(
(
i
,
elm
)
=>
emojiMatches
.
indexOf
(
elm
.
dataset
.
name
)
>=
0
,
...
...
app/assets/javascripts/emoji/index.js
View file @
4f1babeb
...
...
@@ -2,53 +2,57 @@ import { uniq } from 'lodash';
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
import
emojiAliases
from
'
emojis/aliases.json
'
;
import
axios
from
'
../lib/utils/axios_utils
'
;
import
AccessorUtilities
from
'
../lib/utils/accessor
'
;
let
emojiMap
=
null
;
let
emojiPromise
=
null
;
let
validEmojiNames
=
null
;
export
const
EMOJI_VERSION
=
'
1
'
;
const
isLocalStorageAvailable
=
AccessorUtilities
.
isLocalStorageAccessSafe
();
export
function
initEmojiMap
()
{
emojiPromise
=
emojiPromise
||
new
Promise
((
resolve
,
reject
)
=>
{
if
(
emojiMap
)
{
resolve
(
emojiMap
);
}
else
if
(
async
function
loadEmoji
()
{
if
(
isLocalStorageAvailable
&&
window
.
localStorage
.
getItem
(
'
gl-emoji-map-version
'
)
===
EMOJI_VERSION
&&
window
.
localStorage
.
getItem
(
'
gl-emoji-map
'
)
)
{
emojiMap
=
JSON
.
parse
(
window
.
localStorage
.
getItem
(
'
gl-emoji-map
'
));
validEmojiNames
=
[...
Object
.
keys
(
emojiMap
),
...
Object
.
keys
(
emojiAliases
)];
resolve
(
emojiMap
);
}
else
{
return
JSON
.
parse
(
window
.
localStorage
.
getItem
(
'
gl-emoji-map
'
));
}
// We load the JSON file direct from the server
// because it can't be loaded from a CDN due to
// cross domain problems with JSON
axios
.
get
(
`
${
gon
.
relative_url_root
||
''
}
/-/emojis/
${
EMOJI_VERSION
}
/emojis.json`
)
.
then
(({
data
})
=>
{
emojiMap
=
data
;
validEmojiNames
=
[...
Object
.
keys
(
emojiMap
),
...
Object
.
keys
(
emojiAliases
)];
resolve
(
emojiMap
);
if
(
isLocalStorageAvailable
)
{
const
{
data
}
=
await
axios
.
get
(
`
${
gon
.
relative_url_root
||
''
}
/-/emojis/
${
EMOJI_VERSION
}
/emojis.json`
,
);
window
.
localStorage
.
setItem
(
'
gl-emoji-map-version
'
,
EMOJI_VERSION
);
window
.
localStorage
.
setItem
(
'
gl-emoji-map
'
,
JSON
.
stringify
(
emojiMap
));
}
})
.
catch
(
err
=>
{
reject
(
err
);
window
.
localStorage
.
setItem
(
'
gl-emoji-map
'
,
JSON
.
stringify
(
data
));
return
data
;
}
async
function
prepareEmojiMap
()
{
emojiMap
=
await
loadEmoji
();
validEmojiNames
=
[...
Object
.
keys
(
emojiMap
),
...
Object
.
keys
(
emojiAliases
)];
Object
.
keys
(
emojiMap
).
forEach
(
name
=>
{
emojiMap
[
name
].
aliases
=
[];
emojiMap
[
name
].
name
=
name
;
});
}
Object
.
entries
(
emojiAliases
).
forEach
(([
alias
,
name
])
=>
{
// This check, `if (name in emojiMap)` is necessary during testing. In
// production, it shouldn't be necessary, because at no point should there
// be an entry in aliases.json with no corresponding entry in emojis.json.
// However, during testing, the endpoint for emojis.json is mocked with a
// small dataset, whereas aliases.json is always `import`ed directly.
if
(
name
in
emojiMap
)
emojiMap
[
name
].
aliases
.
push
(
alias
);
});
}
return
emojiPromise
;
export
function
initEmojiMap
()
{
initEmojiMap
.
promise
=
initEmojiMap
.
promise
||
prepareEmojiMap
();
return
initEmojiMap
.
promise
;
}
export
function
normalizeEmojiName
(
name
)
{
...
...
@@ -77,6 +81,37 @@ export function queryEmojiNames(filter) {
return
uniq
(
matches
.
map
(
name
=>
normalizeEmojiName
(
name
)));
}
/**
* Searches emoji by name, alias, description, and unicode value and returns an
* array of matches.
*
* Note: `initEmojiMap` must have been called and completed before this method
* can safely be called.
*
* @param {String} query The search query
* @returns {Object[]} A list of emoji that match the query
*/
export
function
searchEmoji
(
query
)
{
if
(
!
emojiMap
)
// eslint-disable-next-line @gitlab/require-i18n-strings
throw
new
Error
(
'
The emoji map is uninitialized or initialization has not completed
'
);
const
matches
=
s
=>
fuzzaldrinPlus
.
score
(
s
,
query
)
>
0
;
// Search emoji
return
Object
.
values
(
emojiMap
).
filter
(
emoji
=>
// by name
matches
(
emoji
.
name
)
||
// by alias
emoji
.
aliases
.
some
(
matches
)
||
// by description
matches
(
emoji
.
d
)
||
// by unicode value
query
===
emoji
.
e
,
);
}
let
emojiCategoryMap
;
export
function
getEmojiCategoryMap
()
{
if
(
!
emojiCategoryMap
)
{
...
...
changelogs/unreleased/improve-emoji-support.yml
0 → 100644
View file @
4f1babeb
---
title
:
Improve issuable reaction search
merge_request
:
42321
author
:
Ethan Reesor (@firelizzard)
type
:
added
spec/frontend/awards_handler_spec.js
View file @
4f1babeb
...
...
@@ -319,6 +319,20 @@ describe('AwardsHandler', () => {
expect
(
$
(
'
[data-name=anger]
'
).
is
(
'
:visible
'
)).
toBe
(
false
);
expect
(
$
(
'
[data-name=sunglasses]
'
).
is
(
'
:visible
'
)).
toBe
(
true
);
});
it
(
'
should filter by emoji description
'
,
async
()
=>
{
await
openAndWaitForEmojiMenu
();
awardsHandler
.
searchEmojis
(
'
baby
'
);
expect
(
$
(
'
[data-name=angel]
'
).
is
(
'
:visible
'
)).
toBe
(
true
);
});
it
(
'
should filter by emoji unicode value
'
,
async
()
=>
{
await
openAndWaitForEmojiMenu
();
awardsHandler
.
searchEmojis
(
'
👼
'
);
expect
(
$
(
'
[data-name=angel]
'
).
is
(
'
:visible
'
)).
toBe
(
true
);
});
});
describe
(
'
emoji menu
'
,
()
=>
{
...
...
spec/frontend/emoji/emoji_spec.js
View file @
4f1babeb
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
trimText
}
from
'
helpers/text_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
initEmojiMap
,
glEmojiTag
,
queryEmojiNames
,
EMOJI_VERSION
}
from
'
~/emoji
'
;
import
{
initEmojiMap
,
glEmojiTag
,
searchEmoji
,
EMOJI_VERSION
}
from
'
~/emoji
'
;
import
isEmojiUnicodeSupported
,
{
isFlagEmoji
,
isRainbowFlagEmoji
,
...
...
@@ -31,25 +31,35 @@ const emptySupportMap = {
};
const
emojiFixtureMap
=
{
atom
:
{
name
:
'
atom
'
,
moji
:
'
⚛
'
,
description
:
'
atom symbol
'
,
unicodeVersion
:
'
4.1
'
,
},
bomb
:
{
name
:
'
bomb
'
,
moji
:
'
💣
'
,
unicodeVersion
:
'
6.0
'
,
description
:
'
bomb
'
,
},
construction_worker_tone5
:
{
name
:
'
construction_worker_tone5
'
,
moji
:
'
👷🏿
'
,
unicodeVersion
:
'
8.0
'
,
description
:
'
construction worker tone 5
'
,
},
five
:
{
name
:
'
five
'
,
moji
:
'
5️⃣
'
,
unicodeVersion
:
'
3.0
'
,
description
:
'
keycap digit five
'
,
},
grey_question
:
{
name
:
'
grey_question
'
,
moji
:
'
❔
'
,
unicodeVersion
:
'
6.0
'
,
description
:
'
white question mark ornament
'
,
},
};
...
...
@@ -386,14 +396,23 @@ describe('gl_emoji', () => {
});
});
describe
(
'
queryEmojiNames
'
,
()
=>
{
const
contains
=
(
e
,
term
)
=>
{
const
names
=
queryEmojiNames
(
term
);
expect
(
names
.
indexOf
(
e
.
name
)
>=
0
).
toBe
(
true
);
};
describe
(
'
searchEmoji
'
,
()
=>
{
const
{
atom
,
grey_question
}
=
emojiFixtureMap
;
const
contains
=
(
e
,
term
)
=>
expect
(
searchEmoji
(
term
).
map
(({
name
})
=>
name
)).
toContain
(
e
.
name
);
it
(
'
should match by full name
'
,
()
=>
contains
(
grey_question
,
'
grey_question
'
));
it
(
'
should match by full alias
'
,
()
=>
contains
(
atom
,
'
atom_symbol
'
));
it
(
'
should match by full description
'
,
()
=>
contains
(
grey_question
,
'
ornament
'
));
it
(
'
should match by partial name
'
,
()
=>
contains
(
grey_question
,
'
question
'
));
it
(
'
should match by partial alias
'
,
()
=>
contains
(
atom
,
'
_symbol
'
));
it
(
'
should match by partial description
'
,
()
=>
contains
(
grey_question
,
'
ment
'
));
it
(
'
should fuzzy match by name
'
,
()
=>
contains
(
grey_question
,
'
greion
'
));
it
(
'
should fuzzy match by alias
'
,
()
=>
contains
(
atom
,
'
atobol
'
));
it
(
'
should fuzzy match by description
'
,
()
=>
contains
(
grey_question
,
'
ornt
'
));
it
(
'
should match by name
'
,
()
=>
contains
(
emojiFixtureMap
.
grey_question
,
'
grey_question
'
));
it
(
'
should match by partial name
'
,
()
=>
contains
(
emojiFixtureMap
.
grey_question
,
'
question
'
));
it
(
'
should fuzzy match by name
'
,
()
=>
contains
(
emojiFixtureMap
.
grey_question
,
'
grqtn
'
));
it
(
'
should match by character
'
,
()
=>
contains
(
grey_question
,
'
❔
'
));
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment