mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: handle non-image modalities across uploads, previews, and filenames (#255)
SnapOtter spans five modalities now, but several code paths still assumed image input. - dropzone: default to accept-all when no fileFilter is given (image tools still pass one); neutral "supported file types" error text instead of "image files" - automate (pipelines): accept any modality in the file pickers and dropzones; render modality-aware previews (video player, audio waveform, document/data card) instead of always using ImageViewer/BeforeAfterSlider - filename sanitizer: extend the double-extension allowlist beyond image extensions to video/audio/document/data so e.g. "report.csv.php" becomes "report.csv"; add tests - thumbnail route: return 422 for non-rasterisable files (audio, data, non-PDF docs) instead of attempting a doomed Sharp decode - pool: unknown tools fall back to the "system" pool, not the image pool - a11y labels: "Previous/Next image", "Image viewer/area/controls/drop zone" are now modality-neutral, across all 21 locales - copy: bulk-rename default, find-duplicates ZIP name, SSRF user-agent, fetch-urls fallback name, file-details MIME label, URL-import placeholder, help dialog
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import { basename } from "node:path";
|
import { basename } from "node:path";
|
||||||
|
|
||||||
const SAFE_IMAGE_EXTENSIONS = new Set([
|
// Recognised, safe file extensions across all five modalities (image, video,
|
||||||
|
// audio, document, data). Used to truncate double-extension attacks after the
|
||||||
|
// first known-good extension, e.g. "report.csv.php" becomes "report.csv".
|
||||||
|
const SAFE_EXTENSIONS = new Set([
|
||||||
|
// Image
|
||||||
".jpg",
|
".jpg",
|
||||||
".jpeg",
|
".jpeg",
|
||||||
".png",
|
".png",
|
||||||
@@ -11,7 +15,65 @@ const SAFE_IMAGE_EXTENSIONS = new Set([
|
|||||||
".tif",
|
".tif",
|
||||||
".avif",
|
".avif",
|
||||||
".svg",
|
".svg",
|
||||||
|
".heic",
|
||||||
|
".heif",
|
||||||
|
".jxl",
|
||||||
|
".ico",
|
||||||
|
".jp2",
|
||||||
|
".qoi",
|
||||||
|
".psd",
|
||||||
|
".dng",
|
||||||
|
// Video
|
||||||
|
".mp4",
|
||||||
|
".webm",
|
||||||
|
".mov",
|
||||||
|
".mkv",
|
||||||
|
".avi",
|
||||||
|
".m4v",
|
||||||
|
".mpg",
|
||||||
|
".mpeg",
|
||||||
|
".wmv",
|
||||||
|
".flv",
|
||||||
|
".ogv",
|
||||||
|
// Audio
|
||||||
|
".mp3",
|
||||||
|
".wav",
|
||||||
|
".flac",
|
||||||
|
".ogg",
|
||||||
|
".oga",
|
||||||
|
".aac",
|
||||||
|
".m4a",
|
||||||
|
".opus",
|
||||||
|
".wma",
|
||||||
|
".aiff",
|
||||||
|
".aif",
|
||||||
|
// Document
|
||||||
".pdf",
|
".pdf",
|
||||||
|
".doc",
|
||||||
|
".docx",
|
||||||
|
".odt",
|
||||||
|
".rtf",
|
||||||
|
".txt",
|
||||||
|
".md",
|
||||||
|
".markdown",
|
||||||
|
".html",
|
||||||
|
".htm",
|
||||||
|
".epub",
|
||||||
|
".ppt",
|
||||||
|
".pptx",
|
||||||
|
".odp",
|
||||||
|
".xls",
|
||||||
|
".xlsx",
|
||||||
|
".ods",
|
||||||
|
// Data
|
||||||
|
".csv",
|
||||||
|
".tsv",
|
||||||
|
".json",
|
||||||
|
".xml",
|
||||||
|
".yaml",
|
||||||
|
".yml",
|
||||||
|
".zip",
|
||||||
|
".srt",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,8 +81,8 @@ const SAFE_IMAGE_EXTENSIONS = new Set([
|
|||||||
*
|
*
|
||||||
* 1. Strips directory separators (basename only).
|
* 1. Strips directory separators (basename only).
|
||||||
* 2. Removes ".." sequences and null bytes.
|
* 2. Removes ".." sequences and null bytes.
|
||||||
* 3. Truncates after the first recognised image extension so that
|
* 3. Truncates after the first recognised file extension so that
|
||||||
* "photo.png.php" becomes "photo.png".
|
* "report.csv.php" becomes "report.csv".
|
||||||
*/
|
*/
|
||||||
export function sanitizeFilename(raw: string): string {
|
export function sanitizeFilename(raw: string): string {
|
||||||
let name = basename(raw);
|
let name = basename(raw);
|
||||||
@@ -30,14 +92,14 @@ export function sanitizeFilename(raw: string): string {
|
|||||||
name = "upload";
|
name = "upload";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard against double-extension attacks (e.g. "image.png.php").
|
// Guard against double-extension attacks (e.g. "report.csv.php").
|
||||||
// Walk the dot-separated parts and truncate after the first safe image extension.
|
// Walk the dot-separated parts and truncate after the first safe extension.
|
||||||
const dotIndex = name.indexOf(".");
|
const dotIndex = name.indexOf(".");
|
||||||
if (dotIndex !== -1) {
|
if (dotIndex !== -1) {
|
||||||
const parts = name.split(".");
|
const parts = name.split(".");
|
||||||
for (let i = 1; i < parts.length; i++) {
|
for (let i = 1; i < parts.length; i++) {
|
||||||
const ext = `.${parts[i].toLowerCase()}`;
|
const ext = `.${parts[i].toLowerCase()}`;
|
||||||
if (SAFE_IMAGE_EXTENSIONS.has(ext)) {
|
if (SAFE_EXTENSIONS.has(ext)) {
|
||||||
// Keep everything up to and including this extension, drop the rest
|
// Keep everything up to and including this extension, drop the rest
|
||||||
name = parts.slice(0, i + 1).join(".");
|
name = parts.slice(0, i + 1).join(".");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import type { Pool } from "../jobs/types.js";
|
|||||||
export function resolveToolPool(toolId: string): Pool {
|
export function resolveToolPool(toolId: string): Pool {
|
||||||
if (hasAiJobHandler(toolId) || TOOL_BUNDLE_MAP[toolId]) return "ai";
|
if (hasAiJobHandler(toolId) || TOOL_BUNDLE_MAP[toolId]) return "ai";
|
||||||
const tool = TOOLS.find((t) => t.id === toolId);
|
const tool = TOOLS.find((t) => t.id === toolId);
|
||||||
return tool ? MODALITY_POOL[tool.modality] : "image";
|
// Unknown tools fall back to the general-purpose "system" pool rather than
|
||||||
|
// assuming the image pool (a legacy image-only default).
|
||||||
|
return tool ? MODALITY_POOL[tool.modality] : "system";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldSkipSyncWindow(executionHint: "fast" | "long" | undefined): boolean {
|
export function shouldSkipSyncWindow(executionHint: "fast" | "long" | undefined): boolean {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export async function safeFetch(url: string, signal?: AbortSignal): Promise<Resp
|
|||||||
signal,
|
signal,
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "SnapOtter/1.0 (image-fetch)",
|
"User-Agent": "SnapOtter/2.0 (file-fetch)",
|
||||||
Host: parsed.host,
|
Host: parsed.host,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -176,7 +176,7 @@ export async function safeFetch(url: string, signal?: AbortSignal): Promise<Resp
|
|||||||
agent,
|
agent,
|
||||||
signal: signal ?? undefined,
|
signal: signal ?? undefined,
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "SnapOtter/1.0 (image-fetch)",
|
"User-Agent": "SnapOtter/2.0 (file-fetch)",
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ function filenameFromUrl(url: string): string {
|
|||||||
} catch {
|
} catch {
|
||||||
// ignore parse errors
|
// ignore parse errors
|
||||||
}
|
}
|
||||||
return `image-${randomUUID().slice(0, 8)}`;
|
return `file-${randomUUID().slice(0, 8)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -524,6 +524,12 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
|
|
||||||
// Image thumbnail (existing path)
|
// Image thumbnail (existing path)
|
||||||
const validation = await validateImageBuffer(rawBuffer, file.originalName);
|
const validation = await validateImageBuffer(rawBuffer, file.originalName);
|
||||||
|
if (!validation.valid) {
|
||||||
|
// Audio, data, and non-PDF document files have no raster thumbnail.
|
||||||
|
// Return 422 (not 204) so the client's <img> onError falls back to a
|
||||||
|
// modality icon instead of attempting a doomed Sharp decode below.
|
||||||
|
return reply.status(422).send({ error: "No thumbnail available for this file type" });
|
||||||
|
}
|
||||||
let decoded: Buffer<ArrayBuffer> = Buffer.from(rawBuffer);
|
let decoded: Buffer<ArrayBuffer> = Buffer.from(rawBuffer);
|
||||||
if (validation.valid && validation.format === "heif") {
|
if (validation.valid && validation.format === "heif") {
|
||||||
decoded = Buffer.from(await decodeHeic(rawBuffer));
|
decoded = Buffer.from(await decodeHeic(rawBuffer));
|
||||||
|
|||||||
@@ -113,7 +113,10 @@ export function Dropzone({
|
|||||||
acceptDescription,
|
acceptDescription,
|
||||||
}: DropzoneProps) {
|
}: DropzoneProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const checkFile = fileFilter ?? isImageFile;
|
// When no explicit filter is given, accept any file: the file picker still
|
||||||
|
// restricts via `accept`, and the server validates per modality. Image tools
|
||||||
|
// pass an explicit fileFilter, so they are unaffected.
|
||||||
|
const checkFile = fileFilter ?? (() => true);
|
||||||
const resolvedAccept = expandAccept(accept);
|
const resolvedAccept = expandAccept(accept);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -168,7 +171,7 @@ export function Dropzone({
|
|||||||
if (validFiles.length > 0) {
|
if (validFiles.length > 0) {
|
||||||
onFiles?.(validFiles);
|
onFiles?.(validFiles);
|
||||||
} else if (droppedFiles.length > 0) {
|
} else if (droppedFiles.length > 0) {
|
||||||
setError(acceptDescription || `This tool accepts ${accept || "image files"}`);
|
setError(acceptDescription || `This tool accepts ${accept || "the supported file types"}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onFiles, checkFile, acceptDescription, accept],
|
[onFiles, checkFile, acceptDescription, accept],
|
||||||
@@ -186,7 +189,7 @@ export function Dropzone({
|
|||||||
if (validFiles.length > 0) {
|
if (validFiles.length > 0) {
|
||||||
onFiles?.(validFiles);
|
onFiles?.(validFiles);
|
||||||
} else if (picked.length > 0) {
|
} else if (picked.length > 0) {
|
||||||
setError(acceptDescription || `This tool accepts ${accept || "image files"}`);
|
setError(acceptDescription || `This tool accepts ${accept || "the supported file types"}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export function UrlImportModal({ onClose, onImport }: UrlImportModalProps) {
|
|||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
placeholder={
|
placeholder={
|
||||||
"https://example.com/photo1.jpg\nhttps://example.com/photo2.png\n- https://example.com/photo3.webp\n[My image](https://example.com/photo4.jpg)"
|
"https://example.com/photo.jpg\nhttps://example.com/report.pdf\n- https://example.com/clip.mp4\n[My file](https://example.com/recording.mp3)"
|
||||||
}
|
}
|
||||||
className="w-full min-h-[120px] max-h-[240px] resize-y rounded-lg border border-border bg-muted px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
|
className="w-full min-h-[120px] max-h-[240px] resize-y rounded-lg border border-border bg-muted px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -502,7 +502,7 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
|
|||||||
<DetailRow label={t.files.name} value={details.originalName} />
|
<DetailRow label={t.files.name} value={details.originalName} />
|
||||||
<DetailRow
|
<DetailRow
|
||||||
label={t.files.format}
|
label={t.files.format}
|
||||||
value={details.mimeType.replace("image/", "").toUpperCase()}
|
value={details.mimeType.split("/").pop()?.toUpperCase() ?? ""}
|
||||||
/>
|
/>
|
||||||
<DetailRow label={t.files.size} value={formatSize(details.size)} />
|
<DetailRow label={t.files.size} value={formatSize(details.size)} />
|
||||||
<DetailRow
|
<DetailRow
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ export function HelpDialog({ open, onClose }: HelpDialogProps) {
|
|||||||
<h3 className="text-sm font-semibold">{t.help.gettingStarted.heading}</h3>
|
<h3 className="text-sm font-semibold">{t.help.gettingStarted.heading}</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Select a tool from the sidebar or search for one with <Kbd keys="mod+k" />. Upload an
|
Select a tool from the sidebar or search for one with <Kbd keys="mod+k" />. Upload a
|
||||||
image by dragging it onto the page or clicking the upload area. Adjust settings and
|
file by dragging it onto the page or clicking the upload area. Adjust settings and
|
||||||
download your result.
|
download your result.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useFileStore } from "@/stores/file-store";
|
|||||||
export function BulkRenameSettings() {
|
export function BulkRenameSettings() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { files, processing, error, setProcessing, setError } = useFileStore();
|
const { files, processing, error, setProcessing, setError } = useFileStore();
|
||||||
const [pattern, setPattern] = useState("image-{{index}}");
|
const [pattern, setPattern] = useState("file-{{index}}");
|
||||||
const [startIndex, setStartIndex] = useState(1);
|
const [startIndex, setStartIndex] = useState(1);
|
||||||
const [downloadReady, setDownloadReady] = useState(false);
|
const [downloadReady, setDownloadReady] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function FindDuplicatesSettings() {
|
|||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = "unique-images.zip";
|
a.download = "unique-files.zip";
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}, [files, results, bestOverrides]);
|
}, [files, results, bestOverrides]);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
Download,
|
Download,
|
||||||
|
FileImage,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
Layers,
|
Layers,
|
||||||
Play,
|
Play,
|
||||||
@@ -15,7 +16,7 @@ import {
|
|||||||
Workflow,
|
Workflow,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { BeforeAfterSlider } from "@/components/common/before-after-slider";
|
import { BeforeAfterSlider } from "@/components/common/before-after-slider";
|
||||||
import { Dropzone } from "@/components/common/dropzone";
|
import { Dropzone } from "@/components/common/dropzone";
|
||||||
@@ -37,6 +38,13 @@ import { cn } from "@/lib/utils";
|
|||||||
import { useFileStore } from "@/stores/file-store";
|
import { useFileStore } from "@/stores/file-store";
|
||||||
import { type SavedPipeline, usePipelineStore } from "@/stores/pipeline-store";
|
import { type SavedPipeline, usePipelineStore } from "@/stores/pipeline-store";
|
||||||
|
|
||||||
|
const MediaPlayerView = lazy(() =>
|
||||||
|
import("@/components/tools/media-player-view").then((m) => ({ default: m.MediaPlayerView })),
|
||||||
|
);
|
||||||
|
const WaveformPlayer = lazy(() =>
|
||||||
|
import("@/components/common/waveform-player").then((m) => ({ default: m.WaveformPlayer })),
|
||||||
|
);
|
||||||
|
|
||||||
export function AutomatePage() {
|
export function AutomatePage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
usePageTitle(t.sidebar.automate);
|
usePageTitle(t.sidebar.automate);
|
||||||
@@ -167,7 +175,6 @@ export function AutomatePage() {
|
|||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.multiple = true;
|
input.multiple = true;
|
||||||
input.accept = "image/*,.avif,.heic,.heif,.hif";
|
|
||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const picked = Array.from((e.target as HTMLInputElement).files || []);
|
const picked = Array.from((e.target as HTMLInputElement).files || []);
|
||||||
if (picked.length > 0) addFiles(picked);
|
if (picked.length > 0) addFiles(picked);
|
||||||
@@ -361,6 +368,102 @@ export function AutomatePage() {
|
|||||||
[addStep, isMobile],
|
[addStep, isMobile],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render modality-aware preview for the pipeline result or source file.
|
||||||
|
* Mirrors the previewKind switching in tool-page.tsx: images get
|
||||||
|
* BeforeAfterSlider / ImageViewer, video gets MediaPlayerView, audio
|
||||||
|
* gets WaveformPlayer, and document/data get a static info card.
|
||||||
|
*/
|
||||||
|
function renderPipelinePreview(mode: "result" | "original") {
|
||||||
|
const kind = currentEntry?.previewKind ?? "image";
|
||||||
|
|
||||||
|
if (mode === "result") {
|
||||||
|
if (kind === "image") {
|
||||||
|
return (
|
||||||
|
<BeforeAfterSlider
|
||||||
|
beforeSrc={originalBlobUrl!}
|
||||||
|
afterSrc={processedUrl as string}
|
||||||
|
beforeSize={originalSize ?? undefined}
|
||||||
|
afterSize={processedSize ?? undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (kind === "video") {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
|
||||||
|
<MediaPlayerView />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (kind === "audio") {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
|
||||||
|
<WaveformPlayer src={processedUrl as string} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// document / data: success card with filename + size
|
||||||
|
const fname = currentEntry?.processedFilename ?? selectedFileName ?? files[0]?.name ?? "file";
|
||||||
|
const fsize = processedSize ?? 0;
|
||||||
|
const ext = fname.split(".").pop()?.toUpperCase() ?? "";
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<div className="text-center p-6 max-w-xs">
|
||||||
|
<div className="mx-auto w-14 h-14 rounded-2xl bg-emerald-50 dark:bg-emerald-950/30 flex items-center justify-center mb-3">
|
||||||
|
<CheckCircle2 className="h-7 w-7 text-emerald-600 dark:text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<p className="font-medium text-foreground mb-1">{fname}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{ext} · {formatFileSize(fsize)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode === "original"
|
||||||
|
if (kind === "image") {
|
||||||
|
return (
|
||||||
|
<ImageViewer
|
||||||
|
src={originalBlobUrl!}
|
||||||
|
filename={selectedFileName ?? files[0].name}
|
||||||
|
fileSize={selectedFileSize ?? files[0].size}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (kind === "video") {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
|
||||||
|
<MediaPlayerView />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (kind === "audio") {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
|
||||||
|
<WaveformPlayer src={originalBlobUrl!} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// document / data: placeholder card
|
||||||
|
const fname = selectedFileName ?? files[0]?.name ?? "file";
|
||||||
|
const fsize = selectedFileSize ?? files[0]?.size ?? 0;
|
||||||
|
const ext = fname.split(".").pop()?.toUpperCase() ?? "";
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<div className="text-center p-6 max-w-xs">
|
||||||
|
<div className="mx-auto w-14 h-14 rounded-2xl bg-muted flex items-center justify-center mb-3">
|
||||||
|
<FileImage className="h-7 w-7 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<p className="font-medium text-foreground mb-1">{fname}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{ext} · {formatFileSize(fsize)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Mobile Layout */
|
/* Mobile Layout */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -383,7 +486,12 @@ export function AutomatePage() {
|
|||||||
<div className="flex-1 overflow-y-auto px-4 py-3">
|
<div className="flex-1 overflow-y-auto px-4 py-3">
|
||||||
{!hasFile && (
|
{!hasFile && (
|
||||||
<div className="mb-4 space-y-2">
|
<div className="mb-4 space-y-2">
|
||||||
<Dropzone onFiles={handleFiles} accept="image/*" multiple currentFiles={files} />
|
<Dropzone
|
||||||
|
onFiles={handleFiles}
|
||||||
|
multiple
|
||||||
|
currentFiles={files}
|
||||||
|
fileFilter={() => true}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLibraryModalOpen(true)}
|
onClick={() => setLibraryModalOpen(true)}
|
||||||
@@ -425,18 +533,11 @@ export function AutomatePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mobile image preview / result */}
|
{/* Mobile preview / result */}
|
||||||
{hasFile && hasProcessed && originalBlobUrl && (
|
{hasFile && hasProcessed && originalBlobUrl && (
|
||||||
<div className="mb-3 rounded-lg border border-border overflow-hidden">
|
<div className="mb-3 rounded-lg border border-border overflow-hidden">
|
||||||
<div className="relative h-48">
|
<div className="relative h-48">{renderPipelinePreview("result")}</div>
|
||||||
<BeforeAfterSlider
|
{processedSize != null && currentEntry?.previewKind === "image" && (
|
||||||
beforeSrc={originalBlobUrl}
|
|
||||||
afterSrc={processedUrl as string}
|
|
||||||
beforeSize={originalSize ?? undefined}
|
|
||||||
afterSize={processedSize ?? undefined}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{processedSize != null && (
|
|
||||||
<div className="flex items-center justify-between px-3 py-1.5 border-t border-border text-xs text-muted-foreground">
|
<div className="flex items-center justify-between px-3 py-1.5 border-t border-border text-xs text-muted-foreground">
|
||||||
<span className="truncate">{selectedFileName ?? files[0].name}</span>
|
<span className="truncate">{selectedFileName ?? files[0].name}</span>
|
||||||
<span>
|
<span>
|
||||||
@@ -449,11 +550,7 @@ export function AutomatePage() {
|
|||||||
|
|
||||||
{hasFile && !hasProcessed && originalBlobUrl && currentEntry?.status !== "failed" && (
|
{hasFile && !hasProcessed && originalBlobUrl && currentEntry?.status !== "failed" && (
|
||||||
<div className="mb-3 rounded-lg border border-border overflow-hidden h-40 flex items-center justify-center bg-muted/20">
|
<div className="mb-3 rounded-lg border border-border overflow-hidden h-40 flex items-center justify-center bg-muted/20">
|
||||||
<ImageViewer
|
{renderPipelinePreview("original")}
|
||||||
src={originalBlobUrl}
|
|
||||||
filename={selectedFileName ?? files[0].name}
|
|
||||||
fileSize={selectedFileSize ?? files[0].size}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -949,10 +1046,10 @@ export function AutomatePage() {
|
|||||||
<div className="w-full max-h-[120px] overflow-hidden">
|
<div className="w-full max-h-[120px] overflow-hidden">
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onFiles={handleFiles}
|
onFiles={handleFiles}
|
||||||
accept="image/*"
|
|
||||||
multiple
|
multiple
|
||||||
currentFiles={files}
|
currentFiles={files}
|
||||||
compact
|
compact
|
||||||
|
fileFilter={() => true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -974,25 +1071,13 @@ export function AutomatePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasFile && hasProcessed && originalBlobUrl && (
|
{hasFile && hasProcessed && originalBlobUrl && renderPipelinePreview("result")}
|
||||||
<BeforeAfterSlider
|
|
||||||
beforeSrc={originalBlobUrl}
|
|
||||||
afterSrc={processedUrl as string}
|
|
||||||
beforeSize={originalSize ?? undefined}
|
|
||||||
afterSize={processedSize ?? undefined}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasFile &&
|
{hasFile &&
|
||||||
!hasProcessed &&
|
!hasProcessed &&
|
||||||
originalBlobUrl &&
|
originalBlobUrl &&
|
||||||
currentEntry?.status !== "failed" && (
|
currentEntry?.status !== "failed" &&
|
||||||
<ImageViewer
|
renderPipelinePreview("original")}
|
||||||
src={originalBlobUrl}
|
|
||||||
filename={selectedFileName ?? files[0].name}
|
|
||||||
fileSize={selectedFileSize ?? files[0].size}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasMultiple && (
|
{hasMultiple && (
|
||||||
|
|||||||
@@ -2436,13 +2436,13 @@ export const ar: TranslationKeys = {
|
|||||||
generatingPreview: "جاري إنشاء المعاينة...",
|
generatingPreview: "جاري إنشاء المعاينة...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"لا يمكن معاينة ملفات {ext} في المتصفح. ستتم معالجة الملف بشكل طبيعي.",
|
"لا يمكن معاينة ملفات {ext} في المتصفح. ستتم معالجة الملف بشكل طبيعي.",
|
||||||
previousImage: "الصورة السابقة",
|
previousImage: "الملف السابق",
|
||||||
nextImage: "الصورة التالية",
|
nextImage: "الملف التالي",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "الإعدادات",
|
settingsLabel: "الإعدادات",
|
||||||
downloadAllZip: "تحميل الكل (ZIP)",
|
downloadAllZip: "تحميل الكل (ZIP)",
|
||||||
hideSettings: "إخفاء الإعدادات",
|
hideSettings: "إخفاء الإعدادات",
|
||||||
imageArea: "منطقة الصورة",
|
imageArea: "منطقة المعاينة",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3176,10 +3176,10 @@ export const ar: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "إلغاء",
|
cancelButton: "إلغاء",
|
||||||
preview: "معاينة",
|
preview: "معاينة",
|
||||||
previousImage: "الصورة السابقة",
|
previousImage: "الملف السابق",
|
||||||
nextImage: "الصورة التالية",
|
nextImage: "الملف التالي",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "منطقة الصورة",
|
imageArea: "منطقة المعاينة",
|
||||||
templateSocialMedia: "جاهز لوسائل التواصل",
|
templateSocialMedia: "جاهز لوسائل التواصل",
|
||||||
templatePrivacyClean: "تنظيف الخصوصية",
|
templatePrivacyClean: "تنظيف الخصوصية",
|
||||||
templateWebOptimization: "تحسين للويب",
|
templateWebOptimization: "تحسين للويب",
|
||||||
@@ -3399,14 +3399,14 @@ export const ar: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "الملف السابق",
|
||||||
nextImage: "Next image",
|
nextImage: "الملف التالي",
|
||||||
imageArea: "Image area",
|
imageArea: "منطقة المعاينة",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "عارض الملفات",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "منطقة إسقاط الملفات",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "عناصر تحكم المعاينة",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2450,13 +2450,13 @@ export const de: TranslationKeys = {
|
|||||||
generatingPreview: "Vorschau wird generiert...",
|
generatingPreview: "Vorschau wird generiert...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"{ext}-Dateien koennen im Browser nicht angezeigt werden. Das Werkzeug verarbeitet diese Datei trotzdem.",
|
"{ext}-Dateien koennen im Browser nicht angezeigt werden. Das Werkzeug verarbeitet diese Datei trotzdem.",
|
||||||
previousImage: "Vorheriges Bild",
|
previousImage: "Vorherige Datei",
|
||||||
nextImage: "Naechstes Bild",
|
nextImage: "Naechste Datei",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Einstellungen",
|
settingsLabel: "Einstellungen",
|
||||||
downloadAllZip: "Alle herunterladen (ZIP)",
|
downloadAllZip: "Alle herunterladen (ZIP)",
|
||||||
hideSettings: "Einstellungen ausblenden",
|
hideSettings: "Einstellungen ausblenden",
|
||||||
imageArea: "Bildbereich",
|
imageArea: "Vorschaubereich",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3205,10 +3205,10 @@ export const de: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Abbrechen",
|
cancelButton: "Abbrechen",
|
||||||
preview: "Vorschau",
|
preview: "Vorschau",
|
||||||
previousImage: "Vorheriges Bild",
|
previousImage: "Vorherige Datei",
|
||||||
nextImage: "Naechstes Bild",
|
nextImage: "Naechste Datei",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Bildbereich",
|
imageArea: "Vorschaubereich",
|
||||||
templateSocialMedia: "Social Media fertig",
|
templateSocialMedia: "Social Media fertig",
|
||||||
templatePrivacyClean: "Datenschutzbereinigung",
|
templatePrivacyClean: "Datenschutzbereinigung",
|
||||||
templateWebOptimization: "Weboptimierung",
|
templateWebOptimization: "Weboptimierung",
|
||||||
@@ -3430,14 +3430,14 @@ export const de: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Vorherige Datei",
|
||||||
nextImage: "Next image",
|
nextImage: "Naechste Datei",
|
||||||
imageArea: "Image area",
|
imageArea: "Vorschaubereich",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Dateibetrachter",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Datei-Ablagezone",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Vorschausteuerung",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2400,13 +2400,13 @@ export const en = {
|
|||||||
generatingPreview: "Generating preview...",
|
generatingPreview: "Generating preview...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"{ext} files cannot be previewed in the browser. The tool will still process this file normally.",
|
"{ext} files cannot be previewed in the browser. The tool will still process this file normally.",
|
||||||
previousImage: "Previous image",
|
previousImage: "Previous file",
|
||||||
nextImage: "Next image",
|
nextImage: "Next file",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Settings",
|
settingsLabel: "Settings",
|
||||||
downloadAllZip: "Download All (ZIP)",
|
downloadAllZip: "Download All (ZIP)",
|
||||||
hideSettings: "Hide Settings",
|
hideSettings: "Hide Settings",
|
||||||
imageArea: "Image area",
|
imageArea: "Preview area",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3142,10 +3142,10 @@ export const en = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Cancel",
|
cancelButton: "Cancel",
|
||||||
preview: "Preview",
|
preview: "Preview",
|
||||||
previousImage: "Previous image",
|
previousImage: "Previous file",
|
||||||
nextImage: "Next image",
|
nextImage: "Next file",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Image area",
|
imageArea: "Preview area",
|
||||||
templateSocialMedia: "Social Media Ready",
|
templateSocialMedia: "Social Media Ready",
|
||||||
templatePrivacyClean: "Privacy Clean",
|
templatePrivacyClean: "Privacy Clean",
|
||||||
templateWebOptimization: "Web Optimization",
|
templateWebOptimization: "Web Optimization",
|
||||||
@@ -3366,14 +3366,14 @@ export const en = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Previous file",
|
||||||
nextImage: "Next image",
|
nextImage: "Next file",
|
||||||
imageArea: "Image area",
|
imageArea: "Preview area",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "File viewer",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "File drop zone",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Preview controls",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2432,13 +2432,13 @@ export const es: TranslationKeys = {
|
|||||||
generatingPreview: "Generando vista previa...",
|
generatingPreview: "Generando vista previa...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Los archivos {ext} no se pueden previsualizar en el navegador. La herramienta procesara este archivo normalmente.",
|
"Los archivos {ext} no se pueden previsualizar en el navegador. La herramienta procesara este archivo normalmente.",
|
||||||
previousImage: "Imagen anterior",
|
previousImage: "Archivo anterior",
|
||||||
nextImage: "Siguiente imagen",
|
nextImage: "Siguiente archivo",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Configuracion",
|
settingsLabel: "Configuracion",
|
||||||
downloadAllZip: "Descargar todo (ZIP)",
|
downloadAllZip: "Descargar todo (ZIP)",
|
||||||
hideSettings: "Ocultar configuracion",
|
hideSettings: "Ocultar configuracion",
|
||||||
imageArea: "Area de imagen",
|
imageArea: "Area de vista previa",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3182,10 +3182,10 @@ export const es: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Cancelar",
|
cancelButton: "Cancelar",
|
||||||
preview: "Vista previa",
|
preview: "Vista previa",
|
||||||
previousImage: "Imagen anterior",
|
previousImage: "Archivo anterior",
|
||||||
nextImage: "Siguiente imagen",
|
nextImage: "Siguiente archivo",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Area de imagen",
|
imageArea: "Area de vista previa",
|
||||||
templateSocialMedia: "Listo para redes sociales",
|
templateSocialMedia: "Listo para redes sociales",
|
||||||
templatePrivacyClean: "Limpieza de privacidad",
|
templatePrivacyClean: "Limpieza de privacidad",
|
||||||
templateWebOptimization: "Optimizacion web",
|
templateWebOptimization: "Optimizacion web",
|
||||||
@@ -3408,14 +3408,14 @@ export const es: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Archivo anterior",
|
||||||
nextImage: "Next image",
|
nextImage: "Siguiente archivo",
|
||||||
imageArea: "Image area",
|
imageArea: "Area de vista previa",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Visor de archivos",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Zona para soltar archivos",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Controles de vista previa",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2451,13 +2451,13 @@ export const fr: TranslationKeys = {
|
|||||||
generatingPreview: "Generation de l'apercu...",
|
generatingPreview: "Generation de l'apercu...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Les fichiers {ext} ne peuvent pas etre previsualises dans le navigateur. L'outil traitera ce fichier normalement.",
|
"Les fichiers {ext} ne peuvent pas etre previsualises dans le navigateur. L'outil traitera ce fichier normalement.",
|
||||||
previousImage: "Image precedente",
|
previousImage: "Fichier precedent",
|
||||||
nextImage: "Image suivante",
|
nextImage: "Fichier suivant",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Parametres",
|
settingsLabel: "Parametres",
|
||||||
downloadAllZip: "Tout telecharger (ZIP)",
|
downloadAllZip: "Tout telecharger (ZIP)",
|
||||||
hideSettings: "Masquer les parametres",
|
hideSettings: "Masquer les parametres",
|
||||||
imageArea: "Zone d'image",
|
imageArea: "Zone d'apercu",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3203,10 +3203,10 @@ export const fr: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Annuler",
|
cancelButton: "Annuler",
|
||||||
preview: "Apercu",
|
preview: "Apercu",
|
||||||
previousImage: "Image precedente",
|
previousImage: "Fichier precedent",
|
||||||
nextImage: "Image suivante",
|
nextImage: "Fichier suivant",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Zone d'image",
|
imageArea: "Zone d'apercu",
|
||||||
templateSocialMedia: "Pret pour les reseaux sociaux",
|
templateSocialMedia: "Pret pour les reseaux sociaux",
|
||||||
templatePrivacyClean: "Nettoyage de confidentialite",
|
templatePrivacyClean: "Nettoyage de confidentialite",
|
||||||
templateWebOptimization: "Optimisation web",
|
templateWebOptimization: "Optimisation web",
|
||||||
@@ -3429,14 +3429,14 @@ export const fr: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Fichier precedent",
|
||||||
nextImage: "Next image",
|
nextImage: "Fichier suivant",
|
||||||
imageArea: "Image area",
|
imageArea: "Zone d'apercu",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Visionneuse de fichiers",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Zone de depot de fichiers",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Controles d'apercu",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2433,13 +2433,13 @@ export const hi: TranslationKeys = {
|
|||||||
generatingPreview: "प्रीव्यू जनरेट हो रहा है...",
|
generatingPreview: "प्रीव्यू जनरेट हो रहा है...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"{ext} फाइलें ब्राउज़र में प्रीव्यू नहीं हो सकतीं। टूल इस फाइल को सामान्य रूप से प्रोसेस करेगा।",
|
"{ext} फाइलें ब्राउज़र में प्रीव्यू नहीं हो सकतीं। टूल इस फाइल को सामान्य रूप से प्रोसेस करेगा।",
|
||||||
previousImage: "पिछली इमेज",
|
previousImage: "पिछली फ़ाइल",
|
||||||
nextImage: "अगली इमेज",
|
nextImage: "अगली फ़ाइल",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "सेटिंग्स",
|
settingsLabel: "सेटिंग्स",
|
||||||
downloadAllZip: "सभी डाउनलोड करें (ZIP)",
|
downloadAllZip: "सभी डाउनलोड करें (ZIP)",
|
||||||
hideSettings: "सेटिंग्स छुपाएं",
|
hideSettings: "सेटिंग्स छुपाएं",
|
||||||
imageArea: "इमेज क्षेत्र",
|
imageArea: "पूर्वावलोकन क्षेत्र",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3173,10 +3173,10 @@ export const hi: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "रद्द करें",
|
cancelButton: "रद्द करें",
|
||||||
preview: "प्रीव्यू",
|
preview: "प्रीव्यू",
|
||||||
previousImage: "पिछली इमेज",
|
previousImage: "पिछली फ़ाइल",
|
||||||
nextImage: "अगली इमेज",
|
nextImage: "अगली फ़ाइल",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "इमेज क्षेत्र",
|
imageArea: "पूर्वावलोकन क्षेत्र",
|
||||||
templateSocialMedia: "सोशल मीडिया रेडी",
|
templateSocialMedia: "सोशल मीडिया रेडी",
|
||||||
templatePrivacyClean: "प्राइवेसी क्लीन",
|
templatePrivacyClean: "प्राइवेसी क्लीन",
|
||||||
templateWebOptimization: "वेब ऑप्टिमाइज़ेशन",
|
templateWebOptimization: "वेब ऑप्टिमाइज़ेशन",
|
||||||
@@ -3396,14 +3396,14 @@ export const hi: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "पिछली फ़ाइल",
|
||||||
nextImage: "Next image",
|
nextImage: "अगली फ़ाइल",
|
||||||
imageArea: "Image area",
|
imageArea: "पूर्वावलोकन क्षेत्र",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "फ़ाइल व्यूअर",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "फ़ाइल ड्रॉप ज़ोन",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "पूर्वावलोकन नियंत्रण",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2448,13 +2448,13 @@ export const pl: TranslationKeys = {
|
|||||||
generatingPreview: "Generowanie podglądu...",
|
generatingPreview: "Generowanie podglądu...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Plików {ext} nie można podejrzeć w przeglądarce. Narzędzie nadal przetworzy ten plik.",
|
"Plików {ext} nie można podejrzeć w przeglądarce. Narzędzie nadal przetworzy ten plik.",
|
||||||
previousImage: "Poprzedni obraz",
|
previousImage: "Poprzedni plik",
|
||||||
nextImage: "Następny obraz",
|
nextImage: "Następny plik",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Ustawienia",
|
settingsLabel: "Ustawienia",
|
||||||
downloadAllZip: "Pobierz wszystko (ZIP)",
|
downloadAllZip: "Pobierz wszystko (ZIP)",
|
||||||
hideSettings: "Ukryj ustawienia",
|
hideSettings: "Ukryj ustawienia",
|
||||||
imageArea: "Obszar obrazu",
|
imageArea: "Obszar podglądu",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3200,10 +3200,10 @@ export const pl: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Anuluj",
|
cancelButton: "Anuluj",
|
||||||
preview: "Podgląd",
|
preview: "Podgląd",
|
||||||
previousImage: "Poprzedni obraz",
|
previousImage: "Poprzedni plik",
|
||||||
nextImage: "Następny obraz",
|
nextImage: "Następny plik",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Obszar obrazu",
|
imageArea: "Obszar podglądu",
|
||||||
templateSocialMedia: "Gotowe dla mediów społecznościowych",
|
templateSocialMedia: "Gotowe dla mediów społecznościowych",
|
||||||
templatePrivacyClean: "Czyszczenie prywatności",
|
templatePrivacyClean: "Czyszczenie prywatności",
|
||||||
templateWebOptimization: "Optymalizacja webowa",
|
templateWebOptimization: "Optymalizacja webowa",
|
||||||
@@ -3425,14 +3425,14 @@ export const pl: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Poprzedni plik",
|
||||||
nextImage: "Next image",
|
nextImage: "Następny plik",
|
||||||
imageArea: "Image area",
|
imageArea: "Obszar podglądu",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Przeglądarka plików",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Strefa upuszczania plików",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Sterowanie podglądem",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2444,13 +2444,13 @@ export const ptBR: TranslationKeys = {
|
|||||||
generatingPreview: "Gerando visualizacao...",
|
generatingPreview: "Gerando visualizacao...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Arquivos {ext} nao podem ser visualizados no navegador. A ferramenta processara este arquivo normalmente.",
|
"Arquivos {ext} nao podem ser visualizados no navegador. A ferramenta processara este arquivo normalmente.",
|
||||||
previousImage: "Imagem anterior",
|
previousImage: "Arquivo anterior",
|
||||||
nextImage: "Proxima imagem",
|
nextImage: "Proximo arquivo",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Configuracoes",
|
settingsLabel: "Configuracoes",
|
||||||
downloadAllZip: "Baixar tudo (ZIP)",
|
downloadAllZip: "Baixar tudo (ZIP)",
|
||||||
hideSettings: "Ocultar configuracoes",
|
hideSettings: "Ocultar configuracoes",
|
||||||
imageArea: "Area da imagem",
|
imageArea: "Area de visualizacao",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3193,10 +3193,10 @@ export const ptBR: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Cancelar",
|
cancelButton: "Cancelar",
|
||||||
preview: "Visualizacao",
|
preview: "Visualizacao",
|
||||||
previousImage: "Imagem anterior",
|
previousImage: "Arquivo anterior",
|
||||||
nextImage: "Proxima imagem",
|
nextImage: "Proximo arquivo",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Area da imagem",
|
imageArea: "Area de visualizacao",
|
||||||
templateSocialMedia: "Pronto para redes sociais",
|
templateSocialMedia: "Pronto para redes sociais",
|
||||||
templatePrivacyClean: "Limpeza de privacidade",
|
templatePrivacyClean: "Limpeza de privacidade",
|
||||||
templateWebOptimization: "Otimizacao para web",
|
templateWebOptimization: "Otimizacao para web",
|
||||||
@@ -3418,14 +3418,14 @@ export const ptBR: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Arquivo anterior",
|
||||||
nextImage: "Next image",
|
nextImage: "Proximo arquivo",
|
||||||
imageArea: "Image area",
|
imageArea: "Area de visualizacao",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Visualizador de arquivos",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Zona de soltar arquivos",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Controles de visualizacao",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2446,13 +2446,13 @@ export const ru: TranslationKeys = {
|
|||||||
generatingPreview: "Генерация предпросмотра...",
|
generatingPreview: "Генерация предпросмотра...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Файлы {ext} невозможно просмотреть в браузере. Инструмент всё равно обработает этот файл.",
|
"Файлы {ext} невозможно просмотреть в браузере. Инструмент всё равно обработает этот файл.",
|
||||||
previousImage: "Предыдущее изображение",
|
previousImage: "Предыдущий файл",
|
||||||
nextImage: "Следующее изображение",
|
nextImage: "Следующий файл",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Настройки",
|
settingsLabel: "Настройки",
|
||||||
downloadAllZip: "Скачать всё (ZIP)",
|
downloadAllZip: "Скачать всё (ZIP)",
|
||||||
hideSettings: "Скрыть настройки",
|
hideSettings: "Скрыть настройки",
|
||||||
imageArea: "Область изображения",
|
imageArea: "Область предпросмотра",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3192,10 +3192,10 @@ export const ru: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Отмена",
|
cancelButton: "Отмена",
|
||||||
preview: "Предпросмотр",
|
preview: "Предпросмотр",
|
||||||
previousImage: "Предыдущее изображение",
|
previousImage: "Предыдущий файл",
|
||||||
nextImage: "Следующее изображение",
|
nextImage: "Следующий файл",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Область изображения",
|
imageArea: "Область предпросмотра",
|
||||||
templateSocialMedia: "Готово для соцсетей",
|
templateSocialMedia: "Готово для соцсетей",
|
||||||
templatePrivacyClean: "Очистка конфиденциальности",
|
templatePrivacyClean: "Очистка конфиденциальности",
|
||||||
templateWebOptimization: "Веб-оптимизация",
|
templateWebOptimization: "Веб-оптимизация",
|
||||||
@@ -3416,14 +3416,14 @@ export const ru: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Предыдущий файл",
|
||||||
nextImage: "Next image",
|
nextImage: "Следующий файл",
|
||||||
imageArea: "Image area",
|
imageArea: "Область предпросмотра",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Просмотр файла",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Зона перетаскивания файлов",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Управление предпросмотром",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2443,13 +2443,13 @@ export const sv: TranslationKeys = {
|
|||||||
generatingPreview: "Genererar forhandsvisning...",
|
generatingPreview: "Genererar forhandsvisning...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"{ext}-filer kan inte forhandsvisas i webblasaren. Verktyget bearbetar anda denna fil.",
|
"{ext}-filer kan inte forhandsvisas i webblasaren. Verktyget bearbetar anda denna fil.",
|
||||||
previousImage: "Foregaende bild",
|
previousImage: "Foregaende fil",
|
||||||
nextImage: "Nasta bild",
|
nextImage: "Nasta fil",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Installningar",
|
settingsLabel: "Installningar",
|
||||||
downloadAllZip: "Ladda ner alla (ZIP)",
|
downloadAllZip: "Ladda ner alla (ZIP)",
|
||||||
hideSettings: "Dolj installningar",
|
hideSettings: "Dolj installningar",
|
||||||
imageArea: "Bildomrade",
|
imageArea: "Forhandsvisningsomrade",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3187,10 +3187,10 @@ export const sv: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Avbryt",
|
cancelButton: "Avbryt",
|
||||||
preview: "Forhandsvisning",
|
preview: "Forhandsvisning",
|
||||||
previousImage: "Foregaende bild",
|
previousImage: "Foregaende fil",
|
||||||
nextImage: "Nasta bild",
|
nextImage: "Nasta fil",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Bildomrade",
|
imageArea: "Forhandsvisningsomrade",
|
||||||
templateSocialMedia: "Sociala medier-redo",
|
templateSocialMedia: "Sociala medier-redo",
|
||||||
templatePrivacyClean: "Integritetsrensning",
|
templatePrivacyClean: "Integritetsrensning",
|
||||||
templateWebOptimization: "Webboptimering",
|
templateWebOptimization: "Webboptimering",
|
||||||
@@ -3411,14 +3411,14 @@ export const sv: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Foregaende fil",
|
||||||
nextImage: "Next image",
|
nextImage: "Nasta fil",
|
||||||
imageArea: "Image area",
|
imageArea: "Forhandsvisningsomrade",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Filvisare",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Filslappzon",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Forhandsvisningskontroller",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2425,13 +2425,13 @@ export const th: TranslationKeys = {
|
|||||||
generatingPreview: "กำลังสร้างตัวอย่าง...",
|
generatingPreview: "กำลังสร้างตัวอย่าง...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"ไม่สามารถแสดงตัวอย่างไฟล์ {ext} ในเบราว์เซอร์ได้ เครื่องมือจะยังประมวลผลไฟล์นี้ตามปกติ",
|
"ไม่สามารถแสดงตัวอย่างไฟล์ {ext} ในเบราว์เซอร์ได้ เครื่องมือจะยังประมวลผลไฟล์นี้ตามปกติ",
|
||||||
previousImage: "ภาพก่อนหน้า",
|
previousImage: "ไฟล์ก่อนหน้า",
|
||||||
nextImage: "ภาพถัดไป",
|
nextImage: "ไฟล์ถัดไป",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "ตั้งค่า",
|
settingsLabel: "ตั้งค่า",
|
||||||
downloadAllZip: "ดาวน์โหลดทั้งหมด (ZIP)",
|
downloadAllZip: "ดาวน์โหลดทั้งหมด (ZIP)",
|
||||||
hideSettings: "ซ่อนการตั้งค่า",
|
hideSettings: "ซ่อนการตั้งค่า",
|
||||||
imageArea: "พื้นที่ภาพ",
|
imageArea: "พื้นที่แสดงตัวอย่าง",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3164,10 +3164,10 @@ export const th: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "ยกเลิก",
|
cancelButton: "ยกเลิก",
|
||||||
preview: "ตัวอย่าง",
|
preview: "ตัวอย่าง",
|
||||||
previousImage: "ภาพก่อนหน้า",
|
previousImage: "ไฟล์ก่อนหน้า",
|
||||||
nextImage: "ภาพถัดไป",
|
nextImage: "ไฟล์ถัดไป",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "พื้นที่ภาพ",
|
imageArea: "พื้นที่แสดงตัวอย่าง",
|
||||||
templateSocialMedia: "พร้อมสำหรับสื่อสังคม",
|
templateSocialMedia: "พร้อมสำหรับสื่อสังคม",
|
||||||
templatePrivacyClean: "ล้างข้อมูลความเป็นส่วนตัว",
|
templatePrivacyClean: "ล้างข้อมูลความเป็นส่วนตัว",
|
||||||
templateWebOptimization: "เพิ่มประสิทธิภาพเว็บ",
|
templateWebOptimization: "เพิ่มประสิทธิภาพเว็บ",
|
||||||
@@ -3387,14 +3387,14 @@ export const th: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "ไฟล์ก่อนหน้า",
|
||||||
nextImage: "Next image",
|
nextImage: "ไฟล์ถัดไป",
|
||||||
imageArea: "Image area",
|
imageArea: "พื้นที่แสดงตัวอย่าง",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "ตัวแสดงไฟล์",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "โซนวางไฟล์",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "การควบคุมการแสดงตัวอย่าง",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2448,13 +2448,13 @@ export const tr: TranslationKeys = {
|
|||||||
generatingPreview: "Önizleme oluşturuluyor...",
|
generatingPreview: "Önizleme oluşturuluyor...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"{ext} dosyaları tarayıcıda önizlenemez. Araç bu dosyayı yine de normal olarak işleyecektir.",
|
"{ext} dosyaları tarayıcıda önizlenemez. Araç bu dosyayı yine de normal olarak işleyecektir.",
|
||||||
previousImage: "Önceki görüntü",
|
previousImage: "Önceki dosya",
|
||||||
nextImage: "Sonraki görüntü",
|
nextImage: "Sonraki dosya",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Ayarlar",
|
settingsLabel: "Ayarlar",
|
||||||
downloadAllZip: "Tümünü İndir (ZIP)",
|
downloadAllZip: "Tümünü İndir (ZIP)",
|
||||||
hideSettings: "Ayarları Gizle",
|
hideSettings: "Ayarları Gizle",
|
||||||
imageArea: "Görüntü alanı",
|
imageArea: "Önizleme alanı",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3197,10 +3197,10 @@ export const tr: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "İptal",
|
cancelButton: "İptal",
|
||||||
preview: "Önizleme",
|
preview: "Önizleme",
|
||||||
previousImage: "Önceki görüntü",
|
previousImage: "Önceki dosya",
|
||||||
nextImage: "Sonraki görüntü",
|
nextImage: "Sonraki dosya",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Görüntü alanı",
|
imageArea: "Önizleme alanı",
|
||||||
templateSocialMedia: "Sosyal Medyaya Hazır",
|
templateSocialMedia: "Sosyal Medyaya Hazır",
|
||||||
templatePrivacyClean: "Gizlilik Temizliği",
|
templatePrivacyClean: "Gizlilik Temizliği",
|
||||||
templateWebOptimization: "Web Optimizasyonu",
|
templateWebOptimization: "Web Optimizasyonu",
|
||||||
@@ -3422,14 +3422,14 @@ export const tr: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Önceki dosya",
|
||||||
nextImage: "Next image",
|
nextImage: "Sonraki dosya",
|
||||||
imageArea: "Image area",
|
imageArea: "Önizleme alanı",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Dosya görüntüleyici",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Dosya bırakma alanı",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Önizleme kontrolleri",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2446,13 +2446,13 @@ export const uk: TranslationKeys = {
|
|||||||
generatingPreview: "Генерація попереднього перегляду...",
|
generatingPreview: "Генерація попереднього перегляду...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Файли {ext} неможливо переглянути у браузері. Інструмент все одно обробить цей файл.",
|
"Файли {ext} неможливо переглянути у браузері. Інструмент все одно обробить цей файл.",
|
||||||
previousImage: "Попереднє зображення",
|
previousImage: "Попередній файл",
|
||||||
nextImage: "Наступне зображення",
|
nextImage: "Наступний файл",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Налаштування",
|
settingsLabel: "Налаштування",
|
||||||
downloadAllZip: "Завантажити все (ZIP)",
|
downloadAllZip: "Завантажити все (ZIP)",
|
||||||
hideSettings: "Сховати налаштування",
|
hideSettings: "Сховати налаштування",
|
||||||
imageArea: "Область зображення",
|
imageArea: "Область попереднього перегляду",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3193,10 +3193,10 @@ export const uk: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Скасувати",
|
cancelButton: "Скасувати",
|
||||||
preview: "Попередній перегляд",
|
preview: "Попередній перегляд",
|
||||||
previousImage: "Попереднє зображення",
|
previousImage: "Попередній файл",
|
||||||
nextImage: "Наступне зображення",
|
nextImage: "Наступний файл",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Область зображення",
|
imageArea: "Область попереднього перегляду",
|
||||||
templateSocialMedia: "Готово для соцмереж",
|
templateSocialMedia: "Готово для соцмереж",
|
||||||
templatePrivacyClean: "Очищення конфіденційності",
|
templatePrivacyClean: "Очищення конфіденційності",
|
||||||
templateWebOptimization: "Веб-оптимізація",
|
templateWebOptimization: "Веб-оптимізація",
|
||||||
@@ -3417,14 +3417,14 @@ export const uk: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Попередній файл",
|
||||||
nextImage: "Next image",
|
nextImage: "Наступний файл",
|
||||||
imageArea: "Image area",
|
imageArea: "Область попереднього перегляду",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Переглядач файлів",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Зона перетягування файлів",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Елементи керування переглядом",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2445,13 +2445,13 @@ export const vi: TranslationKeys = {
|
|||||||
generatingPreview: "Đang tạo bản xem trước...",
|
generatingPreview: "Đang tạo bản xem trước...",
|
||||||
cannotPreviewWillProcess:
|
cannotPreviewWillProcess:
|
||||||
"Không thể xem trước tệp {ext} trong trình duyệt. Công cụ vẫn sẽ xử lý tệp này bình thường.",
|
"Không thể xem trước tệp {ext} trong trình duyệt. Công cụ vẫn sẽ xử lý tệp này bình thường.",
|
||||||
previousImage: "Ảnh trước",
|
previousImage: "Tệp trước",
|
||||||
nextImage: "Ảnh tiếp theo",
|
nextImage: "Tệp tiếp theo",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "Cài đặt",
|
settingsLabel: "Cài đặt",
|
||||||
downloadAllZip: "Tải tất cả (ZIP)",
|
downloadAllZip: "Tải tất cả (ZIP)",
|
||||||
hideSettings: "Ẩn cài đặt",
|
hideSettings: "Ẩn cài đặt",
|
||||||
imageArea: "Vùng ảnh",
|
imageArea: "Vùng xem trước",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3187,10 +3187,10 @@ export const vi: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "Hủy",
|
cancelButton: "Hủy",
|
||||||
preview: "Xem trước",
|
preview: "Xem trước",
|
||||||
previousImage: "Ảnh trước",
|
previousImage: "Tệp trước",
|
||||||
nextImage: "Ảnh tiếp theo",
|
nextImage: "Tệp tiếp theo",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "Vùng ảnh",
|
imageArea: "Vùng xem trước",
|
||||||
templateSocialMedia: "Sẵn sàng cho mạng xã hội",
|
templateSocialMedia: "Sẵn sàng cho mạng xã hội",
|
||||||
templatePrivacyClean: "Dọn dẹp quyền riêng tư",
|
templatePrivacyClean: "Dọn dẹp quyền riêng tư",
|
||||||
templateWebOptimization: "Tối ưu cho Web",
|
templateWebOptimization: "Tối ưu cho Web",
|
||||||
@@ -3411,14 +3411,14 @@ export const vi: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "Tệp trước",
|
||||||
nextImage: "Next image",
|
nextImage: "Tệp tiếp theo",
|
||||||
imageArea: "Image area",
|
imageArea: "Vùng xem trước",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "Trình xem tệp",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "Vùng thả tệp",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "Điều khiển xem trước",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2378,13 +2378,13 @@ export const zhCN: TranslationKeys = {
|
|||||||
cannotPreviewDownload: "{type} 文件无法在浏览器中预览。请使用下载按钮保存文件。",
|
cannotPreviewDownload: "{type} 文件无法在浏览器中预览。请使用下载按钮保存文件。",
|
||||||
generatingPreview: "正在生成预览...",
|
generatingPreview: "正在生成预览...",
|
||||||
cannotPreviewWillProcess: "{ext} 文件无法在浏览器中预览。该工具仍会正常处理此文件。",
|
cannotPreviewWillProcess: "{ext} 文件无法在浏览器中预览。该工具仍会正常处理此文件。",
|
||||||
previousImage: "上一张图片",
|
previousImage: "上一个文件",
|
||||||
nextImage: "下一张图片",
|
nextImage: "下一个文件",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "设置",
|
settingsLabel: "设置",
|
||||||
downloadAllZip: "全部下载(ZIP)",
|
downloadAllZip: "全部下载(ZIP)",
|
||||||
hideSettings: "隐藏设置",
|
hideSettings: "隐藏设置",
|
||||||
imageArea: "图片区域",
|
imageArea: "预览区域",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3115,10 +3115,10 @@ export const zhCN: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "取消",
|
cancelButton: "取消",
|
||||||
preview: "预览",
|
preview: "预览",
|
||||||
previousImage: "上一张图片",
|
previousImage: "上一个文件",
|
||||||
nextImage: "下一张图片",
|
nextImage: "下一个文件",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "图片区域",
|
imageArea: "预览区域",
|
||||||
templateSocialMedia: "社交媒体适配",
|
templateSocialMedia: "社交媒体适配",
|
||||||
templatePrivacyClean: "隐私清理",
|
templatePrivacyClean: "隐私清理",
|
||||||
templateWebOptimization: "网页优化",
|
templateWebOptimization: "网页优化",
|
||||||
@@ -3337,14 +3337,14 @@ export const zhCN: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "上一个文件",
|
||||||
nextImage: "Next image",
|
nextImage: "下一个文件",
|
||||||
imageArea: "Image area",
|
imageArea: "预览区域",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "文件查看器",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "文件拖放区域",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "预览控件",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -2376,13 +2376,13 @@ export const zhTW: TranslationKeys = {
|
|||||||
cannotPreviewDownload: "{type}檔案無法在瀏覽器中預覽。請使用下載按鈕儲存檔案。",
|
cannotPreviewDownload: "{type}檔案無法在瀏覽器中預覽。請使用下載按鈕儲存檔案。",
|
||||||
generatingPreview: "正在產生預覽...",
|
generatingPreview: "正在產生預覽...",
|
||||||
cannotPreviewWillProcess: "{ext}檔案無法在瀏覽器中預覽。該工具仍會正常處理此檔案。",
|
cannotPreviewWillProcess: "{ext}檔案無法在瀏覽器中預覽。該工具仍會正常處理此檔案。",
|
||||||
previousImage: "上一張影像",
|
previousImage: "上一個檔案",
|
||||||
nextImage: "下一張影像",
|
nextImage: "下一個檔案",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
settingsLabel: "設定",
|
settingsLabel: "設定",
|
||||||
downloadAllZip: "全部下載(ZIP)",
|
downloadAllZip: "全部下載(ZIP)",
|
||||||
hideSettings: "隱藏設定",
|
hideSettings: "隱藏設定",
|
||||||
imageArea: "影像區域",
|
imageArea: "預覽區域",
|
||||||
disabledByAdmin: "This tool has been disabled by your administrator",
|
disabledByAdmin: "This tool has been disabled by your administrator",
|
||||||
browseOtherTools: "Browse other tools",
|
browseOtherTools: "Browse other tools",
|
||||||
privacyNote: "Files are processed on your server and never leave your network",
|
privacyNote: "Files are processed on your server and never leave your network",
|
||||||
@@ -3113,10 +3113,10 @@ export const zhTW: TranslationKeys = {
|
|||||||
savingIndicator: "...",
|
savingIndicator: "...",
|
||||||
cancelButton: "取消",
|
cancelButton: "取消",
|
||||||
preview: "預覽",
|
preview: "預覽",
|
||||||
previousImage: "上一張影像",
|
previousImage: "上一個檔案",
|
||||||
nextImage: "下一張影像",
|
nextImage: "下一個檔案",
|
||||||
imageCounter: "{current} / {total}",
|
imageCounter: "{current} / {total}",
|
||||||
imageArea: "影像區域",
|
imageArea: "預覽區域",
|
||||||
templateSocialMedia: "社群媒體就緒",
|
templateSocialMedia: "社群媒體就緒",
|
||||||
templatePrivacyClean: "隱私清理",
|
templatePrivacyClean: "隱私清理",
|
||||||
templateWebOptimization: "網頁最佳化",
|
templateWebOptimization: "網頁最佳化",
|
||||||
@@ -3336,14 +3336,14 @@ export const zhTW: TranslationKeys = {
|
|||||||
zoomOut: "Zoom out",
|
zoomOut: "Zoom out",
|
||||||
fitToView: "Fit to view",
|
fitToView: "Fit to view",
|
||||||
actualSize: "Actual size",
|
actualSize: "Actual size",
|
||||||
previousImage: "Previous image",
|
previousImage: "上一個檔案",
|
||||||
nextImage: "Next image",
|
nextImage: "下一個檔案",
|
||||||
imageArea: "Image area",
|
imageArea: "預覽區域",
|
||||||
imageViewer: "Image viewer",
|
imageViewer: "檔案檢視器",
|
||||||
fileDropZone: "File drop zone",
|
fileDropZone: "File drop zone",
|
||||||
imageDropZone: "Image drop zone",
|
imageDropZone: "檔案拖放區域",
|
||||||
beforeAfterSlider: "Before/after comparison slider",
|
beforeAfterSlider: "Before/after comparison slider",
|
||||||
imageControls: "Image controls",
|
imageControls: "預覽控制項",
|
||||||
zoomControls: "Zoom controls",
|
zoomControls: "Zoom controls",
|
||||||
dragToReorder: "Drag to reorder",
|
dragToReorder: "Drag to reorder",
|
||||||
whiteBackground: "White background",
|
whiteBackground: "White background",
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe("sanitizeFilename", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("handles filename with only unknown extensions", () => {
|
it("handles filename with only unknown extensions", () => {
|
||||||
expect(sanitizeFilename("data.csv")).toBe("data.csv");
|
expect(sanitizeFilename("data.xyz")).toBe("data.xyz");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("truncates very long filenames over 200 bytes", () => {
|
it("truncates very long filenames over 200 bytes", () => {
|
||||||
@@ -139,4 +139,20 @@ describe("sanitizeFilename", () => {
|
|||||||
expect(result).toBe(`file.${ext}`);
|
expect(result).toBe(`file.${ext}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("recognizes safe extensions across all modalities", () => {
|
||||||
|
const cases: Record<string, string> = {
|
||||||
|
"clip.mp4.exe": "clip.mp4",
|
||||||
|
"song.mp3.php": "song.mp3",
|
||||||
|
"report.csv.php": "report.csv",
|
||||||
|
"doc.docx.exe": "doc.docx",
|
||||||
|
"data.json.sh": "data.json",
|
||||||
|
"book.epub.php": "book.epub",
|
||||||
|
"sheet.xlsx.exe": "sheet.xlsx",
|
||||||
|
"subs.srt.php": "subs.srt",
|
||||||
|
};
|
||||||
|
for (const [input, expected] of Object.entries(cases)) {
|
||||||
|
expect(sanitizeFilename(input)).toBe(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user