Render emoji shortcodes as icons in chat (#345) (#629)

Chat models often emit GitHub/Slack-style :shortcode: text (e.g. 😊,
🎤) instead of the actual emoji. The renderer only converted real
Unicode emoji to the monochrome line icons, so shortcodes rendered as literal
text.

Add a pure, browser-free shortcode->Unicode map (emojiShortcodes.js) and run it
inside svgifyEmoji ahead of the existing Unicode->SVG pass, skipping <code>/<pre>
so code stays literal. Covers ~430 common shortcodes plus common aliases
(+1/thumbsup, etc.).

Keep the conversion from touching anything it shouldn't:
* Scope it to chat. mdToHtml/svgifyEmoji take a { shortcodes } option (default
  on); document and email body rendering (compose, export, preview) pass it as
  false so author-typed :shortcode: text stays literal. The Unicode->SVG pass
  still runs there exactly as before.
* Only convert a :shortcode: that stands on its own. A word-boundary guard
  leaves embedded colon runs alone, so "1:100:2", "10:30:45", "16:9" and
  host:fire:port are never rewritten.

Tests: extend the node-driven unit test with the boundary/false-positive cases,
and fix the markdown-rendering test loader to resolve the new emojiShortcodes
import.
This commit is contained in:
Zeus-Deus
2026-06-05 02:28:42 +02:00
committed by GitHub
parent f9c81f3c8d
commit 85334e8f3d
6 changed files with 604 additions and 9 deletions
+6 -4
View File
@@ -2246,7 +2246,9 @@ import * as Modals from './modalManager.js';
// WYSIWYG body — use it verbatim. (Checking a leading '<' isn't enough: a
// rich body often starts with plain text, e.g. "Hi <b>there</b>".)
if (/<\/?(b|i|u|s|strong|em|del|strike|a|p|div|br|ul|ol|li|h[1-3]|blockquote|span|code|pre)\b[^>]*>/i.test(t)) return t;
try { return markdownModule.mdToHtml(text); }
// Email body: keep author-typed `:shortcode:` text literal. Issue #345
// (shortcode → emoji) is scoped to chat; do not rewrite colons in mail.
try { return markdownModule.mdToHtml(text, { shortcodes: false }); }
catch (_) {
const d = document.createElement('div'); d.textContent = text;
return d.innerHTML.replace(/\n/g, '<br>');
@@ -8386,7 +8388,7 @@ import * as Modals from './modalManager.js';
const text = textarea.value || '';
let body;
if (lang === 'markdown' && markdownModule?.mdToHtml) {
body = markdownModule.mdToHtml(text);
body = markdownModule.mdToHtml(text, { shortcodes: false }); // export: keep :shortcodes: literal
} else {
body = '<pre style="white-space:pre-wrap;font-size:12px;font-family:monospace;">' +
text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</pre>';
@@ -8417,7 +8419,7 @@ import * as Modals from './modalManager.js';
// Render content as HTML for PDF
let html;
if (lang === 'markdown' && markdownModule?.mdToHtml) {
html = markdownModule.mdToHtml(text);
html = markdownModule.mdToHtml(text, { shortcodes: false }); // export: keep :shortcodes: literal
} else {
html = '<pre style="white-space:pre-wrap;font-size:11px;font-family:monospace;color:#000;background:#fff;">' +
text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</pre>';
@@ -8547,7 +8549,7 @@ import * as Modals from './modalManager.js';
if (active) {
const md = textarea.value || '';
if (markdownModule && markdownModule.mdToHtml) {
preview.innerHTML = markdownModule.mdToHtml(md);
preview.innerHTML = markdownModule.mdToHtml(md, { shortcodes: false }); // doc preview: keep :shortcodes: literal
} else {
preview.innerHTML = md.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g, '<br>');
}