Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,5 @@ We're hiring developers, technical support, and product managers all the time. C
# 🗒️ Credits

- Emoji provided graciously by [JoyPixels](https://www.joypixels.com).

<!-- Contribution practice by Nitish -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove practice comment from production README.

This HTML comment appears to be a test/practice addition that should not be included in the main README.

🧹 Proposed fix
 - Emoji provided graciously by [JoyPixels](https://www.joypixels.com).
-
-<!-- Contribution practice by Nitish -->
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Contribution practice by Nitish -->
- Emoji provided graciously by [JoyPixels](https://www.joypixels.com).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 124, Remove the stray HTML practice comment "<!--
Contribution practice by Nitish -->" from the README by deleting that comment
line and ensure no other test/practice HTML comments remain in the README;
update the file so only production content stays and commit the change.

20 changes: 10 additions & 10 deletions apps/meteor/app/emoji-emojione/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Updating emojione
# Updating emojis (now using emoji-toolkit)

## Generate new category map variable for emojipicker
Run
Run the generator script which now pulls metadata from `emoji-toolkit`

```
node --experimental-modules generateEmojiIndex.mjs
```
Comment on lines 6 to 8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specification to fenced code block.

The code block is missing a language identifier, which helps with syntax highlighting and accessibility.

📝 Proposed fix
-```
+```shell
 node --experimental-modules generateEmojiIndex.mjs
 ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
node --experimental-modules generateEmojiIndex.mjs
```
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 6-6: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/emoji-emojione/README.md` around lines 6 - 8, The fenced code
block showing the command to run generateEmojiIndex.mjs lacks a language tag;
update the block to include a shell/bash language identifier (e.g., replace ```
with ```shell or ```bash) so the line "node --experimental-modules
generateEmojiIndex.mjs" is syntax-highlighted and accessible.


## Generate new percentage sprite
Clone the repository https://github.com/Ranks/emojione/ and replace the file `assets/sprites/emojione.sprites.mustache` with the content
of [emojione.sprites.mustache](emojione.sprites.mustache), then run at `emojione` folder:
The script will download 64px PNGs from the JoyPixels CDN into `.emoji-cache`
and build updated sprite sheets under `public/packages/emojione/`.

```
grunt sprite
sass --sourcemap=none assets/sprites/emojione.sprites.scss sprites.css
```
## Sprite generation notes
The old manual `grunt`/`sass` workflow is no longer required. If you need to
rebuild sprites offline you can cache the PNG files yourself by running the
script once with internet access; subsequent runs will reuse the cached images.

And replace the file `sprites.css` at Rocket.Chat's `/packages/rocketchat-emoji-emojione/sprites.css`.
The generated CSS is written to `apps/meteor/app/emoji-emojione/client/emojione-sprites.css`.
103 changes: 70 additions & 33 deletions apps/meteor/app/emoji-emojione/lib/generateEmojiIndex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,47 @@
import fs from 'fs';
import nsg from 'node-sprite-generator';
import _ from 'underscore';
import gm from 'gm'; // lgtm[js/unused-local-variable]
import path from 'path';

const assetFolder = '../../../node_modules/emojione-assets';
const emojiJsonFile = `${assetFolder}/emoji.json`;
// helper to resolve paths relative to this script file
const scriptDir = path.dirname(new URL(import.meta.url).pathname);

@cubic-dev-ai cubic-dev-ai Bot Mar 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: new URL(import.meta.url).pathname yields URL-encoded paths and an extra leading slash on Windows, which can make scriptDir invalid. Use fileURLToPath(import.meta.url) before path.dirname to get a correct filesystem path across platforms.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/emoji-emojione/lib/generateEmojiIndex.mjs, line 12:

<comment>`new URL(import.meta.url).pathname` yields URL-encoded paths and an extra leading slash on Windows, which can make `scriptDir` invalid. Use `fileURLToPath(import.meta.url)` before `path.dirname` to get a correct filesystem path across platforms.</comment>

<file context>
@@ -6,31 +6,47 @@
-const assetFolder = '../../../node_modules/emojione-assets';
-const emojiJsonFile = `${assetFolder}/emoji.json`;
+// helper to resolve paths relative to this script file
+const scriptDir = path.dirname(new URL(import.meta.url).pathname);
+
+// Node 18+ has global fetch; if not available uncomment next line
</file context>
Fix with Cubic


// Node 18+ has global fetch; if not available uncomment next line
// import fetch from 'node-fetch';

// Resolve the path to emoji-json using Node's module resolution so the
// script can be run from any working directory. Using createRequire avoids
// experimental JSON import assertions and works reliably in ESM.
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const emojiJsonFile = require.resolve('emoji-toolkit/emoji.json');

// directory where we'll temporarily store downloaded PNGs when using CDN
// keep cache scoped to the script directory so that generated files don't
// clutter the workspace root when the script is invoked from elsewhere.
const tmpFolder = path.join(scriptDir, '.emoji-cache');

// ensure cache directory exists (for CDN downloads)
fs.mkdirSync(tmpFolder, { recursive: true });

const emojiJson = fs.readFileSync(emojiJsonFile, 'utf8');

generateEmojiPicker(emojiJson);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unhandled promise from async function call.

generateEmojiPicker is an async function but is called without await. Any errors thrown inside will result in an unhandled promise rejection.

🐛 Proposed fix
-generateEmojiPicker(emojiJson);
+generateEmojiPicker(emojiJson).catch((err) => {
+	console.error('Failed to generate emoji picker:', err);
+	process.exit(1);
+});

Or wrap in an async IIFE:

-generateEmojiPicker(emojiJson);
+(async () => {
+	await generateEmojiPicker(emojiJson);
+})();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generateEmojiPicker(emojiJson);
generateEmojiPicker(emojiJson).catch((err) => {
console.error('Failed to generate emoji picker:', err);
process.exit(1);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/emoji-emojione/lib/generateEmojiIndex.mjs` at line 34,
generateEmojiPicker is an async function but is invoked without awaiting or
handling its returned promise, risking unhandled rejections; update the call to
either await generateEmojiPicker(emojiJson) from an async context or wrap it in
a promise chain/generic handler (e.g., generateEmojiPicker(emojiJson).catch(err
=> processLogger.error(...))) or execute it inside an async IIFE so any thrown
errors are caught and logged — ensure you reference the generateEmojiPicker
function and handle/await its returned promise accordingly.


if (!fs.existsSync(emojiJsonFile)) {
console.error(`${emojiJsonFile} doesn't exist.`);
console.error("Maybe you need to run 'meteor npm install emojione-assets' or 'meteor npm install'?");
} else {
const emojiJson = fs.readFileSync(emojiJsonFile);
generateEmojiPicker(emojiJson);
}

function generateEmojiPicker(data) {
const emojiList = JSON.parse(data);
console.log(`${Object.keys(emojiList).length} emojis found.`);
async function generateEmojiPicker(data) {
const emojiList = JSON.parse(data);
console.log(`${Object.keys(emojiList).length} emojis found.`);

let toneList = [];
let emojisByCategory = {};
let toneList = [];
let emojisByCategory = {};

_.sortBy(Object.entries(emojiList), (a) => a[1].order).forEach(([code, emoji]) => {
if (emoji && emoji.shortname) {
const toneIndex = emoji.shortname.indexOf('_tone');
if (toneIndex !== -1) {
const tone = emoji.shortname.substr(1, toneIndex - 1);
// iterate in order field to keep categories sorted
_.sortBy(Object.entries(emojiList), (a) => a[1].order).forEach(([code, emoji]) => {
if (emoji && emoji.shortname) {
const toneIndex = emoji.shortname.indexOf('_tone');
if (toneIndex !== -1) {
const tone = emoji.shortname.replace(/:/g, '').split('_tone')[0];
if (!toneList.includes(tone)) {
toneList.push(tone);
}
Expand Down Expand Up @@ -90,11 +106,12 @@ function generateEmojiPicker(data) {

// emojisByCategory
output += `export const emojisByCategory = {\n`;
for (let category in emojisByCategory) {
for (const category of Object.keys(emojisByCategory)) {
output += `\t${category}: [\n`;

for (let emoji in emojisByCategory[category]) {
output += `\t\t'${emojiList[emojisByCategory[category][emoji]].shortname.replace(/:/g, '')}',\n`;
for (const code of emojisByCategory[category]) {
const short = emojiList[code].shortname.replace(/:/g, '');
output += `\t\t'${short}',\n`;
}

output += `\t],\n`;
Expand All @@ -113,25 +130,44 @@ function generateEmojiPicker(data) {

for (let category in emojisByCategory) {
let srcList = [];
let diversityList = [];
let diversityList = {};
const emojis = _.filter(emojiList, (x) => x.category === category);
const spritePath = `../../../public/packages/emojione/${category}-sprites.png`;

_.each(emojis, function (emoji) {
srcList.push(`${assetFolder}/png/64/${emoji.code_points.base}.png`);
// derive output paths relative to this script, not the cwd
const spritePath = path.resolve(scriptDir, '../../../public/packages/emojione', `${category}-sprites.png`);
const cssOutputPath = path.resolve(scriptDir, '../client', `${category}-sprites.css`);

for (const emoji of emojis) {
const code = emoji.code_points.base;
const localPath = `${tmpFolder}/${code}.png`;
if (!fs.existsSync(localPath)) {
const url = `https://cdn.jsdelivr.net/npm/emoji-toolkit@latest/assets/64/${code}.png`;
console.log(`fetching ${url}`);
const res = await fetch(url);
if (res.ok) {
const buf = await res.arrayBuffer();
fs.writeFileSync(localPath, Buffer.from(buf));
} else {
console.warn(`failed to download ${code} (${res.status})`);
}
}
srcList.push(localPath);
if (emoji.diversity) {
diversityList[emoji.code_points.base] = true;
diversityList[code] = true;
}
});
}
spriteCss += `@import './${category}-sprites.css';\n`;

// filter out any images that failed to download
srcList = srcList.filter((p) => fs.existsSync(p));
const templateFile = path.join(scriptDir, 'emojione.tpl');

nsg(
{
src: srcList,
spritePath: spritePath,
layout: 'packed',
stylesheet: 'emojione.tpl',
stylesheetPath: `../client/${category}-sprites.css`,
stylesheet: templateFile,
stylesheetPath: cssOutputPath,
compositor: 'gm',
layoutOptions: {
scaling: 1,
Expand Down Expand Up @@ -175,7 +211,8 @@ function generateEmojiPicker(data) {
image-rendering: optimizeQuality;
}
`;
fs.writeFileSync('../client/emojione-sprites.css', spriteCss, {
const emojioneCssPath = path.resolve(scriptDir, '../client/emojione-sprites.css');
fs.writeFileSync(emojioneCssPath, spriteCss, {
encoding: 'utf8',
flag: 'w',
});
Expand Down
Loading
Loading