polish(#3220): esc() download label + scrub orphaned media_open i18n keys
Opus iter2 SHOULD-FIX (both non-blocking, applied for cleanliness): - esc() the media_download title/aria-label so a future translator's quote can't break out of the attribute (defense-in-depth). - Remove the now-dead media_open key from all 11 locales + the ui.js fallback dict (the Open button was dropped in the clean-image redesign).
This commit is contained in:
parent
8ce02caf11
commit
031a3ce2e9
2 changed files with 3 additions and 13 deletions
|
|
@ -1297,7 +1297,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Audio',
|
||||
media_svg_label: 'Diagram',
|
||||
media_video_label: 'Video',
|
||||
media_open: 'Open',
|
||||
media_download: 'Download',
|
||||
csv_loading: 'Loading CSV',
|
||||
csv_too_large: 'CSV file too large for inline rendering',
|
||||
|
|
@ -2616,7 +2615,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Audio',
|
||||
media_svg_label: 'Diagramma',
|
||||
media_video_label: 'Video',
|
||||
media_open: 'Apri',
|
||||
media_download: 'Scarica',
|
||||
csv_loading: 'Caricamento CSV',
|
||||
csv_too_large: 'File CSV troppo grande per il rendering inline',
|
||||
|
|
@ -3940,7 +3938,6 @@ const LOCALES = {
|
|||
media_audio_label: '音声',
|
||||
media_svg_label: '図',
|
||||
media_video_label: '動画',
|
||||
media_open: '開く',
|
||||
media_download: 'ダウンロード',
|
||||
csv_loading: 'CSV を読み込み中',
|
||||
csv_too_large: 'CSV ファイルが大きすぎてインライン表示できません',
|
||||
|
|
@ -5136,7 +5133,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Аудио',
|
||||
media_svg_label: 'Диаграмма',
|
||||
media_video_label: 'Видео',
|
||||
media_open: 'Открыть',
|
||||
media_download: 'Скачать',
|
||||
csv_loading: 'Загрузка CSV',
|
||||
csv_too_large: 'CSV-файл слишком большой для встроенного отображения',
|
||||
|
|
@ -6380,7 +6376,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Audio',
|
||||
media_svg_label: 'Diagrama',
|
||||
media_video_label: 'Vídeo',
|
||||
media_open: 'Abrir',
|
||||
media_download: 'Descargar',
|
||||
csv_loading: 'Cargando CSV',
|
||||
csv_too_large: 'Archivo CSV demasiado grande para mostrar en línea',
|
||||
|
|
@ -7638,7 +7633,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Audio',
|
||||
media_svg_label: 'Diagramm',
|
||||
media_video_label: 'Video',
|
||||
media_open: 'Öffnen',
|
||||
media_download: 'Herunterladen',
|
||||
csv_loading: 'CSV wird geladen',
|
||||
csv_too_large: 'CSV-Datei zu groß für Inline-Anzeige',
|
||||
|
|
@ -8901,7 +8895,6 @@ const LOCALES = {
|
|||
media_audio_label: '音频',
|
||||
media_svg_label: '图表',
|
||||
media_video_label: '视频',
|
||||
media_open: '打开',
|
||||
media_download: '下载',
|
||||
csv_loading: '加载 CSV',
|
||||
csv_too_large: 'CSV 文件过大,无法内联渲染',
|
||||
|
|
@ -10217,7 +10210,6 @@ const LOCALES = {
|
|||
media_audio_label: '音訊',
|
||||
media_svg_label: '圖表',
|
||||
media_video_label: '影片',
|
||||
media_open: '開啟',
|
||||
media_download: '下載',
|
||||
csv_loading: '載入 CSV',
|
||||
csv_too_large: 'CSV 檔案過大,無法內嵌渲染',
|
||||
|
|
@ -12673,7 +12665,6 @@ const LOCALES = {
|
|||
media_audio_label: '오디오',
|
||||
media_svg_label: '다이어그램',
|
||||
media_video_label: '비디오',
|
||||
media_open: '열기',
|
||||
media_download: '다운로드',
|
||||
csv_loading: 'CSV 로딩 중',
|
||||
csv_too_large: 'CSV 파일이 너무 커서 인라인 렌더링할 수 없습니다',
|
||||
|
|
@ -13895,7 +13886,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Audio',
|
||||
media_svg_label: 'Diagramme',
|
||||
media_video_label: 'Vidéo',
|
||||
media_open: 'Ouvrir',
|
||||
media_download: 'Télécharger',
|
||||
csv_loading: 'Chargement du fichier CSV',
|
||||
csv_too_large: 'Fichier CSV trop volumineux pour le rendu en ligne',
|
||||
|
|
@ -15245,7 +15235,6 @@ const LOCALES = {
|
|||
media_audio_label: 'Ses',
|
||||
media_svg_label: 'Diyagram',
|
||||
media_video_label: 'Video',
|
||||
media_open: 'Aç',
|
||||
media_download: 'İndir',
|
||||
csv_loading: 'CSV yükleniyor',
|
||||
csv_too_large: 'CSV dosyası satır içi oluşturma için çok büyük',
|
||||
|
|
|
|||
|
|
@ -3507,14 +3507,15 @@ function renderMd(raw){
|
|||
const localArtifactCard=(src,name)=>{
|
||||
const safeSrc=esc(src);
|
||||
const safeName=esc(name||'image');
|
||||
const tt=(typeof t==='function')?t:(key=>({media_open:'Open',media_download:'Download'}[key]||key));
|
||||
const tt=(typeof t==='function')?t:(key=>({media_download:'Download'}[key]||key));
|
||||
// Clean inline image (keeps the existing .msg-media-img lightbox-on-click
|
||||
// behavior) with a hover/focus-revealed Download action overlaid top-right,
|
||||
// matching the ChatGPT/Claude/Gemini pattern. The image stays the hero —
|
||||
// no permanent card chrome. Download is the one affordance the lightbox
|
||||
// (zoom-on-click) doesn't already provide.
|
||||
const dlLabel=esc(tt('media_download'));
|
||||
const dlSvg='<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>';
|
||||
return `<span class="msg-artifact-image"><img class="msg-media-img" src="${safeSrc}" alt="${safeName}" loading="lazy"><a class="msg-artifact-download" href="${safeSrc}" download="${safeName}" title="${tt('media_download')}" aria-label="${tt('media_download')}" onclick="event.stopPropagation()">${dlSvg}</a></span>`;
|
||||
return `<span class="msg-artifact-image"><img class="msg-media-img" src="${safeSrc}" alt="${safeName}" loading="lazy"><a class="msg-artifact-download" href="${safeSrc}" download="${safeName}" title="${dlLabel}" aria-label="${dlLabel}" onclick="event.stopPropagation()">${dlSvg}</a></span>`;
|
||||
};
|
||||
if(/^file:\/\//i.test(ref)){
|
||||
try{
|
||||
|
|
|
|||
Loading…
Reference in a new issue