Changeset 0.23.0 (#153)

This commit is contained in:
2026-03-05 22:40:26 +00:00
parent 40d9834f64
commit 2fc620e1ac
62 changed files with 6214 additions and 362 deletions

View File

@@ -44,9 +44,71 @@ function formatMessage(content) {
html = html.replace(`THINK_PLACEHOLDER_${b.id}`, thinkHTML);
}
// @mention highlighting (v0.23.0): style @Name references as pills
html = _highlightMentions(html);
return html;
}
/**
* Highlight @mentions in rendered HTML as styled pills.
* Matches @Name patterns against the channel model roster.
* Only highlights inside text nodes (not inside code/pre/a tags).
*/
function _highlightMentions(html) {
if (typeof ChannelModels === 'undefined') return html;
const roster = ChannelModels.getRoster();
if (!roster || roster.length < 2) return html;
// Build patterns from handles (primary) and display names (fallback)
const patterns = [];
const seen = new Set();
for (const m of roster) {
// Handle pattern (preferred)
if (m.handle) {
const escaped = m.handle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
if (!seen.has(escaped)) {
patterns.push(new RegExp('(@' + escaped + ')(?=[\\s.,!?;:)\\]}<]|$)', 'gi'));
seen.add(escaped);
}
}
// Display name pattern (hyphenated form)
if (m.display_name) {
const hyphenated = m.display_name.replace(/\s+/g, '-');
const escaped = hyphenated.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
if (!seen.has(escaped.toLowerCase())) {
const flexible = escaped.replace(/[-]+/g, '[\\s-]+');
patterns.push(new RegExp('(@' + flexible + ')(?=[\\s.,!?;:)\\]}<]|$)', 'gi'));
seen.add(escaped.toLowerCase());
}
}
}
// @all
patterns.push(new RegExp('(@all)(?=[\\s.,!?;:)\\]}<]|$)', 'gi'));
// Don't highlight inside <code>, <pre>, <a> tags
const parts = html.split(/(<[^>]+>)/);
let inCode = false;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (part.startsWith('<')) {
const tag = part.toLowerCase();
if (tag.startsWith('<code') || tag.startsWith('<pre') || tag.startsWith('<a ')) inCode = true;
if (tag.startsWith('</code') || tag.startsWith('</pre') || tag.startsWith('</a')) inCode = false;
continue;
}
if (inCode) continue;
let text = part;
for (const re of patterns) {
text = text.replace(re, '<span class="mention-pill">$1</span>');
}
parts[i] = text;
}
return parts.join('');
}
function _formatMarked(text) {
// ── Unwrap outer ```markdown wrappers ────────────────────────
// LLMs frequently wrap entire responses in ```markdown ... ``` which is