feat(i18n): traducir Biblioteca de Conocimiento (help-center)

- Página principal: título, subtítulo, búsqueda, tarjetas, diálogo de eliminar, toasts
- Lector de capítulo: navegación, PDF/video/documento, descargas, TOC
- Editor: formulario completo (labels, placeholders), toolbar markdown, subida de archivos, toasts
- ~70 claves common nuevas (help_*)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 16:14:18 -05:00
parent 543fefb743
commit cb202ddd14
5 changed files with 246 additions and 81 deletions

View File

@@ -2008,6 +2008,87 @@
"maint_desc": "MODULE NOT YET AVAILABLE!",
"maint_back": "Back to Home",
"sub_manifest": "Export manifests management",
"title_invoice_report": "Invoice Report"
"title_invoice_report": "Invoice Report",
"title_help_library": "Knowledge Library",
"sub_help": "System Manuals, Guides and Documentation.",
"new_chapter": "New Chapter",
"help_search_ph": "Search the library...",
"help_empty": "The library is empty.",
"help_format": "Format",
"help_unknown": "Unknown",
"help_size": "Size",
"help_watch": "Watch",
"help_read": "Read",
"help_open": "Open",
"help_delete_title": "Delete chapter?",
"help_delete_desc": "\"{title}\" will be permanently deleted.",
"help_toast_load_error": "Error loading articles",
"help_toast_deleted": "Chapter deleted",
"help_toast_error": "Error",
"help_back_library": "Back to Library",
"help_chapter_load_error": "Error loading chapter",
"help_pdf_document": "PDF Document",
"help_open_tab": "Open in tab",
"help_download": "Download",
"help_video_unsupported": "Your browser does not support videos.",
"help_file_info": "File information",
"help_file_download": "File to download",
"help_no_preview": "This file has no direct preview.",
"help_download_now": "Download now",
"help_in_chapter": "In this chapter",
"btn_cancel": "Cancel",
"btn_delete": "Delete",
"btn_edit": "Edit",
"btn_save": "Save",
"help_toast_article_load_error": "Error loading article",
"help_toast_chapter_created": "Chapter created",
"help_toast_changes_saved": "Changes saved",
"help_toast_save_error": "Error saving",
"help_toast_uploading_image": "Uploading image...",
"help_toast_image_inserted": "Image inserted",
"help_toast_upload_error": "Error uploading",
"help_toast_uploading_file": "Uploading {name}...",
"help_untitled": "Untitled",
"help_toast_file_uploaded": "File uploaded successfully",
"help_toast_file_upload_error": "Error uploading file",
"help_back": "Back",
"help_hide_preview": "Hide Preview",
"help_show_preview": "Show Preview",
"help_config": "Settings",
"help_field_title": "Title",
"help_ph_title": "E.g.: Introduction",
"help_field_slug": "Identifier (Slug)",
"help_ph_slug": "eg-article-title",
"help_slug_hint": "Auto-generated from the title if left empty.",
"help_field_type": "Content Type",
"help_type_article": "Article (Markdown)",
"help_type_document": "Other Document",
"help_advanced": "Advanced Options",
"help_field_category": "Category",
"help_ph_category": "E.g.: General",
"help_field_order": "Order",
"help_field_context": "Context Path",
"help_ph_context": "E.g.: /dashboard/...",
"help_context_hint": "URL where this article will appear.",
"help_field_tags": "Tags",
"help_ph_tags": "E.g.: invoices, scrap",
"help_bold": "Bold",
"help_italic": "Italic",
"help_h1": "Heading 1",
"help_h2": "Heading 2",
"help_list": "List",
"help_link": "Link",
"help_upload_image": "Upload Image",
"help_ph_content": "# Start writing here...",
"help_drag_images": "Drag images here",
"help_config_pdf": "PDF Settings",
"help_config_video": "Video Settings",
"help_config_document": "Document Settings",
"help_upload_hint": "Upload the file you want to associate with this title.",
"help_file_loaded": "File Loaded",
"help_view_file": "View File",
"help_change_file": "Change File",
"help_select_file": "Select a file",
"help_drag_drop": "Or drag and drop here"
}
}

