From 235c0da38d5614de7705cf8baccd54d966839a50 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 6 Feb 2026 14:12:04 -0600 Subject: [PATCH] Update emoji system to native Unicode rendering --- package-lock.json | 217 +++++++++++------- package.json | 2 +- .../dataexport/GET-export_inbox.html.test.js | 4 +- test/common/libs/habiticaMarkdown.test.js | 67 ++++++ website/client/package.json | 2 +- 5 files changed, 209 insertions(+), 83 deletions(-) create mode 100644 test/common/libs/habiticaMarkdown.test.js diff --git a/package-lock.json b/package-lock.json index edeeae8b38..1e4be340b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "gulp-filter": "^7.0.0", "gulp-imagemin": "^7.1.0", "gulp.spritesmith": "^6.13.0", - "habitica-markdown": "^3.0.0", + "habitica-markdown": "git+https://github.com/HabitRPG/habitica-markdown.git#fiz/emojis-update", "heapdump": "^0.3.15", "helmet": "^4.6.0", "in-app-purchase": "^1.11.3", @@ -105,6 +105,9 @@ "npm": "^10" } }, + "../habitica-markdown/habitica-markdown": { + "extraneous": true + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -4167,6 +4170,12 @@ "node": ">=14.0.0" } }, + "node_modules/apidoc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/apidoc/node_modules/bootstrap": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", @@ -4202,6 +4211,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/apidoc/node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/apidoc/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4213,6 +4231,22 @@ "node": ">=10" } }, + "node_modules/apidoc/node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, "node_modules/apidoc/node_modules/nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", @@ -12497,50 +12531,14 @@ } }, "node_modules/habitica-markdown": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-3.0.0.tgz", - "integrity": "sha512-rw1LJ5Vsjx8sfjNa4e2wFuZf5eqqyb5/kfZXPxqfMMgJCCgIhWStDqY3nIclnpGWpemlKd+qbdh2rLiLgm9kng==", + "version": "4.0.0", + "resolved": "git+ssh://git@github.com/HabitRPG/habitica-markdown.git#ff94f35c50c9660e48b08f4f294d9150b1ae2643", + "license": "GPL-3.0", "dependencies": { - "habitica-markdown-emoji": "1.2.4", - "markdown-it": "10.0.0", - "markdown-it-link-attributes": "3.0.0", - "markdown-it-linkify-images": "^1.1.1" - } - }, - "node_modules/habitica-markdown-emoji": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/habitica-markdown-emoji/-/habitica-markdown-emoji-1.2.4.tgz", - "integrity": "sha512-UV0AxpDToldFQULuhTxC1y4sdNTApaIOh7ZuV/92HCPmCGkv3DAlHtYE67OmCqLVfs26HWAGVJaU3+OEnW3gjg==", - "dependencies": { - "markdown-it-emoji": "^1.1.1" - } - }, - "node_modules/habitica-markdown/node_modules/entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" - }, - "node_modules/habitica-markdown/node_modules/linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/habitica-markdown/node_modules/markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dependencies": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "^14.0.0", + "markdown-it-emoji": "^2.0.2", + "markdown-it-link-attributes": "^4.0.1", + "markdown-it-linkify-images": "^3.0.0" } }, "node_modules/handlebars": { @@ -14552,13 +14550,20 @@ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, + "node_modules/linkify-it/node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -14906,59 +14911,79 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it-emoji": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", - "integrity": "sha512-QCz3Hkd+r5gDYtS2xsFXmBYrgw6KuWcJZLCEkdfAuwzZbShCmCfta+hwAMq4NX/4xPzkSHduMKgMkkPUJxSXNg==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==", + "license": "MIT" }, "node_modules/markdown-it-link-attributes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-3.0.0.tgz", - "integrity": "sha512-B34ySxVeo6MuEGSPCWyIYryuXINOvngNZL87Mp7YYfKIf6DcD837+lXA8mo6EBbauKsnGz22ZH0zsbOiQRWTNg==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz", + "integrity": "sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==", + "license": "MIT" }, "node_modules/markdown-it-linkify-images": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-1.1.1.tgz", - "integrity": "sha512-1IEmAaAjIgAwY+tZI0sxDXdy9QKHutj5cN0lH2JBiSZt+2NYKrWRJj0cloQW3OFIfP2MLFA1E+6OLJhXPiLgNw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-3.0.0.tgz", + "integrity": "sha512-Vs5yGJa5MWjFgytzgtn8c1U6RcStj3FZKhhx459U8dYbEE5FTWZ6mMRkYMiDlkFO0j4VCsQT1LT557bY0ETgtg==", + "license": "MIT", "dependencies": { - "markdown-it": "^8.4.2" + "markdown-it": "^13.0.1" } }, + "node_modules/markdown-it-linkify-images/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/markdown-it-linkify-images/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/markdown-it-linkify-images/node_modules/linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "license": "MIT", "dependencies": { "uc.micro": "^1.0.1" } }, "node_modules/markdown-it-linkify-images/node_modules/markdown-it": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", - "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "entities": "~1.1.1", - "linkify-it": "^2.0.0", + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" }, @@ -14969,7 +14994,32 @@ "node_modules/markdown-it/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-it/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/markdown-it/node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" }, "node_modules/matchdep": { "version": "2.0.0", @@ -18049,6 +18099,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", diff --git a/package.json b/package.json index 68074286b4..b9483383d2 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "gulp-filter": "^7.0.0", "gulp-imagemin": "^7.1.0", "gulp.spritesmith": "^6.13.0", - "habitica-markdown": "^3.0.0", + "habitica-markdown": "git+https://github.com/HabitRPG/habitica-markdown.git#fiz/emojis-update", "heapdump": "^0.3.15", "helmet": "^4.6.0", "in-app-purchase": "^1.11.3", diff --git a/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js b/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js index b9f9b96fff..d153828688 100644 --- a/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js +++ b/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js @@ -38,7 +38,7 @@ describe('GET /export/inbox.html', () => { it('renders the markdown messages as html', async () => { const res = await user.get('/export/inbox.html'); - expect(res).to.include('img class="habitica-emoji"'); + expect(res).to.include('😄'); expect(res).to.include('

