Coreccion del cliente para descargar los archivos multimedia
This commit is contained in:
@@ -16,6 +16,25 @@
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
// Helper to resolve an API path from the DB (e.g., /api/uploads/...) to a full URL pointing to the local backend.
|
||||
function resolveAssetUrl(url: string | undefined): string {
|
||||
try {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
||||
|
||||
// Fallback to empty string if VITE_API_URL is missing
|
||||
// @ts-ignore
|
||||
const apiUrl = import.meta.env.VITE_API_URL || '';
|
||||
const base = apiUrl.replace(/\/$/, '');
|
||||
const cleanUrl = url.replace(/^\/api\//, '/');
|
||||
const finalUrl = `${base}${cleanUrl}`;
|
||||
return finalUrl;
|
||||
} catch (e) {
|
||||
console.error('[SSR DEBUG] Error in resolveAssetUrl:', e);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
let article: HelpArticle | null = null; // Removed $state() as it's not reactive by itself in Svelte 5 standard store usage unless wrapped
|
||||
let loading = true; // Use standard value, reactive via variable assignment in Svelte 5 runes if enabled
|
||||
let error: string | null = null;
|
||||
@@ -60,16 +79,32 @@
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
function renderMarkdown(content: string) {
|
||||
const rawHtml = marked.parse(content || '') as string;
|
||||
// Add IDs to headers for TOC navigation
|
||||
const htmlWithIds = rawHtml.replace(/<h([1-3])>(.*?)<\/h\1>/g, (match, level, text) => {
|
||||
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
|
||||
return `<h${level} id="${id}">${text}</h${level}>`;
|
||||
});
|
||||
if (browser) {
|
||||
return DOMPurify.sanitize(htmlWithIds);
|
||||
if (!content) return '';
|
||||
try {
|
||||
const rawHtml = marked.parse(content) as string;
|
||||
// Add IDs to headers for TOC navigation
|
||||
let htmlWithIds = rawHtml.replace(/<h([1-3])>(.*?)<\/h\1>/g, (match, level, text) => {
|
||||
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
|
||||
return `<h${level} id="${id}">${text}</h${level}>`;
|
||||
});
|
||||
|
||||
// Rewrite image src and link hrefs to point to the local backend
|
||||
htmlWithIds = htmlWithIds.replace(/src="([^"]+)"/g, (match, src) => {
|
||||
return `src="${resolveAssetUrl(src)}"`;
|
||||
});
|
||||
htmlWithIds = htmlWithIds.replace(/href="(\/api\/uploads\/[^"]+)"/g, (match, href) => {
|
||||
return `href="${resolveAssetUrl(href)}"`;
|
||||
});
|
||||
|
||||
if (browser && typeof window !== 'undefined') {
|
||||
// DOMPurify only works in the browser
|
||||
return DOMPurify.sanitize(htmlWithIds);
|
||||
}
|
||||
return htmlWithIds; // SSR returns raw HTML (or could use isomorphic-dompurify)
|
||||
} catch (e) {
|
||||
console.error('Markdown parsing error:', e);
|
||||
return '';
|
||||
}
|
||||
return htmlWithIds;
|
||||
}
|
||||
|
||||
function scrollToHeader(id: string) {
|
||||
@@ -152,11 +187,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="sm" href={article.file_url} target="_blank">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
href={resolveAssetUrl(article.file_url)}
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink class="mr-2 h-4 w-4" />
|
||||
Abrir en pestaña
|
||||
</Button>
|
||||
<Button size="sm" href={article.file_url} download={article.title}>
|
||||
<Button
|
||||
size="sm"
|
||||
href={resolveAssetUrl(article.file_url)}
|
||||
download={article.title}
|
||||
>
|
||||
<Download class="mr-2 h-4 w-4" />
|
||||
Descargar
|
||||
</Button>
|
||||
@@ -164,7 +208,7 @@
|
||||
</div>
|
||||
<!-- PDF Embed -->
|
||||
<iframe
|
||||
src={article.file_url}
|
||||
src={resolveAssetUrl(article.file_url)}
|
||||
title={article.title}
|
||||
class="h-[800px] w-full rounded-xl border shadow-inner"
|
||||
></iframe>
|
||||
@@ -172,7 +216,7 @@
|
||||
{:else if article.content_type === 'video'}
|
||||
<div class="flex flex-col gap-4">
|
||||
<video controls class="w-full rounded-xl border shadow-lg">
|
||||
<source src={article.file_url} type={article.mime_type} />
|
||||
<source src={resolveAssetUrl(article.file_url)} type={article.mime_type} />
|
||||
Tu navegador no soporta videos.
|
||||
</video>
|
||||
<div class="rounded-lg border bg-muted/30 p-4">
|
||||
@@ -193,7 +237,11 @@
|
||||
Este archivo no tiene vista previa directa.
|
||||
</p>
|
||||
</div>
|
||||
<Button href={article.file_url} download={article.title} class="px-8 shadow-lg">
|
||||
<Button
|
||||
href={resolveAssetUrl(article.file_url)}
|
||||
download={article.title}
|
||||
class="px-8 shadow-lg"
|
||||
>
|
||||
<Download class="mr-2 h-4 w-4" />
|
||||
Descargar ahora
|
||||
</Button>
|
||||
|
||||
@@ -27,6 +27,19 @@
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
// Helper to resolve an API path from the DB (e.g., /api/uploads/...) to a full URL pointing to the local backend.
|
||||
function resolveAssetUrl(url: string | undefined): string {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
||||
|
||||
// Fallback to empty string if VITE_API_URL is missing
|
||||
// @ts-ignore
|
||||
const apiUrl = import.meta.env.VITE_API_URL || '';
|
||||
const base = apiUrl.replace(/\/$/, '');
|
||||
const cleanUrl = url.replace(/^\/api\//, '/');
|
||||
return `${base}${cleanUrl}`;
|
||||
}
|
||||
|
||||
let article = $state<HelpArticle | null>(null);
|
||||
let loading = $state(true);
|
||||
let processing = $state(false);
|
||||
@@ -206,11 +219,26 @@
|
||||
}
|
||||
|
||||
function renderMarkdown(text: string) {
|
||||
const html = marked.parse(text || '') as string;
|
||||
if (browser) {
|
||||
return DOMPurify.sanitize(html);
|
||||
if (!text) return '';
|
||||
try {
|
||||
const html = marked.parse(text) as string;
|
||||
|
||||
// Rewrite image src and link hrefs to point to the local backend
|
||||
let resolvedHtml = html.replace(/src="([^"]+)"/g, (match, src) => {
|
||||
return `src="${resolveAssetUrl(src)}"`;
|
||||
});
|
||||
resolvedHtml = resolvedHtml.replace(/href="(\/api\/uploads\/[^"]+)"/g, (match, href) => {
|
||||
return `href="${resolveAssetUrl(href)}"`;
|
||||
});
|
||||
|
||||
if (browser && typeof window !== 'undefined') {
|
||||
return DOMPurify.sanitize(resolvedHtml);
|
||||
}
|
||||
return resolvedHtml;
|
||||
} catch (e) {
|
||||
console.error("Markdown parsing error in editor:", e);
|
||||
return '';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -359,8 +387,8 @@
|
||||
bind:value={content}
|
||||
class="w-full flex-1 resize-none bg-background p-8 font-mono text-sm leading-relaxed outline-none"
|
||||
placeholder="# Empieza a escribir aquí..."
|
||||
ondrop={handleDrop}
|
||||
ondragover={(e) => e.preventDefault()}
|
||||
ondrop={(e: DragEvent) => handleDrop(e)}
|
||||
ondragover={(e: DragEvent) => e.preventDefault()}
|
||||
></textarea>
|
||||
|
||||
<!-- Dropping Hint -->
|
||||
@@ -418,7 +446,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" class="flex-1" href={file_url} target="_blank">
|
||||
<Button variant="outline" class="flex-1" href={resolveAssetUrl(file_url)} target="_blank">
|
||||
Ver Archivo
|
||||
</Button>
|
||||
<label
|
||||
|
||||
Reference in New Issue
Block a user