View File

@@ -2008,6 +2008,87 @@
"maint_desc": "¡MÓDULO AÚN NO DISPONIBLE!",
"maint_back": "Volver al Inicio",
"sub_manifest": "Gestión de manifiestos de exportación",
"title_invoice_report": "Reporte de Facturas"
"title_invoice_report": "Reporte de Facturas",
"title_help_library": "Biblioteca de Conocimiento",
"sub_help": "Manuales, Guías y Documentación del Sistema.",
"new_chapter": "Nuevo Capítulo",
"help_search_ph": "Buscar en la biblioteca...",
"help_empty": "La biblioteca está vacía.",
"help_format": "Formato",
"help_unknown": "Desconocido",
"help_size": "Tamaño",
"help_watch": "Ver",
"help_read": "Leer",
"help_open": "Abrir",
"help_delete_title": "¿Eliminar capítulo?",
"help_delete_desc": "Se eliminará permanentemente \"{title}\".",
"help_toast_load_error": "Error al cargar artículos",
"help_toast_deleted": "Capítulo eliminado",
"help_toast_error": "Error",
"help_back_library": "Volver a la Biblioteca",
"help_chapter_load_error": "Error al cargar el capítulo",
"help_pdf_document": "Documento PDF",
"help_open_tab": "Abrir en pestaña",
"help_download": "Descargar",
"help_video_unsupported": "Tu navegador no soporta videos.",
"help_file_info": "Información del archivo",
"help_file_download": "Archivo para descargar",
"help_no_preview": "Este archivo no tiene vista previa directa.",
"help_download_now": "Descargar ahora",
"help_in_chapter": "En este capítulo",
"btn_cancel": "Cancelar",
"btn_delete": "Eliminar",
"btn_edit": "Editar",
"btn_save": "Guardar",
"help_toast_article_load_error": "Error al cargar artículo",
"help_toast_chapter_created": "Capítulo creado",
"help_toast_changes_saved": "Cambios guardados",
"help_toast_save_error": "Error al guardar",
"help_toast_uploading_image": "Subiendo imagen...",
"help_toast_image_inserted": "Imagen insertada",
"help_toast_upload_error": "Error al subir",
"help_toast_uploading_file": "Subiendo {name}...",
"help_untitled": "Sin Título",
"help_toast_file_uploaded": "Archivo subido correctamente",
"help_toast_file_upload_error": "Error al subir archivo",
"help_back": "Volver",
"help_hide_preview": "Ocultar Vista Previa",
"help_show_preview": "Ver Vista Previa",
"help_config": "Configuración",
"help_field_title": "Título",
"help_ph_title": "Ej: Introducción",
"help_field_slug": "Identificador (Slug)",
"help_ph_slug": "ej-titulo-articulo",
"help_slug_hint": "Se autogenera del título si se deja vacío.",
"help_field_type": "Tipo de Contenido",
"help_type_article": "Artículo (Markdown)",
"help_type_document": "Otro Documento",
"help_advanced": "Opciones Avanzadas",
"help_field_category": "Categoría",
"help_ph_category": "Ej: General",
"help_field_order": "Orden",
"help_field_context": "Ruta Contextual",
"help_ph_context": "Ej: /dashboard/...",
"help_context_hint": "URL donde aparecerá este artículo.",
"help_field_tags": "Etiquetas",
"help_ph_tags": "Ej: facturas, mermas",
"help_bold": "Negrita",
"help_italic": "Cursiva",
"help_h1": "Título 1",
"help_h2": "Título 2",
"help_list": "Lista",
"help_link": "Enlace",
"help_upload_image": "Subir Imagen",
"help_ph_content": "# Empieza a escribir aquí...",
"help_drag_images": "Arrastra imágenes aquí",
"help_config_pdf": "Configuración de PDF",
"help_config_video": "Configuración de Video",
"help_config_document": "Configuración de Documento",
"help_upload_hint": "Sube el archivo que deseas asociar a este título.",
"help_file_loaded": "Archivo Cargado",
"help_view_file": "Ver Archivo",
"help_change_file": "Cambiar Archivo",
"help_select_file": "Selecciona un archivo",
"help_drag_drop": "O arrastra y suelta aquí"
}
}

