diff --git a/bin/build-assets.js b/bin/build-assets.js index f493c724..4c155a4a 100644 --- a/bin/build-assets.js +++ b/bin/build-assets.js @@ -7,6 +7,7 @@ import { getIntl, trimWhitespace } from './getIntl.js' const __dirname = path.dirname(new URL(import.meta.url).pathname) const readFile = promisify(fs.readFile) const writeFile = promisify(fs.writeFile) +const copyFile = promisify(fs.copyFile) // Try 'en-US' first, then 'en' if that doesn't exist const PREFERRED_LOCALES = [LOCALE, LOCALE.split('-')[0]] @@ -63,10 +64,20 @@ async function buildManifestJson () { ) } +async function buildFlagEmojiFile () { + await copyFile(path.resolve( + __dirname, + '../node_modules/country-flag-emoji-polyfill/dist/TwemojiCountryFlags.woff2' + ), path.resolve( + __dirname, '../static/TwemojiCountryFlags.woff2' + )) +} + async function main () { await Promise.all([ buildEmojiI18nFile(), - buildManifestJson() + buildManifestJson(), + buildFlagEmojiFile() ]) } diff --git a/bin/build-vercel-json.js b/bin/build-vercel-json.js index 02aeeb69..d7ecbcae 100644 --- a/bin/build-vercel-json.js +++ b/bin/build-vercel-json.js @@ -51,7 +51,7 @@ const JSON_TEMPLATE = { } }, { - src: '^/.*\\.(png|css|json|svg|jpe?g|map|txt|gz|webapp)$', + src: '^/.*\\.(png|css|json|svg|jpe?g|map|txt|gz|webapp|woff|woff2)$', headers: { 'cache-control': 'public,max-age=3600' } diff --git a/package.json b/package.json index 62774e27..cdd31f13 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "chokidar": "^3.5.3", "circular-dependency-plugin": "^5.2.2", "compression": "^1.7.4", + "country-flag-emoji-polyfill": "^0.1.1", "cross-env": "^7.0.3", "css-dedoupe": "^0.1.1", "emoji-picker-element": "^1.11.1", diff --git a/src/routes/_components/compose/ComposeAutosuggestionList.html b/src/routes/_components/compose/ComposeAutosuggestionList.html index 71f72a83..2c582547 100644 --- a/src/routes/_components/compose/ComposeAutosuggestionList.html +++ b/src/routes/_components/compose/ComposeAutosuggestionList.html @@ -98,7 +98,7 @@ fill: var(--deemphasized-text-color); } .compose-autosuggest-list-item-native-emoji { - font-family: PinaforeEmoji; + font-family: CountryFlagEmojiPolyfill, PinaforeEmoji; font-size: 32px; line-height: 1; display: flex; diff --git a/src/routes/_store/observers/countryFlagEmojiPolyfill.js b/src/routes/_store/observers/countryFlagEmojiPolyfill.js new file mode 100644 index 00000000..fe0075db --- /dev/null +++ b/src/routes/_store/observers/countryFlagEmojiPolyfill.js @@ -0,0 +1,30 @@ +import { polyfillCountryFlagEmojis } from 'country-flag-emoji-polyfill' +import { store } from '../store.js' + +let polyfilled = false + +const COUNTRY_FLAG_FONT_URL = '/TwemojiCountryFlags.woff2' + +export function countryFlagEmojiPolyfill () { + if (!polyfilled) { + polyfilled = true + const numStylesBefore = document.head.querySelectorAll('style').length + polyfillCountryFlagEmojis('Twemoji Mozilla', COUNTRY_FLAG_FONT_URL) + const numStylesAfter = document.head.querySelectorAll('style').length + // if a style was added, then the polyfill was activated + const polyfillActivated = numStylesAfter !== numStylesBefore + if (polyfillActivated) { + const style = document.createElement('style') + style.textContent = ` + @font-face { + font-family: CountryFlagEmojiPolyfill; + src: url(${JSON.stringify(COUNTRY_FLAG_FONT_URL)}); + } + ` + document.head.appendChild(style) + // "Twemoji Mozilla" is for emoji-picker-element, since it lists that font first. + // "CountryFlagEmojiPolyfill" is for us so we can set it before everything else in our own font family lists. + } + store.set({ polyfilledCountryFlagEmoji: polyfillActivated }) + } +} diff --git a/src/routes/_store/observers/loggedInObservers.js b/src/routes/_store/observers/loggedInObservers.js index e18ebc3b..2243bc4f 100644 --- a/src/routes/_store/observers/loggedInObservers.js +++ b/src/routes/_store/observers/loggedInObservers.js @@ -9,6 +9,7 @@ import { cleanup } from './cleanup.js' import { wordFilterObservers } from './wordFilterObservers.js' import { showComposeDialogObservers } from './showComposeDialogObservers.js' import { badgeObservers } from './badgeObservers.js' +import { countryFlagEmojiPolyfill } from './countryFlagEmojiPolyfill.js' // These observers can be lazy-loaded when the user is actually logged in. // Prevents circular dependencies and reduces the size of main.js @@ -24,4 +25,5 @@ export function loggedInObservers () { showComposeDialogObservers() badgeObservers() cleanup() + countryFlagEmojiPolyfill() } diff --git a/src/routes/_utils/testEmojiSupported.js b/src/routes/_utils/testEmojiSupported.js index 12f70b69..0edd7c0e 100644 --- a/src/routes/_utils/testEmojiSupported.js +++ b/src/routes/_utils/testEmojiSupported.js @@ -1,8 +1,280 @@ import { isEmojiSupported, setCacheHandler } from 'is-emoji-supported' import { QuickLRU } from '../_thirdparty/quick-lru/quick-lru.js' +import { store } from '../_store/store.js' // avoid recomputing emoji support over and over again // use our own LRU since the built-in one grows forever, which is a small memory leak, but still setCacheHandler(new QuickLRU({ maxSize: 500 })) -export const testEmojiSupported = isEmojiSupported +const COUNTRY_FLAG_EMOJI = new Set( + [ + '🇦🇨', + '🇦🇩', + '🇦🇪', + '🇦🇫', + '🇦🇬', + '🇦🇮', + '🇦🇱', + '🇦🇲', + '🇦🇴', + '🇦🇶', + '🇦🇷', + '🇦🇸', + '🇦🇹', + '🇦🇺', + '🇦🇼', + '🇦🇽', + '🇦🇿', + '🇧🇦', + '🇧🇧', + '🇧🇩', + '🇧🇪', + '🇧🇫', + '🇧🇬', + '🇧🇭', + '🇧🇮', + '🇧🇯', + '🇧🇱', + '🇧🇲', + '🇧🇳', + '🇧🇴', + '🇧🇶', + '🇧🇷', + '🇧🇸', + '🇧🇹', + '🇧🇻', + '🇧🇼', + '🇧🇾', + '🇧🇿', + '🇨🇦', + '🇨🇨', + '🇨🇩', + '🇨🇫', + '🇨🇬', + '🇨🇭', + '🇨🇮', + '🇨🇰', + '🇨🇱', + '🇨🇲', + '🇨🇳', + '🇨🇴', + '🇨🇵', + '🇨🇷', + '🇨🇺', + '🇨🇻', + '🇨🇼', + '🇨🇽', + '🇨🇾', + '🇨🇿', + '🇩🇪', + '🇩🇬', + '🇩🇯', + '🇩🇰', + '🇩🇲', + '🇩🇴', + '🇩🇿', + '🇪🇦', + '🇪🇨', + '🇪🇪', + '🇪🇬', + '🇪🇭', + '🇪🇷', + '🇪🇸', + '🇪🇹', + '🇪🇺', + '🇫🇮', + '🇫🇯', + '🇫🇰', + '🇫🇲', + '🇫🇴', + '🇫🇷', + '🇬🇦', + '🇬🇧', + '🇬🇩', + '🇬🇪', + '🇬🇫', + '🇬🇬', + '🇬🇭', + '🇬🇮', + '🇬🇱', + '🇬🇲', + '🇬🇳', + '🇬🇵', + '🇬🇶', + '🇬🇷', + '🇬🇸', + '🇬🇹', + '🇬🇺', + '🇬🇼', + '🇬🇾', + '🇭🇰', + '🇭🇲', + '🇭🇳', + '🇭🇷', + '🇭🇹', + '🇭🇺', + '🇮🇨', + '🇮🇩', + '🇮🇪', + '🇮🇱', + '🇮🇲', + '🇮🇳', + '🇮🇴', + '🇮🇶', + '🇮🇷', + '🇮🇸', + '🇮🇹', + '🇯🇪', + '🇯🇲', + '🇯🇴', + '🇯🇵', + '🇰🇪', + '🇰🇬', + '🇰🇭', + '🇰🇮', + '🇰🇲', + '🇰🇳', + '🇰🇵', + '🇰🇷', + '🇰🇼', + '🇰🇾', + '🇰🇿', + '🇱🇦', + '🇱🇧', + '🇱🇨', + '🇱🇮', + '🇱🇰', + '🇱🇷', + '🇱🇸', + '🇱🇹', + '🇱🇺', + '🇱🇻', + '🇱🇾', + '🇲🇦', + '🇲🇨', + '🇲🇩', + '🇲🇪', + '🇲🇫', + '🇲🇬', + '🇲🇭', + '🇲🇰', + '🇲🇱', + '🇲🇲', + '🇲🇳', + '🇲🇴', + '🇲🇵', + '🇲🇶', + '🇲🇷', + '🇲🇸', + '🇲🇹', + '🇲🇺', + '🇲🇻', + '🇲🇼', + '🇲🇽', + '🇲🇾', + '🇲🇿', + '🇳🇦', + '🇳🇨', + '🇳🇪', + '🇳🇫', + '🇳🇬', + '🇳🇮', + '🇳🇱', + '🇳🇴', + '🇳🇵', + '🇳🇷', + '🇳🇺', + '🇳🇿', + '🇴🇲', + '🇵🇦', + '🇵🇪', + '🇵🇫', + '🇵🇬', + '🇵🇭', + '🇵🇰', + '🇵🇱', + '🇵🇲', + '🇵🇳', + '🇵🇷', + '🇵🇸', + '🇵🇹', + '🇵🇼', + '🇵🇾', + '🇶🇦', + '🇷🇪', + '🇷🇴', + '🇷🇸', + '🇷🇺', + '🇷🇼', + '🇸🇦', + '🇸🇧', + '🇸🇨', + '🇸🇩', + '🇸🇪', + '🇸🇬', + '🇸🇭', + '🇸🇮', + '🇸🇯', + '🇸🇰', + '🇸🇱', + '🇸🇲', + '🇸🇳', + '🇸🇴', + '🇸🇷', + '🇸🇸', + '🇸🇹', + '🇸🇻', + '🇸🇽', + '🇸🇾', + '🇸🇿', + '🇹🇦', + '🇹🇨', + '🇹🇩', + '🇹🇫', + '🇹🇬', + '🇹🇭', + '🇹🇯', + '🇹🇰', + '🇹🇱', + '🇹🇲', + '🇹🇳', + '🇹🇴', + '🇹🇷', + '🇹🇹', + '🇹🇻', + '🇹🇼', + '🇹🇿', + '🇺🇦', + '🇺🇬', + '🇺🇲', + '🇺🇳', + '🇺🇸', + '🇺🇾', + '🇺🇿', + '🇻🇦', + '🇻🇨', + '🇻🇪', + '🇻🇬', + '🇻🇮', + '🇻🇳', + '🇻🇺', + '🇼🇫', + '🇼🇸', + '🇽🇰', + '🇾🇪', + '🇾🇹', + '🇿🇦', + '🇿🇲', + '🇿🇼', + '🏴󠁧󠁢󠁥󠁮󠁧󠁿', + '🏴󠁧󠁢󠁳󠁣󠁴󠁿', + '🏴󠁧󠁢󠁷󠁬󠁳󠁿' + ] +) + +export function testEmojiSupported (unicode) { + if (store.get().polyfilledCountryFlagEmoji && COUNTRY_FLAG_EMOJI.has(unicode)) { + return true // just assume it's supported; is-emoji-supported doesn't work in this case + } + return isEmojiSupported(unicode) +} diff --git a/src/scss/global.scss b/src/scss/global.scss index 9e14d2dd..94981fab 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -97,10 +97,6 @@ input, textarea { color: inherit; } -textarea { - font-family: system-ui, -apple-system, PinaforeRegular, sans-serif, PinaforeEmoji; -} - button, .button { font-size: 1.2em; background: var(--button-bg); @@ -164,7 +160,7 @@ input:required, input:invalid { } textarea { - font-family: inherit; + font-family: CountryFlagEmojiPolyfill, system-ui, -apple-system, PinaforeRegular, sans-serif, PinaforeEmoji; font-size: inherit; box-sizing: border-box; } @@ -208,5 +204,5 @@ textarea { } .inline-emoji { - font-family: PinaforeEmoji, sans-serif; + font-family: CountryFlagEmojiPolyfill, PinaforeEmoji, sans-serif; } diff --git a/src/service-worker.js b/src/service-worker.js index e9fd42b8..26f36e95 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -22,6 +22,10 @@ const ON_DEMAND_CACHE = [ { regex: /\$polyfill\$/, cache: WEBPACK_ASSETS + }, + { + regex: /TwemojiCountryFlags\.woff2/, + cache: ASSETS } ] @@ -34,6 +38,7 @@ const assets = __assets__ .filter(filename => !filename.endsWith('.webapp')) // KaiOS manifest .filter(filename => !/emoji-.*?\.json$/.test(filename)) // useless to cache; it already goes in IndexedDB .filter(filename => !/screenshot-.*?\.png/.test(filename)) // only used during PWA installation, don't bother caching + .filter(filename => !/TwemojiCountryFlags\.woff2/.test(filename)) // cache on-demand // `shell` is an array of all the files generated by webpack // also contains '/index.html' for some reason diff --git a/static/TwemojiCountryFlags.woff2 b/static/TwemojiCountryFlags.woff2 new file mode 100644 index 00000000..d93f2164 Binary files /dev/null and b/static/TwemojiCountryFlags.woff2 differ diff --git a/vercel.json b/vercel.json index dbba08d6..221a7a66 100644 --- a/vercel.json +++ b/vercel.json @@ -36,7 +36,7 @@ } }, { - "src": "^/.*\\.(png|css|json|svg|jpe?g|map|txt|gz|webapp)$", + "src": "^/.*\\.(png|css|json|svg|jpe?g|map|txt|gz|webapp|woff|woff2)$", "headers": { "cache-control": "public,max-age=3600" } diff --git a/yarn.lock b/yarn.lock index e2364ef4..2022515d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2415,6 +2415,13 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +country-flag-emoji-polyfill@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/country-flag-emoji-polyfill/-/country-flag-emoji-polyfill-0.1.1.tgz#1fab7b364c2631f7e7949fbd98659552f401596e" + integrity sha512-H4iB8BAnlcLxE9mxVXm9vY28zKtqjEYRCO+vz1x4u6IsmomHQd/EQfR/Py0YmxeVYkBAOUPJBcY3KJPd+BQe6Q== + dependencies: + is-emoji-supported "^0.0.5" + cross-env@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"