diff --git a/src/systems/integration/expressionSync.js b/src/systems/integration/expressionSync.js index acf7a60..a700687 100644 --- a/src/systems/integration/expressionSync.js +++ b/src/systems/integration/expressionSync.js @@ -15,6 +15,7 @@ import { removeSyncedExpressionPortrait } from '../../core/state.js'; import { saveChatData } from '../../core/persistence.js'; +import { isSafeImageSrc, normalizeImageSrc, resolveImageUrl } from '../../utils/imageUrls.js'; import { renderAlternatePresentCharacters } from '../ui/alternatePresentCharacters.js'; let expressionContainerObserver = null; @@ -33,20 +34,11 @@ function normalizeName(name) { } function normalizeExpressionSrc(src) { - return String(src || '').trim(); + return normalizeImageSrc(src); } function resolveExpressionUrl(src) { - const normalized = normalizeExpressionSrc(src); - if (!normalized) { - return null; - } - - try { - return new URL(normalized, window.location.href); - } catch { - return null; - } + return resolveImageUrl(src); } function isDocumentLikeUrl(src) { @@ -76,6 +68,10 @@ function isUsableExpressionSrc(src) { return false; } + if (!isSafeImageSrc(normalized)) { + return false; + } + return true; } diff --git a/src/systems/ui/alternatePresentCharacters.js b/src/systems/ui/alternatePresentCharacters.js index 34969ef..e0a28d0 100644 --- a/src/systems/ui/alternatePresentCharacters.js +++ b/src/systems/ui/alternatePresentCharacters.js @@ -1,5 +1,6 @@ import { extensionSettings } from '../../core/state.js'; import { i18n } from '../../core/i18n.js'; +import { getSafeImageSrc } from '../../utils/imageUrls.js'; import { getExpressionPortraitForCharacter } from '../integration/expressionSync.js'; import { getPresentCharactersTrackerData, @@ -34,15 +35,6 @@ function ensureAlternatePresentCharactersPanel() { return $panel; } -function escapeHtml(value) { - return String(value ?? '') - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - function hexToRgba(hex, opacity = 100) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); @@ -51,6 +43,44 @@ function hexToRgba(hex, opacity = 100) { return `rgba(${r}, ${g}, ${b}, ${a})`; } +function handlePortraitLoadError() { + this.style.opacity = '0.5'; + $(this).off('error', handlePortraitLoadError); +} + +function createAlternatePresentCharacterCard(character) { + const rawPortrait = (extensionSettings.syncExpressionsToPresentCharacters + ? getExpressionPortraitForCharacter(character.name) + : null) || resolvePresentCharacterPortrait(character.name); + const portrait = getSafeImageSrc(rawPortrait); + const name = String(character.name || ''); + + const $card = $('
') + .attr('data-character-name', name) + .attr('title', name); + + const $portrait = $(''); + const $image = $('