Hello!

'); expect(res).to.include('
  • list 1
  • '); }); @@ -46,7 +46,7 @@ describe('GET /export/inbox.html', () => { it('sorts messages from newest to oldest', async () => { const res = await user.get('/export/inbox.html'); - const emojiPosition = res.indexOf('img class="habitica-emoji"'); + const emojiPosition = res.indexOf('😄'); const headingPosition = res.indexOf('

    Hello!

    '); const listPosition = res.indexOf('
  • list 1
  • '); diff --git a/test/common/libs/habiticaMarkdown.test.js b/test/common/libs/habiticaMarkdown.test.js new file mode 100644 index 0000000000..af0527b0ea --- /dev/null +++ b/test/common/libs/habiticaMarkdown.test.js @@ -0,0 +1,67 @@ +import md from 'habitica-markdown'; + +describe('habiticaMarkdown emoji plugin', () => { + it('renders standard emoji as Unicode', () => { + const result = md.render(':smile:'); + expect(result).to.include('😄'); + expect(result).not.to.include('img'); + }); + + it('renders thumbsup emoji as Unicode', () => { + const result = md.render(':thumbsup:'); + expect(result).to.include('👍'); + }); + + it('renders +1 emoji as Unicode', () => { + const result = md.render(':+1:'); + expect(result).to.include('👍'); + }); + + it('renders melior as an img tag', () => { + const result = md.render(':melior:'); + expect(result).to.include(' { + const result = md.render('[:smile: link](http://example.com)'); + expect(result).to.include(':smile: link'); + expect(result).not.to.include('😄'); + }); + + it('converts emoji outside of links normally', () => { + const result = md.render(':smile: [link](http://example.com)'); + expect(result).to.include('😄'); + expect(result).to.include('link'); + }); + + it('leaves removed custom emoji (bowtie) as literal text', () => { + const result = md.render(':bowtie:'); + expect(result).to.include(':bowtie:'); + expect(result).not.to.include('img'); + }); + + it('leaves unknown shortcodes as literal text', () => { + const result = md.render(':nonexistent_emoji_xyz:'); + expect(result).to.include(':nonexistent_emoji_xyz:'); + }); + + it('renders new emoji not in the old dataset', () => { + const result = md.render(':yawning_face:'); + expect(result).to.include('🥱'); + }); + + it('supports unsafeHTMLRender', () => { + const result = md.unsafeHTMLRender('bold :smile:'); + expect(result).to.include('bold'); + expect(result).to.include('😄'); + }); + + it('supports renderWithMentions', () => { + const result = md.renderWithMentions(':smile: @testuser', { userName: 'testuser' }); + expect(result).to.include('😄'); + expect(result).to.include('at-text'); + expect(result).to.include('at-highlight'); + }); +}); diff --git a/website/client/package.json b/website/client/package.json index 1cff12c077..a44fd19d7f 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -28,7 +28,7 @@ "eslint-config-habitrpg": "6.2.0", "eslint-plugin-mocha": "5.3.0", "eslint-plugin-vue": "7.20.0", - "habitica-markdown": "^3.0.0", + "habitica-markdown": "git+https://github.com/HabitRPG/habitica-markdown.git#fiz/emojis-update", "hellojs": "^1.20.0", "intro.js": "^7.2.0", "jquery": "^3.7.1",