fix: filter unsupported emoji
This commit is contained in:
parent
6833b12be1
commit
53126351cc
|
@ -2,10 +2,31 @@ import { store } from '../_store/store'
|
|||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
import * as emojiDatabase from '../_utils/emojiDatabase'
|
||||
import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
|
||||
import { testEmojiSupported } from '../_utils/testEmojiSupported'
|
||||
import { mark, stop } from '../_utils/marks'
|
||||
|
||||
async function searchEmoji (searchText) {
|
||||
const results = await emojiDatabase.findBySearchQuery(searchText.substring(1))
|
||||
return results.slice(0, SEARCH_RESULTS_LIMIT)
|
||||
let emojis = await emojiDatabase.findBySearchQuery(searchText)
|
||||
|
||||
const results = []
|
||||
|
||||
if (searchText.startsWith(':') && searchText.endsWith(':')) {
|
||||
// exact shortcode search
|
||||
const shortcode = searchText.substring(1, searchText.length - 1).toLowerCase()
|
||||
emojis = emojis.filter(_ => _.shortcodes.includes(shortcode))
|
||||
}
|
||||
|
||||
mark('testEmojiSupported')
|
||||
for (const emoji of emojis) {
|
||||
if (results.length === SEARCH_RESULTS_LIMIT) {
|
||||
break
|
||||
}
|
||||
if (testEmojiSupported(emoji.unicode)) {
|
||||
results.push(emoji)
|
||||
}
|
||||
}
|
||||
stop('testEmojiSupported')
|
||||
return results
|
||||
}
|
||||
|
||||
export function doEmojiSearch (searchText) {
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
</span>
|
||||
{/if}
|
||||
<span class="compose-autosuggest-list-display-name compose-autosuggest-list-display-name-single">
|
||||
{':' + item.shortcodes[0] + ':'}
|
||||
{item.shortcodes.map(_ => `:${_}:`).join(' ')}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
ref:textarea
|
||||
bind:value=rawText
|
||||
on:focus="onFocus()"
|
||||
on:blur="onBlur()"
|
||||
on:selectionChange="onSelectionChange(event)"
|
||||
on:keydown="onKeydown(event)"
|
||||
></textarea>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// same as the one used for PinaforeEmoji
|
||||
export const FONT_FAMILY = '"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",' +
|
||||
'"Twemoji Mozilla","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif'
|
|
@ -5,7 +5,7 @@ const MIN_PREFIX_LENGTH = 2
|
|||
// Technically mastodon accounts allow dots, but it would be weird to do an autosuggest search if it ends with a dot.
|
||||
// Also this is rare. https://github.com/tootsuite/mastodon/pull/6844
|
||||
// However for emoji search we allow some extra things (e.g. :+1:, :white_heart:)
|
||||
const VALID_CHARS = '[\\w\\+_]'
|
||||
const VALID_CHARS = '[\\w\\+_\\-:]'
|
||||
const PREFIXES = '(?:@|:|#)'
|
||||
const REGEX = new RegExp(`(?:\\s|^)(${PREFIXES}${VALID_CHARS}{${MIN_PREFIX_LENGTH},})$`)
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Copied from
|
||||
// https://github.com/nolanlawson/emoji-picker-element/blob/04f490a/src/picker/utils/testColorEmojiSupported.js
|
||||
|
||||
import { FONT_FAMILY } from '../_static/fonts'
|
||||
|
||||
const getTextFeature = (text, color) => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = canvas.height = 1
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.textBaseline = 'top'
|
||||
ctx.font = `100px ${FONT_FAMILY}`
|
||||
ctx.fillStyle = color
|
||||
ctx.scale(0.01, 0.01)
|
||||
ctx.fillText(text, 0, 0)
|
||||
|
||||
return ctx.getImageData(0, 0, 1, 1).data
|
||||
} catch (e) { /* ignore, return undefined */ }
|
||||
}
|
||||
|
||||
const compareFeatures = (feature1, feature2) => {
|
||||
const feature1Str = [...feature1].join(',')
|
||||
const feature2Str = [...feature2].join(',')
|
||||
return feature1Str === feature2Str && feature1Str !== '0,0,0,0'
|
||||
}
|
||||
|
||||
export function testColorEmojiSupported (text) {
|
||||
// Render white and black and then compare them to each other and ensure they're the same
|
||||
// color, and neither one is black. This shows that the emoji was rendered in color.
|
||||
const feature1 = getTextFeature(text, '#000')
|
||||
const feature2 = getTextFeature(text, '#fff')
|
||||
|
||||
const supported = feature1 && feature2 && compareFeatures(feature1, feature2)
|
||||
if (!supported) {
|
||||
console.log('Filtered unsupported emoji via color test', text)
|
||||
}
|
||||
return supported
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Return true if the unicode is rendered as a double character, e.g.
|
||||
// "black cat" or "polar bar" or "person with red hair" or other emoji
|
||||
// that look like double or triple emoji if the unicode is not rendered properly
|
||||
|
||||
const BASELINE_EMOJI = '😀'
|
||||
|
||||
let baselineWidth
|
||||
|
||||
export function testEmojiRenderedAtCorrectSize (unicode) {
|
||||
if (!unicode.includes('\u200d')) { // ZWJ
|
||||
return true // benefit of the doubt
|
||||
}
|
||||
|
||||
let emojiTestDiv = document.getElementById('emoji-test')
|
||||
if (!emojiTestDiv) {
|
||||
emojiTestDiv = document.createElement('div')
|
||||
emojiTestDiv.id = 'emoji-test'
|
||||
emojiTestDiv.ariaHidden = true
|
||||
Object.assign(emojiTestDiv.style, {
|
||||
position: 'absolute',
|
||||
opacity: '0',
|
||||
'pointer-events': 'none',
|
||||
'font-family': 'PinaforeEmoji',
|
||||
'font-size': '14px',
|
||||
contain: 'content'
|
||||
})
|
||||
document.body.appendChild(emojiTestDiv)
|
||||
}
|
||||
emojiTestDiv.textContent = unicode
|
||||
const { width } = emojiTestDiv.getBoundingClientRect()
|
||||
if (typeof baselineWidth === 'undefined') {
|
||||
emojiTestDiv.textContent = BASELINE_EMOJI
|
||||
baselineWidth = emojiTestDiv.getBoundingClientRect().width
|
||||
}
|
||||
|
||||
// WebKit has some imprecision here, so round it
|
||||
const emojiSupported = width.toFixed(2) === baselineWidth.toFixed(2)
|
||||
if (!emojiSupported) {
|
||||
console.log('Filtered unsupported emoji via size test', unicode, 'width', width, 'baselineWidth', baselineWidth)
|
||||
}
|
||||
|
||||
return emojiSupported
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { testColorEmojiSupported } from './testColorEmojiSupported'
|
||||
import { testEmojiRenderedAtCorrectSize } from './testEmojiRenderedAtCorrectSize'
|
||||
import { QuickLRU } from '../_thirdparty/quick-lru/quick-lru'
|
||||
|
||||
// avoid recomputing emoji support over and over again
|
||||
const emojiSupportCache = new QuickLRU({
|
||||
maxSize: 500
|
||||
})
|
||||
|
||||
export function testEmojiSupported (unicode) {
|
||||
let supported = emojiSupportCache.get(unicode)
|
||||
if (typeof supported !== 'boolean') {
|
||||
supported = !!(testColorEmojiSupported(unicode) && testEmojiRenderedAtCorrectSize(unicode))
|
||||
emojiSupportCache.set(unicode, supported)
|
||||
}
|
||||
return supported
|
||||
}
|
Loading…
Reference in New Issue