View File

@@ -21,6 +21,7 @@
FileCode
} from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import { m } from '$lib/i18n/messages';
let articles: HelpArticle[] = $state([]);
let loading = $state(true);
@@ -92,7 +93,7 @@
try {
articles = await helpApi.listArticles();
} catch (e: any) {
toast.error('Error al cargar artículos: ' + e.message);
toast.error(m['common.help_toast_load_error']() + ': ' + e.message);
} finally {
loading = false;
}
@@ -111,11 +112,11 @@
processing = true;
try {
await helpApi.deleteArticle(selectedArticle.uuid);
toast.success('Capítulo eliminado');
toast.success(m['common.help_toast_deleted']());
showDeleteDialog = false;
await loadArticles();
} catch (e: any) {
toast.error('Error: ' + e.message);
toast.error(m['common.help_toast_error']() + ': ' + e.message);
} finally {
processing = false;
}
@@ -138,15 +139,15 @@
<div>
<h1 class="flex items-center gap-2 text-3xl font-bold tracking-tight text-primary">
<Book class="h-8 w-8" />
Biblioteca de Conocimiento
{m['common.title_help_library']()}
</h1>
<p class="text-muted-foreground">Manuales, Guías y Documentación del Sistema.</p>
<p class="text-muted-foreground">{m['common.sub_help']()}</p>
</div>
<div class="flex items-center gap-2">
{#if canCreate}
<Button href="/dashboard/help-center/editor/new" class="shadow-lg">
<Plus class="mr-2 h-4 w-4" />
Nuevo Capítulo
{m['common.new_chapter']()}
</Button>
{/if}
</div>
@@ -157,7 +158,7 @@
<Search class="absolute top-3 left-3 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Buscar en la biblioteca..."
placeholder={m['common.help_search_ph']()}
class="h-10 border-muted-foreground/20 bg-background pl-10"
bind:value={searchTerm}
/>
@@ -172,7 +173,7 @@
{:else if Object.keys(groupedArticles).length === 0}
<div class="flex flex-col items-center justify-center py-20 text-muted-foreground">
<Book class="mb-4 h-16 w-16 opacity-20" />
<p>La biblioteca está vacía.</p>
<p>{m['common.help_empty']()}</p>
</div>
{:else}
{#each Object.entries(groupedArticles) as [category, groupArticles]}
@@ -219,9 +220,9 @@
</div>
{:else}
<div class="flex flex-col gap-1 text-xs text-muted-foreground">
<p>Formato: {article.mime_type || 'Desconocido'}</p>
<p>{m['common.help_format']()}: {article.mime_type || m['common.help_unknown']()}</p>
{#if article.file_size}
<p>Tamaño: {(article.file_size / 1024 / 1024).toFixed(2)} MB</p>
<p>{m['common.help_size']()}: {(article.file_size / 1024 / 1024).toFixed(2)} MB</p>
{/if}
</div>
{/if}
@@ -229,11 +230,11 @@
<Card.Footer class="flex items-center justify-between border-t bg-muted/5 pt-4">
<Button variant="ghost" size="sm" href={`/dashboard/help-center/${article.uuid}`}>
{#if article.content_type === 'video'}
Ver
{m['common.help_watch']()}
{:else if article.content_type === 'article'}
Leer
{m['common.help_read']()}
{:else}
Abrir
{m['common.help_open']()}
{/if}
</Button>
<div class="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100">
@@ -272,19 +273,19 @@
<AlertDialog.Root bind:open={showDeleteDialog}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>¿Eliminar capítulo?</AlertDialog.Title>
<AlertDialog.Title>{m['common.help_delete_title']()}</AlertDialog.Title>
<AlertDialog.Description>
Se eliminará permanentemente "{selectedArticle?.title}".
{m['common.help_delete_desc']({ title: selectedArticle?.title ?? '' })}
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel onclick={() => (showDeleteDialog = false)}>Cancelar</AlertDialog.Cancel>
<AlertDialog.Cancel onclick={() => (showDeleteDialog = false)}>{m['common.btn_cancel']()}</AlertDialog.Cancel>
<AlertDialog.Action
class="text-destructive-foreground bg-destructive hover:bg-destructive/90"
onclick={handleDelete}
>
{#if processing}<Loader2 class="mr-2 h-4 w-4 animate-spin" />{/if}
Eliminar
{m['common.btn_delete']()}
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { m } from '$lib/i18n/messages';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { helpApi, type HelpArticle } from '$lib/api/help';
@@ -122,7 +123,7 @@
>
<Button variant="ghost" size="sm" href="/dashboard/help-center" class="gap-2">
<ArrowLeft class="h-4 w-4" />
Volver a la Biblioteca
{m['common.help_back_library']()}
</Button>
<div class="mx-2 h-4 w-px bg-border"></div>
{#if article}
@@ -140,7 +141,7 @@
</div>
{:else if error}
<div class="flex h-full flex-col items-center justify-center gap-2 text-destructive">
<p class="text-lg font-medium">Error al cargar el capítulo</p>
<p class="text-lg font-medium">{m['common.help_chapter_load_error']()}</p>
<p class="text-sm text-muted-foreground">{error}</p>
</div>
{:else if article}
@@ -180,7 +181,7 @@
<div class="flex items-center gap-3">
<FileText class="h-8 w-8 text-red-500" />
<div>
<p class="font-medium">Documento PDF</p>
<p class="font-medium">{m['common.help_pdf_document']()}</p>
<p class="text-xs text-muted-foreground">
{article.file_size ? (article.file_size / 1024 / 1024).toFixed(2) : '??'} MB
</p>
@@ -194,7 +195,7 @@
target="_blank"
>
<ExternalLink class="mr-2 h-4 w-4" />
Abrir en pestaña
{m['common.help_open_tab']()}
</Button>
<Button
size="sm"
@@ -202,7 +203,7 @@
download={article.title}
>
<Download class="mr-2 h-4 w-4" />
Descargar
{m['common.help_download']()}
</Button>
</div>
</div>
@@ -217,10 +218,10 @@
<div class="flex flex-col gap-4">
<video controls class="w-full rounded-xl border shadow-lg">
<source src={resolveAssetUrl(article.file_url)} type={article.mime_type} />
Tu navegador no soporta videos.
{m['common.help_video_unsupported']()}
</video>
<div class="rounded-lg border bg-muted/30 p-4">
<p class="text-sm font-medium">Información del archivo</p>
<p class="text-sm font-medium">{m['common.help_file_info']()}</p>
<p class="text-xs text-muted-foreground">{article.mime_type}</p>
</div>
</div>
@@ -232,9 +233,9 @@
<Download class="h-12 w-12 text-primary" />
</div>
<div class="text-center">
<h3 class="text-xl font-semibold">Archivo para descargar</h3>
<h3 class="text-xl font-semibold">{m['common.help_file_download']()}</h3>
<p class="text-muted-foreground truncate max-w-md">
Este archivo no tiene vista previa directa.
{m['common.help_no_preview']()}
</p>
</div>
<Button
@@ -243,7 +244,7 @@
class="px-8 shadow-lg"
>
<Download class="mr-2 h-4 w-4" />
Descargar ahora
{m['common.help_download_now']()}
</Button>
</div>
{/if}
@@ -254,7 +255,7 @@
{#if toc.length > 0}
<div class="hidden w-72 overflow-y-auto border-l bg-muted/5 p-6 xl:block">
<h4 class="mb-4 text-sm font-semibold tracking-wider text-muted-foreground uppercase">
En este capitulo
{m['common.help_in_chapter']()}
</h4>
<nav class="space-y-1">
{#each toc as item}

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { m } from '$lib/i18n/messages';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { onMount, tick } from 'svelte';
@@ -99,7 +100,7 @@
tags = article.tags || '';
}
} catch (e: any) {
toast.error('Error al cargar artículo: ' + e.message);
toast.error(m['common.help_toast_article_load_error']() + ': ' + e.message);
} finally {
loading = false;
}
@@ -135,17 +136,17 @@
const uuid = $page.params.uuid as string;
if (uuid === 'new') {
const newArticle = await helpApi.createArticle(data);
toast.success('Capítulo creado');
toast.success(m['common.help_toast_chapter_created']());
// Redirect to edit mode or list? For now, stay here but update URL would be ideal.
// simpler to just go back to list or reload.
// Let's redirect to edit mode of this new UUID to avoid duplicates on re-save
window.location.href = `/dashboard/help-center/editor/${newArticle.uuid}`;
} else {
await helpApi.updateArticle(uuid, data);
toast.success('Cambios guardados');
toast.success(m['common.help_toast_changes_saved']());
}
} catch (e: any) {
toast.error('Error al guardar: ' + e.message);
toast.error(m['common.help_toast_save_error']() + ': ' + e.message);
} finally {
processing = false;
}
@@ -173,22 +174,22 @@
}
async function handleImageUpload(file: File) {
const tid = toast.loading('Subiendo imagen...');
const tid = toast.loading(m['common.help_toast_uploading_image']());
processing = true;
try {
const result = await helpApi.uploadImage(file);
const imageMarkdown = `\n![${file.name}](${result.url})\n`;
insertText(imageMarkdown);
toast.success('Imagen insertada', { id: tid });
toast.success(m['common.help_toast_image_inserted'](), { id: tid });
} catch (e: any) {
toast.error('Error al subir: ' + e.message, { id: tid });
toast.error(m['common.help_toast_upload_error']() + ': ' + e.message, { id: tid });
} finally {
processing = false;
}
}
async function handleAssetUpload(file: File) {
const tid = toast.loading(`Subiendo ${file.name}...`);
const tid = toast.loading(m['common.help_toast_uploading_file']({ name: file.name }));
processing = true;
try {
const result = await helpApi.uploadAsset(file);
@@ -198,7 +199,7 @@
// Auto-set title if empty
if (!title) {
title = file.name.split('.').shift() || 'Sin Título';
title = file.name.split('.').shift() || m['common.help_untitled']();
}
if (!slug) {
slug = title.toLowerCase().replace(/[^\w]+/g, '-');
@@ -211,9 +212,9 @@
else content_type = 'document';
}
toast.success('Archivo subido correctamente', { id: tid });
toast.success(m['common.help_toast_file_uploaded'](), { id: tid });
} catch (e: any) {
toast.error('Error al subir archivo: ' + e.message, { id: tid });
toast.error(m['common.help_toast_file_upload_error']() + ': ' + e.message, { id: tid });
} finally {
processing = false;
}
@@ -260,20 +261,20 @@
<div class="flex items-center gap-4">
<Button variant="ghost" size="sm" href="/dashboard/help-center">
<ArrowLeft class="mr-2 h-4 w-4" />
Volver
{m['common.help_back']()}
</Button>
<h1 class="max-w-md truncate text-lg font-semibold">
{title || 'Sin Título'}
{title || m['common.help_untitled']()}
</h1>
</div>
<div class="flex items-center gap-2">
<Button variant="outline" size="sm" onclick={() => (showPreview = !showPreview)}>
{showPreview ? 'Ocultar Vista Previa' : 'Ver Vista Previa'}
{showPreview ? m['common.help_hide_preview']() : m['common.help_show_preview']()}
</Button>
<Button onclick={handleSave} disabled={processing || !title}>
{#if processing}<Loader2 class="mr-2 h-4 w-4 animate-spin" />{/if}
<Save class="mr-2 h-4 w-4" />
Guardar
{m['common.btn_save']()}
</Button>
</div>
</header>
@@ -283,56 +284,56 @@
<!-- Metadata Sidebar (Left) - Collapsible or Fixed width -->
<aside class="hidden w-64 overflow-y-auto border-r bg-muted/10 p-4 lg:block">
<h3 class="mb-4 text-sm font-semibold tracking-wider text-muted-foreground uppercase">
Configuración
{m['common.help_config']()}
</h3>
<div class="space-y-4">
<div class="grid gap-2">
<Label for="title">Título</Label>
<Input id="title" bind:value={title} placeholder="Ej: Introducción" />
<Label for="title">{m['common.help_field_title']()}</Label>
<Input id="title" bind:value={title} placeholder={m['common.help_ph_title']()} />
</div>
<div class="grid gap-2 border-t pt-4">
<Label for="slug">Identificador (Slug)</Label>
<Input id="slug" bind:value={slug} placeholder="ej-titulo-articulo" />
<p class="text-[10px] text-muted-foreground">Se autogenera del título si se deja vacío.</p>
<Label for="slug">{m['common.help_field_slug']()}</Label>
<Input id="slug" bind:value={slug} placeholder={m['common.help_ph_slug']()} />
<p class="text-[10px] text-muted-foreground">{m['common.help_slug_hint']()}</p>
</div>
<div class="grid gap-2 border-t pt-4">
<Label for="type">Tipo de Contenido</Label>
<Label for="type">{m['common.help_field_type']()}</Label>
<select
id="type"
bind:value={content_type}
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="article">Artículo (Markdown)</option>
<option value="article">{m['common.help_type_article']()}</option>
<option value="pdf">PDF</option>
<option value="video">Video</option>
<option value="document">Otro Documento</option>
<option value="document">{m['common.help_type_document']()}</option>
</select>
</div>
<details class="group border-t pt-4">
<summary class="flex cursor-pointer items-center justify-between text-xs font-semibold tracking-wider text-muted-foreground uppercase hover:text-primary transition-colors">
Opciones Avanzadas
{m['common.help_advanced']()}
<HelpCircle size={14} class="opacity-50 group-open:rotate-180 transition-transform" />
</summary>
<div class="space-y-4 pt-4">
<div class="grid gap-2">
<Label for="category">Categoría</Label>
<Input id="category" bind:value={category} placeholder="Ej: General" />
<Label for="category">{m['common.help_field_category']()}</Label>
<Input id="category" bind:value={category} placeholder={m['common.help_ph_category']()} />
</div>
<div class="grid gap-2">
<Label for="order">Orden</Label>
<Label for="order">{m['common.help_field_order']()}</Label>
<Input id="order" type="number" bind:value={order} />
</div>
<div class="grid gap-2">
<Label for="context_path" class="flex items-center gap-1">
Ruta Contextual
{m['common.help_field_context']()}
</Label>
<Input id="context_path" bind:value={context_path} placeholder="Ej: /dashboard/..." />
<p class="text-[10px] text-muted-foreground">URL donde aparecerá este artículo.</p>
<Input id="context_path" bind:value={context_path} placeholder={m['common.help_ph_context']()} />
<p class="text-[10px] text-muted-foreground">{m['common.help_context_hint']()}</p>
</div>
<div class="grid gap-2">
<Label for="tags">Etiquetas</Label>
<Input id="tags" bind:value={tags} placeholder="Ej: facturas, mermas" />
<Label for="tags">{m['common.help_field_tags']()}</Label>
<Input id="tags" bind:value={tags} placeholder={m['common.help_ph_tags']()} />
</div>
</div>
</details>
@@ -351,7 +352,7 @@
size="icon"
class="h-8 w-8"
onclick={() => insertText('**', '**')}
title="Negrita"
title={m['common.help_bold']()}
>
<Bold class="h-4 w-4" />
</Button>
@@ -360,7 +361,7 @@
size="icon"
class="h-8 w-8"
onclick={() => insertText('*', '*')}
title="Cursiva"
title={m['common.help_italic']()}
>
<Italic class="h-4 w-4" />
</Button>
@@ -370,7 +371,7 @@
size="icon"
class="h-8 w-8"
onclick={() => insertText('# ')}
title="Título 1"
title={m['common.help_h1']()}
>
<Heading1 class="h-4 w-4" />
</Button>
@@ -379,7 +380,7 @@
size="icon"
class="h-8 w-8"
onclick={() => insertText('## ')}
title="Título 2"
title={m['common.help_h2']()}
>
<Heading2 class="h-4 w-4" />
</Button>
@@ -389,7 +390,7 @@
size="icon"
class="h-8 w-8"
onclick={() => insertText('- ')}
title="Lista"
title={m['common.help_list']()}
>
<List class="h-4 w-4" />
</Button>
@@ -398,13 +399,13 @@
size="icon"
class="h-8 w-8"
onclick={() => insertText('[texto](url)')}
title="Enlace"
title={m['common.help_link']()}
>
<LinkIcon class="h-4 w-4" />
</Button>
<label
class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
title="Subir Imagen"
title={m['common.help_upload_image']()}
>
<ImageIcon class="h-4 w-4" />
<input
@@ -423,7 +424,7 @@
id="markdown-editor"
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í..."
placeholder={m['common.help_ph_content']()}
ondrop={(e: DragEvent) => handleDrop(e)}
ondragover={(e: DragEvent) => e.preventDefault()}
></textarea>
@@ -432,7 +433,7 @@
<div
class="pointer-events-none absolute inset-0 m-4 hidden items-center justify-center rounded-lg border-2 border-dashed border-primary bg-primary/10 opacity-0 transition-opacity group-hover:flex"
>
<p class="font-medium text-primary">Arrastra imágenes aquí</p>
<p class="font-medium text-primary">{m['common.help_drag_images']()}</p>
</div>
</div>
@@ -453,19 +454,19 @@
<div class="rounded-full bg-red-100 p-6 dark:bg-red-900/20">
<FileText class="h-16 w-16 text-red-500" />
</div>
<h2 class="text-2xl font-bold">Configuración de PDF</h2>
<h2 class="text-2xl font-bold">{m['common.help_config_pdf']()}</h2>
{:else if content_type === 'video'}
<div class="rounded-full bg-blue-100 p-6 dark:bg-blue-900/20">
<VideoIcon class="h-16 w-16 text-blue-500" />
</div>
<h2 class="text-2xl font-bold">Configuración de Video</h2>
<h2 class="text-2xl font-bold">{m['common.help_config_video']()}</h2>
{:else}
<div class="rounded-full bg-primary/10 p-6">
<FileCode class="h-16 w-16 text-primary" />
</div>
<h2 class="text-2xl font-bold">Configuración de Documento</h2>
<h2 class="text-2xl font-bold">{m['common.help_config_document']()}</h2>
{/if}
<p class="text-muted-foreground">Sube el archivo que deseas asociar a este título.</p>
<p class="text-muted-foreground">{m['common.help_upload_hint']()}</p>
</div>
{#if file_url}
@@ -475,7 +476,7 @@
<FileCode class="h-6 w-6" />
</div>
<div class="flex-1 text-left">
<p class="font-medium">Archivo Cargado</p>
<p class="font-medium">{m['common.help_file_loaded']()}</p>
<p class="truncate text-xs text-muted-foreground">
{getFileNameFromPath(file_url)}
</p>
@@ -486,12 +487,12 @@
</div>
<div class="flex gap-2">
<Button variant="outline" class="flex-1" href={resolveAssetUrl(file_url)} target="_blank">
Ver Archivo
{m['common.help_view_file']()}
</Button>
<label
class="flex flex-1 cursor-pointer items-center justify-center rounded-md border border-input bg-background hover:bg-accent hover:text-accent-foreground"
>
Cambiar Archivo
{m['common.help_change_file']()}
<input
type="file"
class="hidden"
@@ -511,8 +512,8 @@
<div class="rounded-full bg-background p-4 shadow-sm">
<Upload class="h-8 w-8 text-primary" />
</div>
<span class="font-medium">Selecciona un archivo</span>
<span class="text-xs text-muted-foreground">O arrastra y suelta aquí</span>
<span class="font-medium">{m['common.help_select_file']()}</span>
<span class="text-xs text-muted-foreground">{m['common.help_drag_drop']()}</span>
</div>
<input
type="file"