feat: version 2.12.1 -- ver imagenes (png/jpg/webp) en Documentos, sin tocar txt/md
Agrega soporte de imagenes al popup "Ver" de Documentos (proyectos y actividades): se descargan como blob y se muestran con un object URL (revocado al cerrar), separado por completo del camino de texto/markdown ya existente (readAttachmentText), que queda intacto. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog - project.linkedcorp.net
|
||||
|
||||
## [2.12.1] - 2026-10-14
|
||||
### Added
|
||||
- Documentos (proyectos y actividades): las imágenes (.png, .jpg, .webp) también se pueden ver
|
||||
en el popup con el botón "Ver", igual que ya funcionaba con .txt/.md — sin tocar ese flujo.
|
||||
|
||||
## [2.12.0] - 2026-10-14
|
||||
### Added
|
||||
- Comentarios (de proyectos y actividades): ahora se pueden editar y borrar, solo quien lo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "project-linkedcorp-backend",
|
||||
"version": "2.12.0",
|
||||
"version": "2.12.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "project-linkedcorp-frontend",
|
||||
"private": true,
|
||||
"version": "2.12.0",
|
||||
"version": "2.12.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRef, useState } from 'react';
|
||||
import { Upload, Download, Trash2, Paperclip, Eye } from 'lucide-react';
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
import { useAsync, useAction } from '../hooks/useAsync.js';
|
||||
import { listAttachments, uploadAttachment, deleteAttachment, downloadAttachment, readAttachmentText } from '../services/attachments.js';
|
||||
import { listAttachments, uploadAttachment, deleteAttachment, downloadAttachment, readAttachmentText, fetchAttachmentBlob } from '../services/attachments.js';
|
||||
import { renderMarkdown } from '../utils/renderMarkdown.js';
|
||||
import { Button } from './Button.jsx';
|
||||
import { DetailPopup } from './DetailPopup.jsx';
|
||||
@@ -11,7 +11,9 @@ import { DetailPopup } from './DetailPopup.jsx';
|
||||
import hljsLight from 'highlight.js/styles/vs.css?inline';
|
||||
import hljsDark from 'highlight.js/styles/vs2015.css?inline';
|
||||
|
||||
const VIEWABLE_MIME = new Set(['text/plain', 'text/markdown']);
|
||||
const TEXT_VIEWABLE_MIME = new Set(['text/plain', 'text/markdown']);
|
||||
const IMAGE_VIEWABLE_MIME = new Set(['image/png', 'image/jpeg', 'image/webp']);
|
||||
const VIEWABLE_MIME = new Set([...TEXT_VIEWABLE_MIME, ...IMAGE_VIEWABLE_MIME]);
|
||||
|
||||
export function AttachmentsPanel({ entityType, entityId, onChanged }) {
|
||||
const { t, openDialog, theme } = useUi();
|
||||
@@ -20,14 +22,29 @@ export function AttachmentsPanel({ entityType, entityId, onChanged }) {
|
||||
const inputRef = useRef(null);
|
||||
const [viewing, setViewing] = useState(null); // adjunto abierto en el popup de lectura
|
||||
const [viewContent, setViewContent] = useState('');
|
||||
const [viewImageUrl, setViewImageUrl] = useState(null);
|
||||
const [viewLoading, setViewLoading] = useState(false);
|
||||
|
||||
function closeView() {
|
||||
setViewing(null);
|
||||
// Libera la memoria del blob de la imagen -- no toca nada del flujo de texto/markdown.
|
||||
if (viewImageUrl) URL.revokeObjectURL(viewImageUrl);
|
||||
setViewImageUrl(null);
|
||||
}
|
||||
|
||||
async function openView(file) {
|
||||
setViewing(file);
|
||||
setViewContent('');
|
||||
if (viewImageUrl) URL.revokeObjectURL(viewImageUrl);
|
||||
setViewImageUrl(null);
|
||||
setViewLoading(true);
|
||||
try {
|
||||
if (IMAGE_VIEWABLE_MIME.has(file.mime_type)) {
|
||||
const blob = await fetchAttachmentBlob(file.id);
|
||||
setViewImageUrl(URL.createObjectURL(blob));
|
||||
} else {
|
||||
setViewContent(await readAttachmentText(file.id));
|
||||
}
|
||||
} catch {
|
||||
setViewContent(t('document_view_error'));
|
||||
} finally {
|
||||
@@ -84,9 +101,13 @@ export function AttachmentsPanel({ entityType, entityId, onChanged }) {
|
||||
</div>
|
||||
))}
|
||||
|
||||
<DetailPopup open={!!viewing} title={viewing?.original_name || ''} onClose={() => setViewing(null)} width={720} height={600}>
|
||||
<DetailPopup open={!!viewing} title={viewing?.original_name || ''} onClose={closeView} width={720} height={600}>
|
||||
{viewLoading ? (
|
||||
<div className="text-muted">{t('common_loading')}</div>
|
||||
) : viewImageUrl ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
|
||||
<img src={viewImageUrl} alt={viewing?.original_name} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
|
||||
</div>
|
||||
) : viewing?.mime_type === 'text/markdown' ? (
|
||||
<>
|
||||
<style>{theme === 'light' ? hljsLight : hljsDark}</style>
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function uploadAttachment(entityType, entityId, file) {
|
||||
|
||||
export const deleteAttachment = (id) => api.delete(`/attachments/${id}`);
|
||||
|
||||
async function fetchAttachmentBlob(id) {
|
||||
export async function fetchAttachmentBlob(id) {
|
||||
const token = localStorage.getItem('plc_token');
|
||||
const res = await fetch(api.downloadUrl(`/attachments/${id}/download`), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
|
||||
Reference in New Issue
Block a user