From b6a407c392ac2eb3370b2de38b380d09a922b1d9 Mon Sep 17 00:00:00 2001 From: M-ZubairAhmed Date: Mon, 5 Jun 2023 18:38:46 +0530 Subject: [PATCH] MM-52819 : "medical_symbol", "male_sign" and "female_sign" emojis are broken (#23538) --- webapp/channels/build/emoji/make_emojis.mjs | 83 ++++++++++++++---- webapp/channels/package.json | 5 +- .../channels/src/images/emoji/2640-fe0f.png | Bin 0 -> 2783 bytes .../channels/src/images/emoji/2642-fe0f.png | Bin 0 -> 2341 bytes .../channels/src/images/emoji/2695-fe0f.png | Bin 0 -> 4200 bytes webapp/package-lock.json | 42 ++++++--- 6 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 webapp/channels/src/images/emoji/2640-fe0f.png create mode 100644 webapp/channels/src/images/emoji/2642-fe0f.png create mode 100644 webapp/channels/src/images/emoji/2695-fe0f.png diff --git a/webapp/channels/build/emoji/make_emojis.mjs b/webapp/channels/build/emoji/make_emojis.mjs index 244ed0e462..b8ecb8e8b4 100755 --- a/webapp/channels/build/emoji/make_emojis.mjs +++ b/webapp/channels/build/emoji/make_emojis.mjs @@ -13,14 +13,13 @@ * npm run make-emojis -- --help */ -/* eslint-disable no-console */ - import path from 'node:path'; import * as fsPromise from 'node:fs/promises'; import * as fs from 'node:fs'; import * as url from 'node:url'; import yargs from 'yargs'; +import chalk from 'chalk'; import jsonData from 'emoji-datasource/emoji.json'; import jsonCategories from 'emoji-datasource/categories.json'; @@ -31,10 +30,6 @@ const EMOJI_SIZE_PADDED = EMOJI_SIZE + 2; // 1px per side const EMOJI_DEFAULT_SKIN = 'default'; const endResults = []; -const errorLogColor = '\x1b[31m%s\x1b[0m'; -const warnLogColor = '\x1b[33m%s\x1b[0m'; -const successLogColor = '\x1b[32m%s\x1b[0m'; - const currentDir = path.dirname(url.fileURLToPath(import.meta.url)); const rootDir = path.resolve(currentDir, '..', '..', '..', '..'); const serverRootDir = path.resolve(rootDir, 'server'); @@ -54,12 +49,48 @@ const argv = yargs(process.argv.slice(1)). const argsExcludedEmojiFile = argv['excluded-emoji-file']; +function log(level = '', text) { + if (level === 'info') { + // eslint-disable-next-line no-console + console.log(chalk.cyan(`[INFO]: ${text}`)); + } else if (level === 'warn') { + // eslint-disable-next-line no-console + console.log(chalk.yellow(`[WARN]: ${text}`)); + } else if (level === 'error') { + // eslint-disable-next-line no-console + console.error(chalk.red(`[ERRO]: ${text}`)); + } else if (level === 'success') { + // eslint-disable-next-line no-console + console.log(chalk.green(`[SUCC]: ${text}`)); + } else { + // eslint-disable-next-line no-console + console.log(text); + } +} + function writeToFileAndPrint(fileName, filePath, data) { const promise = fsPromise.writeFile(filePath, data, writeOptions); promise.then(() => { - console.log(successLogColor, `${fileName} generated successfully in ${filePath}\n`); + log('info', `"${fileName}" generated successfully in "${filePath}"`); + }).catch((err) => { + log('error', `Failed to generate "${fileName}": ${err}`); }); + + return promise; +} + +function copyFileAndPrint(source, destination, fileName, print = true) { + const promise = fsPromise.copyFile(source, destination); + + promise.then(() => { + if (print) { + log('info', `"${fileName}" copied successfully to "${destination}"`); + } + }).catch((err) => { + log('error', `Failed to copy "${fileName}": ${err}`); + }); + return promise; } @@ -73,24 +104,36 @@ const emojiImagesDir = path.resolve(webappRootDir, 'channels', 'src', 'images', const readDirPromise = fsPromise.readdir(emojiDataSourceDir); endResults.push(readDirPromise); readDirPromise.then((images) => { - console.log(`Copying ${images.length} emoji images, this might take a while\n`); - for (const imageFile of images) { - endResults.push( - fsPromise.copyFile(path.join(emojiDataSourceDir, imageFile), path.join(emojiImagesDir, imageFile)). - catch((err) => console.log(errorLogColor, `[ERROR] Failed to copy ${imageFile}: ${err}`))); + endResults.push(copyFileAndPrint(path.join(emojiDataSourceDir, imageFile), path.join(emojiImagesDir, imageFile), imageFile, false)); } }); +// Missing emojis from Apple emoji set ["medical_symbol", "male_sign", and "female_sign"] +// @see https://github.com/iamcal/emoji-data#image-sources +const missingEmojis = ['2640-fe0f', '2642-fe0f', '2695-fe0f']; + +// Copy the missing from google emoji set +const missingEmojiDataSourceDir = path.resolve(webappRootDir, `node_modules/emoji-datasource-google/img/google/${EMOJI_SIZE}/`); +const readMissingDirPromise = fsPromise.readdir(missingEmojiDataSourceDir); +endResults.push(readMissingDirPromise); +readMissingDirPromise.then(() => { + for (const missingEmoji of missingEmojis) { + endResults.push( + copyFileAndPrint(path.join(missingEmojiDataSourceDir, `${missingEmoji}.png`), path.join(emojiImagesDir, `${missingEmoji}.png`), `Missed ${missingEmoji}`)); + } +}); + +// Copy mattermost emoji image const webappImagesDir = path.resolve(webappRootDir, 'channels', 'src', 'images'); -fsPromise.copyFile(path.resolve(webappImagesDir, 'icon64x64.png'), path.resolve(webappImagesDir, 'emoji/mattermost.png')); +endResults.push(copyFileAndPrint(path.resolve(webappImagesDir, 'icon64x64.png'), path.resolve(webappImagesDir, 'emoji/mattermost.png'), 'mattermost-emoji')); const sheetSource = path.resolve(webappRootDir, `node_modules/emoji-datasource-apple/img/apple/sheets/${EMOJI_SIZE}.png`); const sheetAbsoluteFile = path.resolve(webappRootDir, 'channels', 'src', 'images/emoji-sheets/apple-sheet.png'); const sheetFile = 'images/emoji-sheets/apple-sheet.png'; // Copy sheet image -fsPromise.copyFile(sheetSource, sheetAbsoluteFile).catch((err) => console.log(errorLogColor, `[ERROR] Failed to copy sheet file: ${err}`)); +endResults.push(copyFileAndPrint(sheetSource, sheetAbsoluteFile, 'emoji-sheet')); // we'll load it as a two dimensional array so we can generate a Map out of it const emojiIndicesByAlias = []; @@ -129,7 +172,7 @@ if (argsExcludedEmojiFile) { fs.readFileSync(path.normalize(argsExcludedEmojiFile), 'utf-8').split(/\r?\n/).forEach((line) => { excludedEmoji.push(line); }); - console.log(warnLogColor, `\n[WARNING] The following emoji(s) will be excluded from the webapp: \n${excludedEmoji}\n`); + log('warn', `The following emoji(s) will be excluded from the webapp: \n${excludedEmoji}\n`); } // Remove unwanted emoji @@ -454,11 +497,13 @@ ${cssEmojis.join('\n')} const emojispriteDir = path.resolve(webappRootDir, 'channels', 'src', 'sass', 'components', '_emojisprite.scss'); endResults.push(writeToFileAndPrint('_emojisprite.scss', emojispriteDir, cssRules)); +log('', '\n'); +log('info', 'Running "make-emojis" script...'); Promise.all(endResults).then(() => { - console.log(warnLogColor, '\n[WARNING] Remember to npm run \'i18n-extract\' as categories might have changed.'); - console.log(warnLogColor, '[WARNING] Remember to run `gofmt -w ./server/public/model/emoji_data.go` from the root of the repository.'); - console.log(successLogColor, '\n[SUCCESS] make-emojis script completed successfully.'); + log('warn', 'Remember to npm run "i18n-extract" as categories might have changed.'); + log('warn', 'Remember to run "gofmt -w ./server/public/model/emoji_data.go" from the root of the repository.'); + log('success', 'make-emojis script completed successfully.'); }).catch((err) => { control.abort(); // cancel any other file writing - console.log(errorLogColor, `[ERROR] There was an error while running make-emojis script: ${err}`); + log('error', `There was an error while running make-emojis script: ${err}`); }); diff --git a/webapp/channels/package.json b/webapp/channels/package.json index 17786e8442..6fe42fd9f5 100644 --- a/webapp/channels/package.json +++ b/webapp/channels/package.json @@ -34,8 +34,6 @@ "css-vars-ponyfill": "2.4.8", "date-fns": "2.29.3", "dynamic-virtualized-list": "github:mattermost/dynamic-virtualized-list#1c330717d6778315a40e70137ace38247c6a1d0f", - "emoji-datasource": "6.1.1", - "emoji-datasource-apple": "6.0.1", "emoji-regex": "10.2.1", "exif2css": "1.3.0", "fast-deep-equal": "3.1.3", @@ -157,6 +155,9 @@ "bundle-loader": "0.2.0", "copy-webpack-plugin": "11.0.0", "dotenv-webpack": "8.0.1", + "emoji-datasource": "6.1.1", + "emoji-datasource-apple": "6.1.1", + "emoji-datasource-google": "6.1.1", "enzyme": "3.11.0", "enzyme-adapter-react-17-updated": "1.0.2", "enzyme-to-json": "3.6.2", diff --git a/webapp/channels/src/images/emoji/2640-fe0f.png b/webapp/channels/src/images/emoji/2640-fe0f.png new file mode 100644 index 0000000000000000000000000000000000000000..2c134435b382c2051780ba4c9ee16969f3851d13 GIT binary patch literal 2783 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 literal 0 HcmV?d00001 diff --git a/webapp/channels/src/images/emoji/2642-fe0f.png b/webapp/channels/src/images/emoji/2642-fe0f.png new file mode 100644 index 0000000000000000000000000000000000000000..e465b825d6a052ba0d0bd9e5d44eed3eb02b1f4f GIT binary patch literal 2341 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 LNkvXXu0mjfxu0V$ literal 0 HcmV?d00001 diff --git a/webapp/channels/src/images/emoji/2695-fe0f.png b/webapp/channels/src/images/emoji/2695-fe0f.png new file mode 100644 index 0000000000000000000000000000000000000000..4254a42d3ce586abd4805bdb16b141acbf523b7b GIT binary patch literal 4200 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