/, mirroring the Shaer render.
export function emojiHtml(html, json) {
const map = emojiMap(json);
if (!html || !Object.keys(map).length) return html || '';
let out = '';
let i = 0;
let code = 0;
while (i < html.length) {
if (html[i] === '<') {
const close = html.indexOf('>', i);
if (close < 0) { out += html.slice(i); break; }
const raw = html.slice(i + 1, close);
const name = raw.replace(/^\//, '').split(/[\s/>]/)[0].toLowerCase();
if (name === 'code' || name === 'pre') code = Math.max(0, code + (raw[0] === '/' ? -1 : 1));
out += html.slice(i, close + 1); // copy the tag verbatim
i = close + 1;
} else {
const next = html.indexOf('<', i);
const end = next < 0 ? html.length : next;
const text = html.slice(i, end);
out += code > 0 ? text : substitute(text, map);
i = end;
}
}
return out;
}
// A plain-text display name with custom emojis → safe HTML. The name is HTML-
// escaped first; shortcode characters ([A-Za-z0-9_+-]) survive escaping, so the
// image substitution stays correct.
export function emojiName(text, json) {
const esc = escapeHtml(text);
const map = emojiMap(json);
if (!Object.keys(map).length) return esc;
return substitute(esc, map);
}
// The resolved quoted-post snapshot Klonkt stored (quote_json), or null.
export function parseQuote(json) {
try {
const q = json == null ? null : (typeof json === 'string' ? JSON.parse(json) : json);
return (q && typeof q === 'object' && q.url) ? q : null;
} catch { return null; }
}
export default { escapeHtml, emojiMap, emojiHtml, emojiName, parseQuote };