Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8e47898e | ||
|
|
34316ec6c9 | ||
|
|
d7e590f24e | ||
|
|
49a10a3865 | ||
|
|
190178a471 | ||
|
|
8e8b417f5e | ||
|
|
a57373b968 | ||
|
|
7dad484aa7 | ||
|
|
cfdb6eaa1e | ||
|
|
8a367aab54 | ||
|
|
f3fd85d777 | ||
|
|
9d723eae69 | ||
|
|
494ef801a2 | ||
|
|
e8af4f6b35 | ||
|
|
c25985e49e | ||
|
|
316be5eac5 | ||
|
|
11d23a2d43 | ||
|
|
1898df0df9 | ||
|
|
da359d329d | ||
|
|
bd13f6bf57 | ||
|
|
87c63efcec | ||
|
|
5caec41d96 | ||
|
|
d558bb5fac | ||
|
|
cd1fc682ab | ||
|
|
b9cf7e7820 | ||
|
|
94e8f603ff | ||
|
|
74609e54fe | ||
|
|
003285506f | ||
|
|
6d3b08d9b6 | ||
|
|
295e682e03 | ||
|
|
1a3e8e7ecf | ||
|
|
96aa43860b | ||
|
|
6dbaff5342 | ||
|
|
2f2f966ee9 | ||
|
|
3b28b398d9 | ||
|
|
eecc410b77 | ||
|
|
62c929b89b | ||
|
|
7c9769f5b9 | ||
|
|
cf771075f4 | ||
|
|
a7d5c80188 | ||
|
|
5b20f11e20 | ||
|
|
442b373ff4 | ||
|
|
e142af2863 | ||
|
|
581bafbd37 | ||
|
|
2cac8e8edf | ||
|
|
0002043266 | ||
|
|
00f0194b32 | ||
|
|
1c8b20b6e9 | ||
|
|
1eee5c9a11 | ||
|
|
68d59fd377 | ||
|
|
47ccb6a6ed | ||
|
|
3d26b054f1 | ||
|
|
9360a36c31 | ||
|
|
d64c56a897 | ||
|
|
ca423f9646 | ||
|
|
9b8091a630 | ||
|
|
4aac124e76 | ||
|
|
6d7f76353e | ||
|
|
16e028a8ef | ||
|
|
95b3e22229 | ||
|
|
4b70ef1298 | ||
|
|
1cc803545a | ||
|
|
fe9d2367d5 | ||
|
|
42d7664e25 | ||
|
|
bbd658d3b8 | ||
|
|
ed61c71db7 | ||
|
|
73deece29e | ||
|
|
55ebf9ebd0 | ||
|
|
e6f4cfb318 | ||
|
|
895bcebc7b | ||
|
|
61f5221c58 | ||
|
|
888bac9408 | ||
|
|
23d86deae7 | ||
|
|
7d9c0b0298 | ||
|
|
949ffa01ad | ||
|
|
a33e51351b | ||
|
|
a6706fcb0c | ||
|
|
d06cbcaa91 | ||
|
|
cd2b82d614 | ||
|
|
eada9e43ec | ||
|
|
d1cb3f0b30 | ||
|
|
857b2b5c53 | ||
|
|
d74c3f4598 |
@@ -11,8 +11,12 @@
|
||||
"Bash(npm test:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(npx tsc:*)",
|
||||
"Bash(sed:*)"
|
||||
"Bash(node:*)",
|
||||
"Bash(npm run dev:*)",
|
||||
"Bash(sed:*)",
|
||||
"Bash(npm run typecheck:*)"
|
||||
],
|
||||
"deny": []
|
||||
"deny": [],
|
||||
"defaultMode": "acceptEdits"
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -24,7 +24,7 @@ indent_size = 2
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{*.js,*.jsx,*.ts,*.tsx}]
|
||||
[{*.js,*.jsx,*.mjs,*.ts,*.tsx}]
|
||||
indent_size = 2
|
||||
|
||||
[*.css]
|
||||
|
||||
@@ -147,6 +147,8 @@ jobs:
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
- name: Install frontend dependencies
|
||||
run: cd frontend && npm ci
|
||||
- name: Lint frontend
|
||||
run: cd frontend && npm run lint
|
||||
- name: Build frontend
|
||||
run: cd frontend && npm run build
|
||||
- name: Run frontend tests
|
||||
|
||||
Vendored
+1
@@ -19,5 +19,6 @@
|
||||
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
||||
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
|
||||
"redhat.vscode-yaml", // YAML extension for Visual Studio Code
|
||||
"dbaeumer.vscode-eslint", // ESLint extension for TypeScript linting
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -139,5 +139,8 @@
|
||||
"app/core/src/main/java",
|
||||
"app/common/src/main/java",
|
||||
"app/proprietary/src/main/java"
|
||||
]
|
||||
],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
# Adding New React Tools to Stirling PDF
|
||||
|
||||
This guide covers how to add new PDF tools to the React frontend, either by migrating existing Thymeleaf templates or creating entirely new tools.
|
||||
|
||||
## Overview
|
||||
|
||||
When adding tools, follow this systematic approach using the established patterns and architecture.
|
||||
|
||||
## 1. Create Tool Structure
|
||||
|
||||
Create these files in the correct directories:
|
||||
```
|
||||
frontend/src/hooks/tools/[toolName]/
|
||||
├── use[ToolName]Parameters.ts # Parameter definitions and validation
|
||||
└── use[ToolName]Operation.ts # Tool operation logic using useToolOperation
|
||||
|
||||
frontend/src/components/tools/[toolName]/
|
||||
└── [ToolName]Settings.tsx # Settings UI component (if needed)
|
||||
|
||||
frontend/src/tools/
|
||||
└── [ToolName].tsx # Main tool component
|
||||
```
|
||||
|
||||
## 2. Implementation Pattern
|
||||
|
||||
Use `useBaseTool` for simplified hook management. This is the recommended approach for all new tools:
|
||||
|
||||
**Parameters Hook** (`use[ToolName]Parameters.ts`):
|
||||
```typescript
|
||||
import { BaseParameters } from '../../../types/parameters';
|
||||
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
||||
|
||||
export interface [ToolName]Parameters extends BaseParameters {
|
||||
// Define your tool-specific parameters here
|
||||
someOption: boolean;
|
||||
}
|
||||
|
||||
export const defaultParameters: [ToolName]Parameters = {
|
||||
someOption: false,
|
||||
};
|
||||
|
||||
export const use[ToolName]Parameters = (): BaseParametersHook<[ToolName]Parameters> => {
|
||||
return useBaseParameters({
|
||||
defaultParameters,
|
||||
endpointName: 'your-endpoint-name',
|
||||
validateFn: (params) => true, // Add validation logic
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
**Operation Hook** (`use[ToolName]Operation.ts`):
|
||||
```typescript
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
|
||||
export const build[ToolName]FormData = (parameters: [ToolName]Parameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append('fileInput', file);
|
||||
// Add parameters to formData
|
||||
return formData;
|
||||
};
|
||||
|
||||
export const [toolName]OperationConfig = {
|
||||
toolType: ToolType.singleFile, // or ToolType.multiFile (buildFormData's file parameter will need to be updated)
|
||||
buildFormData: build[ToolName]FormData,
|
||||
operationType: '[toolName]',
|
||||
endpoint: '/api/v1/category/endpoint-name',
|
||||
filePrefix: 'processed_', // Will be overridden with translation
|
||||
defaultParameters,
|
||||
} as const;
|
||||
|
||||
export const use[ToolName]Operation = () => {
|
||||
const { t } = useTranslation();
|
||||
return useToolOperation({
|
||||
...[toolName]OperationConfig,
|
||||
filePrefix: t('[toolName].filenamePrefix', 'processed') + '_',
|
||||
getErrorMessage: createStandardErrorHandler(t('[toolName].error.failed', 'Operation failed'))
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
**Main Component** (`[ToolName].tsx`):
|
||||
```typescript
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
import { use[ToolName]Parameters } from "../hooks/tools/[toolName]/use[ToolName]Parameters";
|
||||
import { use[ToolName]Operation } from "../hooks/tools/[toolName]/use[ToolName]Operation";
|
||||
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||
|
||||
const [ToolName] = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
const base = useBaseTool('[toolName]', use[ToolName]Parameters, use[ToolName]Operation, props);
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
isCollapsed: base.hasResults,
|
||||
placeholder: t("[toolName].files.placeholder", "Select files to get started"),
|
||||
},
|
||||
steps: [
|
||||
// Add settings steps if needed
|
||||
],
|
||||
executeButton: {
|
||||
text: t("[toolName].submit", "Process"),
|
||||
isVisible: !base.hasResults,
|
||||
loadingText: t("loading"),
|
||||
onClick: base.handleExecute,
|
||||
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
|
||||
},
|
||||
review: {
|
||||
isVisible: base.hasResults,
|
||||
operation: base.operation,
|
||||
title: t("[toolName].results.title", "Results"),
|
||||
onFileClick: base.handleThumbnailClick,
|
||||
onUndo: base.handleUndo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
[ToolName].tool = () => use[ToolName]Operation;
|
||||
export default [ToolName] as ToolComponent;
|
||||
```
|
||||
|
||||
**Note**: Some existing tools (like AddPassword, Compress) use a legacy pattern with manual hook management. **Always use the Modern Pattern above for new tools** - it's cleaner, more maintainable, and includes automation support.
|
||||
|
||||
## 3. Register Tool in System
|
||||
Update these files to register your new tool:
|
||||
|
||||
**Tool Registry** (`frontend/src/data/useTranslatedToolRegistry.tsx`):
|
||||
1. Add imports at the top:
|
||||
```typescript
|
||||
import [ToolName] from "../tools/[ToolName]";
|
||||
import { [toolName]OperationConfig } from "../hooks/tools/[toolName]/use[ToolName]Operation";
|
||||
import [ToolName]Settings from "../components/tools/[toolName]/[ToolName]Settings";
|
||||
```
|
||||
|
||||
2. Add tool entry in the `allTools` object:
|
||||
```typescript
|
||||
[toolName]: {
|
||||
icon: <LocalIcon icon="your-icon-name" width="1.5rem" height="1.5rem" />,
|
||||
name: t("home.[toolName].title", "Tool Name"),
|
||||
component: [ToolName],
|
||||
description: t("home.[toolName].desc", "Tool description"),
|
||||
categoryId: ToolCategoryId.STANDARD_TOOLS, // or appropriate category
|
||||
subcategoryId: SubcategoryId.APPROPRIATE_SUBCATEGORY,
|
||||
maxFiles: -1, // or specific number
|
||||
endpoints: ["endpoint-name"],
|
||||
operationConfig: [toolName]OperationConfig,
|
||||
settingsComponent: [ToolName]Settings, // if settings exist
|
||||
},
|
||||
```
|
||||
|
||||
## 4. Add Tooltips (Optional but Recommended)
|
||||
Create user-friendly tooltips to help non-technical users understand your tool. **Use simple, clear language - avoid technical jargon:**
|
||||
|
||||
**Tooltip Hook** (`frontend/src/components/tooltips/use[ToolName]Tips.ts`):
|
||||
```typescript
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TooltipContent } from '../../types/tips';
|
||||
|
||||
export const use[ToolName]Tips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
header: {
|
||||
title: t("[toolName].tooltip.header.title", "Tool Overview")
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
title: t("[toolName].tooltip.description.title", "What does this tool do?"),
|
||||
description: t("[toolName].tooltip.description.text", "Simple explanation in everyday language that non-technical users can understand."),
|
||||
bullets: [
|
||||
t("[toolName].tooltip.description.bullet1", "Easy-to-understand benefit 1"),
|
||||
t("[toolName].tooltip.description.bullet2", "Easy-to-understand benefit 2")
|
||||
]
|
||||
}
|
||||
// Add more tip sections as needed
|
||||
]
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**Add tooltip to your main component:**
|
||||
```typescript
|
||||
import { use[ToolName]Tips } from "../components/tooltips/use[ToolName]Tips";
|
||||
|
||||
const [ToolName] = (props: BaseToolProps) => {
|
||||
const tips = use[ToolName]Tips();
|
||||
|
||||
// In your steps array:
|
||||
steps: [
|
||||
{
|
||||
title: t("[toolName].steps.settings", "Settings"),
|
||||
tooltip: tips, // Add this line
|
||||
content: <[ToolName]Settings ... />
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 5. Add Translations
|
||||
Update translation files. **Important: Only update `en-GB` files** - other languages are handled separately.
|
||||
|
||||
**File to update:** `frontend/public/locales/en-GB/translation.json`
|
||||
|
||||
**Required Translation Keys**:
|
||||
```json
|
||||
{
|
||||
"home": {
|
||||
"[toolName]": {
|
||||
"title": "Tool Name",
|
||||
"desc": "Tool description"
|
||||
}
|
||||
},
|
||||
"[toolName]": {
|
||||
"title": "Tool Name",
|
||||
"submit": "Process",
|
||||
"filenamePrefix": "processed",
|
||||
"files": {
|
||||
"placeholder": "Select files to get started"
|
||||
},
|
||||
"steps": {
|
||||
"settings": "Settings"
|
||||
},
|
||||
"options": {
|
||||
"title": "Tool Options",
|
||||
"someOption": "Option Label",
|
||||
"someOption.desc": "Option description",
|
||||
"note": "General information about the tool."
|
||||
},
|
||||
"results": {
|
||||
"title": "Results"
|
||||
},
|
||||
"error": {
|
||||
"failed": "Operation failed"
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Tool Overview"
|
||||
},
|
||||
"description": {
|
||||
"title": "What does this tool do?",
|
||||
"text": "Simple explanation in everyday language",
|
||||
"bullet1": "Easy-to-understand benefit 1",
|
||||
"bullet2": "Easy-to-understand benefit 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Translation Notes:**
|
||||
- **Only update `en-GB/translation.json`** - other locale files are managed separately
|
||||
- Use descriptive keys that match your component's `t()` calls
|
||||
- Include tooltip translations if you created tooltip hooks
|
||||
- Add `options.*` keys if your tool has settings with descriptions
|
||||
|
||||
**Tooltip Writing Guidelines:**
|
||||
- **Use simple, everyday language** - avoid technical terms like "converts interactive elements"
|
||||
- **Focus on benefits** - explain what the user gains, not how it works internally
|
||||
- **Use concrete examples** - "text boxes become regular text" vs "form fields are flattened"
|
||||
- **Answer user questions** - "What does this do?", "When should I use this?", "What's this option for?"
|
||||
- **Keep descriptions concise** - 1-2 sentences maximum per section
|
||||
- **Use bullet points** for multiple benefits or features
|
||||
|
||||
## 6. Migration from Thymeleaf
|
||||
When migrating existing Thymeleaf templates:
|
||||
|
||||
1. **Identify Form Parameters**: Look at the original `<form>` inputs to determine parameter structure
|
||||
2. **Extract Translation Keys**: Find `#{key.name}` references and add them to JSON translations (For many tools these translations will already exist but some parts will be missing)
|
||||
3. **Map API Endpoint**: Note the `th:action` URL for the operation hook
|
||||
4. **Preserve Functionality**: Ensure all original form behaviour is replicated which is applicable to V2 react UI
|
||||
|
||||
## 7. Testing Your Tool
|
||||
- Verify tool appears in UI with correct icon and description
|
||||
- Test with various file sizes and types
|
||||
- Confirm translations work
|
||||
- Check error handling
|
||||
- Test undo functionality
|
||||
- Verify results display correctly
|
||||
|
||||
## Tool Development Patterns
|
||||
|
||||
### Three Tool Patterns:
|
||||
|
||||
**Pattern 1: Single-File Tools** (Individual processing)
|
||||
- Backend processes one file per API call
|
||||
- Set `multiFileEndpoint: false`
|
||||
- Examples: Compress, Rotate
|
||||
|
||||
**Pattern 2: Multi-File Tools** (Batch processing)
|
||||
- Backend accepts `MultipartFile[]` arrays in single API call
|
||||
- Set `multiFileEndpoint: true`
|
||||
- Examples: Split, Merge, Overlay
|
||||
|
||||
**Pattern 3: Complex Tools** (Custom processing)
|
||||
- Tools with complex routing logic or non-standard processing
|
||||
- Provide `customProcessor` for full control
|
||||
- Examples: Convert, OCR
|
||||
@@ -208,6 +208,7 @@ return useToolOperation({
|
||||
- **Tool Development**: New tools should follow `useToolOperation` hook pattern (see `useCompressOperation.ts`)
|
||||
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
||||
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
||||
- **Adding Tools**: See `ADDING_TOOLS.md` for complete guide to creating new PDF tools
|
||||
|
||||
## Communication Style
|
||||
- Be direct and to the point
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
# Stirling PDF File History Specification
|
||||
|
||||
## Overview
|
||||
|
||||
Stirling PDF implements a client-side file history system using IndexedDB storage. File metadata, including version history and tool chains, are stored as `StirlingFileStub` objects that travel alongside the actual file data. This enables comprehensive version tracking, tool history, and file lineage management without modifying PDF content.
|
||||
|
||||
## Storage Architecture
|
||||
|
||||
### IndexedDB-Based Storage
|
||||
File history is stored in the browser's IndexedDB using the `fileStorage` service, providing:
|
||||
- **Persistent storage**: Survives browser sessions and page reloads
|
||||
- **Large capacity**: Supports files up to 100GB+ with full metadata
|
||||
- **Fast queries**: Optimized for file browsing and history lookups
|
||||
- **Type safety**: Structured TypeScript interfaces
|
||||
|
||||
### Core Data Structures
|
||||
|
||||
```typescript
|
||||
interface StirlingFileStub extends BaseFileMetadata {
|
||||
id: FileId; // Unique file identifier (UUID)
|
||||
quickKey: string; // Deduplication key: name|size|lastModified
|
||||
thumbnailUrl?: string; // Generated thumbnail blob URL
|
||||
processedFile?: ProcessedFileMetadata; // PDF page data and processing results
|
||||
|
||||
// File Metadata
|
||||
name: string;
|
||||
size: number;
|
||||
type: string;
|
||||
lastModified: number;
|
||||
createdAt: number;
|
||||
|
||||
// Version Control
|
||||
isLeaf: boolean; // True if this is the latest version
|
||||
versionNumber?: number; // Version number (1, 2, 3, etc.)
|
||||
originalFileId?: string; // UUID of the root file in version chain
|
||||
parentFileId?: string; // UUID of immediate parent file
|
||||
|
||||
// Tool History
|
||||
toolHistory?: ToolOperation[]; // Complete sequence of applied tools
|
||||
}
|
||||
|
||||
interface ToolOperation {
|
||||
toolName: string; // Tool identifier (e.g., 'compress', 'sanitize')
|
||||
timestamp: number; // When the tool was applied
|
||||
}
|
||||
|
||||
interface StoredStirlingFileRecord extends StirlingFileStub {
|
||||
data: ArrayBuffer; // Actual file content
|
||||
fileId: FileId; // Duplicate for indexing
|
||||
}
|
||||
```
|
||||
|
||||
## Version Management System
|
||||
|
||||
### Version Progression
|
||||
- **v1**: Original uploaded file (first version)
|
||||
- **v2**: First tool applied to original
|
||||
- **v3**: Second tool applied (inherits from v2)
|
||||
- **v4**: Third tool applied (inherits from v3)
|
||||
- **etc.**
|
||||
|
||||
### Leaf Node System
|
||||
Only the latest version of each file family is marked as `isLeaf: true`:
|
||||
- **Leaf files**: Show in default file list, available for tool processing
|
||||
- **History files**: Hidden by default, accessible via history expansion
|
||||
|
||||
### File Relationships
|
||||
```
|
||||
document.pdf (v1, isLeaf: false)
|
||||
↓ compress
|
||||
document.pdf (v2, isLeaf: false)
|
||||
↓ sanitize
|
||||
document.pdf (v3, isLeaf: true) ← Current active version
|
||||
```
|
||||
|
||||
## Implementation Architecture
|
||||
|
||||
### 1. FileStorage Service (`fileStorage.ts`)
|
||||
|
||||
**Core Methods:**
|
||||
```typescript
|
||||
// Store file with complete metadata
|
||||
async storeStirlingFile(stirlingFile: StirlingFile, stub: StirlingFileStub): Promise<void>
|
||||
|
||||
// Load file with metadata
|
||||
async getStirlingFile(id: FileId): Promise<StirlingFile | null>
|
||||
async getStirlingFileStub(id: FileId): Promise<StirlingFileStub | null>
|
||||
|
||||
// Query operations
|
||||
async getLeafStirlingFileStubs(): Promise<StirlingFileStub[]>
|
||||
async getAllStirlingFileStubs(): Promise<StirlingFileStub[]>
|
||||
|
||||
// Version management
|
||||
async markFileAsProcessed(fileId: FileId): Promise<boolean> // Set isLeaf = false
|
||||
async markFileAsLeaf(fileId: FileId): Promise<boolean> // Set isLeaf = true
|
||||
```
|
||||
|
||||
### 2. File Context Integration
|
||||
|
||||
**FileContext** manages runtime state with `StirlingFileStub[]` in memory:
|
||||
```typescript
|
||||
interface FileContextState {
|
||||
files: {
|
||||
ids: FileId[];
|
||||
byId: Record<FileId, StirlingFileStub>;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Key Operations:**
|
||||
- `addFiles()`: Stores new files with initial metadata
|
||||
- `addStirlingFileStubs()`: Loads existing files from storage with preserved metadata
|
||||
- `consumeFiles()`: Processes files through tools, creating new versions
|
||||
|
||||
### 3. Tool Operation Integration
|
||||
|
||||
**Tool Processing Flow:**
|
||||
1. **Input**: User selects files (marked as `isLeaf: true`)
|
||||
2. **Processing**: Backend processes files and returns results
|
||||
3. **History Creation**: New `StirlingFileStub` created with:
|
||||
- Incremented version number
|
||||
- Updated tool history
|
||||
- Parent file reference
|
||||
4. **Storage**: Both parent (marked `isLeaf: false`) and child (marked `isLeaf: true`) stored
|
||||
5. **UI Update**: FileContext updated with new file state
|
||||
|
||||
**Child Stub Creation:**
|
||||
```typescript
|
||||
export function createChildStub(
|
||||
parentStub: StirlingFileStub,
|
||||
operation: { toolName: string; timestamp: number },
|
||||
resultingFile: File,
|
||||
thumbnail?: string
|
||||
): StirlingFileStub {
|
||||
return {
|
||||
id: createFileId(),
|
||||
name: resultingFile.name,
|
||||
size: resultingFile.size,
|
||||
type: resultingFile.type,
|
||||
lastModified: resultingFile.lastModified,
|
||||
quickKey: createQuickKey(resultingFile),
|
||||
createdAt: Date.now(),
|
||||
isLeaf: true,
|
||||
|
||||
// Version Control
|
||||
versionNumber: (parentStub.versionNumber || 1) + 1,
|
||||
originalFileId: parentStub.originalFileId || parentStub.id,
|
||||
parentFileId: parentStub.id,
|
||||
|
||||
// Tool History
|
||||
toolHistory: [...(parentStub.toolHistory || []), operation],
|
||||
thumbnailUrl: thumbnail
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## UI Integration
|
||||
|
||||
### File Manager History Display
|
||||
|
||||
**FileManager** (`FileManager.tsx`) provides:
|
||||
- **Default View**: Shows only leaf files (`isLeaf: true`)
|
||||
- **History Expansion**: Click to show all versions of a file family
|
||||
- **History Groups**: Nested display using `FileHistoryGroup.tsx`
|
||||
|
||||
**FileListItem** (`FileListItem.tsx`) displays:
|
||||
- **Version Badges**: v1, v2, v3 indicators
|
||||
- **Tool Chain**: Complete processing history in tooltips
|
||||
- **History Actions**: "Show/Hide History" toggle, "Restore" for history files
|
||||
|
||||
### FileManagerContext Integration
|
||||
|
||||
**File Selection Flow:**
|
||||
```typescript
|
||||
// Recent files (from storage)
|
||||
onRecentFileSelect: (stirlingFileStubs: StirlingFileStub[]) => void
|
||||
// Calls: actions.addStirlingFileStubs(stirlingFileStubs, options)
|
||||
|
||||
// New uploads
|
||||
onFileUpload: (files: File[]) => void
|
||||
// Calls: actions.addFiles(files, options)
|
||||
```
|
||||
|
||||
**History Management:**
|
||||
```typescript
|
||||
// Toggle history visibility
|
||||
const { expandedFileIds, onToggleExpansion } = useFileManagerContext();
|
||||
|
||||
// Restore history file to current
|
||||
const handleAddToRecents = (file: StirlingFileStub) => {
|
||||
fileStorage.markFileAsLeaf(file.id); // Make this version current
|
||||
};
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### New File Upload
|
||||
```
|
||||
1. User uploads files → addFiles()
|
||||
2. Generate thumbnails and page count
|
||||
3. Create StirlingFileStub with isLeaf: true, versionNumber: 1
|
||||
4. Store both StirlingFile + StirlingFileStub in IndexedDB
|
||||
5. Dispatch to FileContext state
|
||||
```
|
||||
|
||||
### Tool Processing
|
||||
```
|
||||
1. User selects tool + files → useToolOperation()
|
||||
2. API processes files → returns processed File objects
|
||||
3. createChildStub() for each result:
|
||||
- Parent marked isLeaf: false
|
||||
- Child created with isLeaf: true, incremented version
|
||||
4. Store all files with updated metadata
|
||||
5. Update FileContext with new state
|
||||
```
|
||||
|
||||
### File Loading (Recent Files)
|
||||
```
|
||||
1. User selects from FileManager → onRecentFileSelect()
|
||||
2. addStirlingFileStubs() with preserved metadata
|
||||
3. Load actual StirlingFile data from storage
|
||||
4. Files appear in workbench with complete history intact
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Metadata Regeneration
|
||||
When loading files from storage, missing `processedFile` data is regenerated:
|
||||
```typescript
|
||||
// In addStirlingFileStubs()
|
||||
const needsProcessing = !record.processedFile ||
|
||||
!record.processedFile.pages ||
|
||||
record.processedFile.pages.length === 0;
|
||||
|
||||
if (needsProcessing) {
|
||||
const result = await generateThumbnailWithMetadata(stirlingFile);
|
||||
record.processedFile = createProcessedFile(result.pageCount, result.thumbnail);
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Management
|
||||
- **Blob URL Tracking**: Automatic cleanup of thumbnail URLs
|
||||
- **Lazy Loading**: Files loaded from storage only when needed
|
||||
- **LRU Caching**: File objects cached in memory with size limits
|
||||
|
||||
## File Deduplication
|
||||
|
||||
### QuickKey System
|
||||
Files are deduplicated using `quickKey` format:
|
||||
```typescript
|
||||
const quickKey = `${file.name}|${file.size}|${file.lastModified}`;
|
||||
```
|
||||
|
||||
This prevents duplicate uploads while allowing different versions of the same logical file.
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Graceful Degradation
|
||||
- **Storage Failures**: Files continue to work without persistence
|
||||
- **Metadata Issues**: Missing metadata regenerated on demand
|
||||
- **Version Conflicts**: Automatic version number resolution
|
||||
|
||||
### Recovery Scenarios
|
||||
- **Corrupted Storage**: Automatic cleanup and re-initialization
|
||||
- **Missing Files**: Stubs cleaned up automatically
|
||||
- **Version Mismatches**: Automatic version chain reconstruction
|
||||
|
||||
## Developer Guidelines
|
||||
|
||||
### Adding File History to New Components
|
||||
|
||||
1. **Use FileContext Actions**:
|
||||
```typescript
|
||||
const { actions } = useFileActions();
|
||||
await actions.addFiles(files); // For new uploads
|
||||
await actions.addStirlingFileStubs(stubs); // For existing files
|
||||
```
|
||||
|
||||
2. **Preserve Metadata When Processing**:
|
||||
```typescript
|
||||
const childStub = createChildStub(parentStub, {
|
||||
toolName: 'compress',
|
||||
timestamp: Date.now()
|
||||
}, processedFile, thumbnail);
|
||||
```
|
||||
|
||||
3. **Handle Storage Operations**:
|
||||
```typescript
|
||||
await fileStorage.storeStirlingFile(stirlingFile, stirlingFileStub);
|
||||
const stub = await fileStorage.getStirlingFileStub(fileId);
|
||||
```
|
||||
|
||||
### Testing File History
|
||||
|
||||
1. **Upload files**: Should show v1, marked as leaf
|
||||
2. **Apply tool**: Should create v2, mark v1 as non-leaf
|
||||
3. **Check FileManager**: History should show both versions
|
||||
4. **Restore old version**: Should mark old version as leaf
|
||||
5. **Check storage**: Both versions should persist in IndexedDB
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Potential Improvements
|
||||
- **Branch History**: Support for parallel processing branches
|
||||
- **History Export**: Export complete version history as JSON
|
||||
- **Conflict Resolution**: Handle concurrent modifications
|
||||
- **Cloud Sync**: Sync history across devices
|
||||
- **Compression**: Compress historical file data
|
||||
|
||||
### API Extensions
|
||||
- **Batch Operations**: Process multiple version chains simultaneously
|
||||
- **Search Integration**: Search within tool history and file metadata
|
||||
- **Analytics**: Track usage patterns and tool effectiveness
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 2025
|
||||
**Implementation**: Stirling PDF Frontend v2
|
||||
**Storage Version**: IndexedDB with fileStorage service
|
||||
+5
-1
@@ -24,4 +24,8 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
playwright-report
|
||||
test-results
|
||||
test-results
|
||||
|
||||
# auto-generated files
|
||||
/src/assets/material-symbols-icons.json
|
||||
/src/assets/material-symbols-icons.d.ts
|
||||
@@ -0,0 +1,42 @@
|
||||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default defineConfig(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
{
|
||||
ignores: [
|
||||
"dist", // Contains 3rd party code
|
||||
"public", // Contains 3rd party code
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
"no-undef": "off", // Temporarily disabled until codebase conformant
|
||||
"@typescript-eslint/no-empty-object-type": [
|
||||
"error",
|
||||
{
|
||||
// Allow empty extending interfaces because there's no real reason not to, and it makes it obvious where to put extra attributes in the future
|
||||
allowInterfaces: 'with-single-extends',
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
|
||||
"@typescript-eslint/no-require-imports": "off", // Temporarily disabled until codebase conformant
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"args": "all", // All function args must be used (or explicitly ignored)
|
||||
"argsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"caughtErrors": "all", // Caught errors must be used (or explicitly ignored)
|
||||
"caughtErrorsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"destructuredArrayIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"varsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"ignoreRestSiblings": true, // Allow unused variables when removing attributes from objects (otherwise this requires explicit renaming like `({ x: _x, ...y }) => y`, which is clunky)
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
Generated
+2576
-28
File diff suppressed because it is too large
Load Diff
+21
-4
@@ -5,14 +5,17 @@
|
||||
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
|
||||
"proxy": "http://localhost:8080",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.4",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@iconify/react": "^6.0.0",
|
||||
"@mantine/core": "^8.0.1",
|
||||
"@mantine/dropzone": "^8.0.1",
|
||||
"@mantine/hooks": "^8.0.1",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
@@ -23,9 +26,9 @@
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"material-symbols": "^0.33.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"posthog-js": "^1.261.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.2",
|
||||
@@ -34,11 +37,19 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npx tsc --noEmit && vite",
|
||||
"build": "npx tsc --noEmit && vite build",
|
||||
"predev": "npm run generate-icons",
|
||||
"dev": "npm run typecheck && vite",
|
||||
"prebuild": "npm run generate-icons",
|
||||
"lint": "eslint",
|
||||
"build": "npm run typecheck && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"check": "npm run typecheck && npm run lint && npm run test:run",
|
||||
"generate-licenses": "node scripts/generate-licenses.js",
|
||||
"generate-icons": "node scripts/generate-icons.js",
|
||||
"generate-icons:verbose": "node scripts/generate-icons.js --verbose",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:watch": "vitest --watch",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"test:e2e": "playwright test",
|
||||
@@ -64,19 +75,25 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.34.0",
|
||||
"@iconify-json/material-symbols": "^1.2.33",
|
||||
"@iconify/utils": "^3.0.1",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/react": "^19.1.4",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"@vitest/coverage-v8": "^1.0.0",
|
||||
"eslint": "^9.34.0",
|
||||
"jsdom": "^23.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"madge": "^8.0.0",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.42.0",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^1.0.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg width="146" height="157" viewBox="0 0 146 157" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.77397 72.5462L93.6741 23.0462L94.7739 70.0462L3.77395 119.046L3.77397 72.5462Z" fill="#E6E6E6" fill-opacity="0.4"/>
|
||||
<path d="M50.774 73.5735L96.387 50.2673L142 26.961L142 71.687L50.7739 122.046L50.774 73.5735Z" fill="#E6E6E6" fill-opacity="0.7"/>
|
||||
<svg width="92" height="100" viewBox="0 0 92 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 50L60 0V46.5L0 96.5V50Z" fill="#E6E6E6" fill-opacity="0.4"/>
|
||||
<path d="M32 53L92 3V49.5L32 99.5V53Z" fill="#E6E6E6" fill-opacity="0.7"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 253 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="146" height="146" viewBox="0 0 146 146" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.77397 72.5462L93.6741 23.0462L94.7739 70.0462L3.77395 119.046L3.77397 72.5462Z" fill="#ACACAC" fill-opacity="0.3"/>
|
||||
<path d="M50.774 73.5735L96.387 50.2673L142 26.961L142 71.687L50.7739 122.046L50.774 73.5735Z" fill="#FC9999" fill-opacity="0.5"/>
|
||||
<svg width="92" height="100" viewBox="0 0 92 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 50L60 0V46.5L0 96.5V50Z" fill="#ACACAC" fill-opacity="0.3"/>
|
||||
<path d="M32 53L92 3V49.5L32 99.5V53Z" fill="#FC9999" fill-opacity="0.5"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 253 B |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,178 @@
|
||||
/* Light theme variables */
|
||||
:root {
|
||||
--cc-bg: #ffffff;
|
||||
--cc-primary-color: #1c1c1c;
|
||||
--cc-secondary-color: #666666;
|
||||
|
||||
--cc-btn-primary-bg: #007BFF;
|
||||
--cc-btn-primary-color: #ffffff;
|
||||
--cc-btn-primary-border-color: #007BFF;
|
||||
--cc-btn-primary-hover-bg: #0056b3;
|
||||
--cc-btn-primary-hover-color: #ffffff;
|
||||
--cc-btn-primary-hover-border-color: #0056b3;
|
||||
|
||||
--cc-btn-secondary-bg: #f1f3f4;
|
||||
--cc-btn-secondary-color: #1c1c1c;
|
||||
--cc-btn-secondary-border-color: #f1f3f4;
|
||||
--cc-btn-secondary-hover-bg: #007BFF;
|
||||
--cc-btn-secondary-hover-color: #ffffff;
|
||||
--cc-btn-secondary-hover-border-color: #007BFF;
|
||||
|
||||
--cc-separator-border-color: #e0e0e0;
|
||||
|
||||
--cc-toggle-on-bg: #007BFF;
|
||||
--cc-toggle-off-bg: #667481;
|
||||
--cc-toggle-on-knob-bg: #ffffff;
|
||||
--cc-toggle-off-knob-bg: #ffffff;
|
||||
|
||||
--cc-toggle-enabled-icon-color: #ffffff;
|
||||
--cc-toggle-disabled-icon-color: #ffffff;
|
||||
|
||||
--cc-toggle-readonly-bg: #f1f3f4;
|
||||
--cc-toggle-readonly-knob-bg: #79747E;
|
||||
--cc-toggle-readonly-knob-icon-color: #f1f3f4;
|
||||
|
||||
--cc-section-category-border: #e0e0e0;
|
||||
|
||||
--cc-cookie-category-block-bg: #f1f3f4;
|
||||
--cc-cookie-category-block-border: #f1f3f4;
|
||||
--cc-cookie-category-block-hover-bg: #e9eff4;
|
||||
--cc-cookie-category-block-hover-border: #e9eff4;
|
||||
|
||||
--cc-cookie-category-expanded-block-bg: #f1f3f4;
|
||||
--cc-cookie-category-expanded-block-hover-bg: #e9eff4;
|
||||
|
||||
--cc-footer-bg: #ffffff;
|
||||
--cc-footer-color: #1c1c1c;
|
||||
--cc-footer-border-color: #ffffff;
|
||||
}
|
||||
|
||||
/* Dark theme variables */
|
||||
.cc--darkmode{
|
||||
--cc-bg: #2d2d2d;
|
||||
--cc-primary-color: #e5e5e5;
|
||||
--cc-secondary-color: #b0b0b0;
|
||||
|
||||
--cc-btn-primary-bg: #4dabf7;
|
||||
--cc-btn-primary-color: #2d2d2d;
|
||||
--cc-btn-primary-border-color: #4dabf7;
|
||||
--cc-btn-primary-hover-bg: #3d3d3d;
|
||||
--cc-btn-primary-hover-color: #e5e5e5;
|
||||
--cc-btn-primary-hover-border-color: #3d3d3d;
|
||||
|
||||
--cc-btn-secondary-bg: #3d3d3d;
|
||||
--cc-btn-secondary-color: #e5e5e5;
|
||||
--cc-btn-secondary-border-color: #3d3d3d;
|
||||
--cc-btn-secondary-hover-bg: #4dabf7;
|
||||
--cc-btn-secondary-hover-color: #2d2d2d;
|
||||
--cc-btn-secondary-hover-border-color: #4dabf7;
|
||||
|
||||
--cc-separator-border-color: #555555;
|
||||
|
||||
--cc-toggle-on-bg: #4dabf7;
|
||||
--cc-toggle-off-bg: #667481;
|
||||
--cc-toggle-on-knob-bg: #2d2d2d;
|
||||
--cc-toggle-off-knob-bg: #2d2d2d;
|
||||
|
||||
--cc-toggle-enabled-icon-color: #2d2d2d;
|
||||
--cc-toggle-disabled-icon-color: #2d2d2d;
|
||||
|
||||
--cc-toggle-readonly-bg: #555555;
|
||||
--cc-toggle-readonly-knob-bg: #8e8e8e;
|
||||
--cc-toggle-readonly-knob-icon-color: #555555;
|
||||
|
||||
--cc-section-category-border: #555555;
|
||||
|
||||
--cc-cookie-category-block-bg: #3d3d3d;
|
||||
--cc-cookie-category-block-border: #3d3d3d;
|
||||
--cc-cookie-category-block-hover-bg: #4d4d4d;
|
||||
--cc-cookie-category-block-hover-border: #4d4d4d;
|
||||
|
||||
--cc-cookie-category-expanded-block-bg: #3d3d3d;
|
||||
--cc-cookie-category-expanded-block-hover-bg: #4d4d4d;
|
||||
|
||||
--cc-footer-bg: #2d2d2d;
|
||||
--cc-footer-color: #e5e5e5;
|
||||
--cc-footer-border-color: #2d2d2d;
|
||||
}
|
||||
.cm__body{
|
||||
max-width: 90% !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
|
||||
}
|
||||
|
||||
.cm__desc{
|
||||
max-width: 70rem !important;
|
||||
}
|
||||
|
||||
.cm__btns{
|
||||
flex-direction: row-reverse !important;
|
||||
gap:10px !important;
|
||||
padding-top: 3.4rem !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1400px) {
|
||||
.cm__body{
|
||||
max-width: 90% !important;
|
||||
flex-direction: column !important;
|
||||
align-items: normal !important;
|
||||
}
|
||||
|
||||
.cm__btns{
|
||||
padding-top: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Toggle visibility fixes */
|
||||
#cc-main .section__toggle {
|
||||
opacity: 0 !important; /* Keep invisible but functional */
|
||||
}
|
||||
|
||||
#cc-main .toggle__icon {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
#cc-main .toggle__icon-circle {
|
||||
display: block !important;
|
||||
position: absolute !important;
|
||||
transition: transform 0.25s ease !important;
|
||||
}
|
||||
|
||||
#cc-main .toggle__icon-on,
|
||||
#cc-main .toggle__icon-off {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
position: absolute !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* Ensure toggles are visible in both themes */
|
||||
#cc-main .toggle__icon {
|
||||
background: var(--cc-toggle-off-bg) !important;
|
||||
border: 1px solid var(--cc-toggle-off-bg) !important;
|
||||
}
|
||||
|
||||
#cc-main .section__toggle:checked ~ .toggle__icon {
|
||||
background: var(--cc-toggle-on-bg) !important;
|
||||
border: 1px solid var(--cc-toggle-on-bg) !important;
|
||||
}
|
||||
|
||||
/* Ensure toggle text is visible */
|
||||
#cc-main .pm__section-title {
|
||||
color: var(--cc-primary-color) !important;
|
||||
}
|
||||
|
||||
#cc-main .pm__section-desc {
|
||||
color: var(--cc-secondary-color) !important;
|
||||
}
|
||||
|
||||
/* Make sure the modal has proper contrast */
|
||||
#cc-main .pm {
|
||||
background: var(--cc-bg) !important;
|
||||
color: var(--cc-primary-color) !important;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "جارٍ التحميل...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "إستبدال و عكس الألوان",
|
||||
"desc": "استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف"
|
||||
},
|
||||
"convert": {
|
||||
"title": "تحويل"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "استخراج الصفحات"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "إزالة الصفحات",
|
||||
"desc": "حذف الصفحات غير المرغوب فيها من مستند PDF الخاص بك."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "إزالة الصورة",
|
||||
"desc": "إزالة الصورة من PDF لتقليل حجم الملف"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "تقسيم تلقائي حسب الحجم/العدد",
|
||||
"desc": "تقسيم ملف PDF واحد إلى مستندات متعددة بناءً على الحجم أو عدد الصفحات أو عدد المستندات"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "ضبط الألوان/التباين",
|
||||
"desc": "ضبط التباين والتشبع والسطوع لملف PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "إستبدال و عكس الألوان",
|
||||
"desc": "استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "تغيير الأذونات"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "الترتيب حسب الاسم",
|
||||
"sortByDate": "الترتيب حسب التاريخ",
|
||||
"removeCertSign": "إزالة التوقيع الرقمي في الملف المدمج؟",
|
||||
"submit": "دمج"
|
||||
"submit": "دمج",
|
||||
"sortBy": {
|
||||
"filename": "اسم الملف"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "عمليات الصفحة,تقسيم,صفحات متعددة,قص,جانب الخادم",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "المستند رقم 6: الصفحة 10"
|
||||
},
|
||||
"splitPages": "أدخل الصفحات المراد تقسيمها:",
|
||||
"submit": "تقسيم"
|
||||
"submit": "تقسيم",
|
||||
"steps": {
|
||||
"settings": "إعدادات"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "حجم الملف"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "حجم الملف"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "جانب الخادم",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "نص",
|
||||
"2": "صورة"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "نص"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "حجم الخط"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "تغيير"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "إزالة الصفحات,حذف الصفحات"
|
||||
"tags": "إزالة الصفحات,حذف الصفحات",
|
||||
"title": "إزالة الصفحات",
|
||||
"submit": "إزالة الصفحات"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "تأمين,أمان",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "يقيد ما يمكن القيام به بالمستند بمجرد فتحه (غير مدعوم من قبل جميع القراء)",
|
||||
"16": "يقيد فتح المستند نفسه"
|
||||
},
|
||||
"submit": "تشفير"
|
||||
"submit": "تشفير",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "تغيير الأذونات"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "تأمين,فك التشفير,أمان,إزالة كلمة المرور,حذف كلمة المرور",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "حدد PDF لفك التشفير",
|
||||
"2": "كلمة المرور"
|
||||
},
|
||||
"submit": "إزالة"
|
||||
"submit": "إزالة",
|
||||
"desc": "إزالة الحماية بكلمة مرور من مستند PDF الخاص بك.",
|
||||
"password": {
|
||||
"stepTitle": "إزالة كلمة المرور",
|
||||
"label": "كلمة المرور الحالية"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "ضغط,صغير,ضئيل"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "يرجى قراءة هذه الوثائق حول كيفية استخدام هذا للغات أخرى و/أو الاستخدام ليس في Docker",
|
||||
"credit": "تستخدم هذه الخدمة qpdf و Tesseract للتعرف الضوئي على الحروف.",
|
||||
"submit": "معالجة PDF باستخدام OCR"
|
||||
"submit": "معالجة PDF باستخدام OCR",
|
||||
"desc": "يقوم برنامج التنظيف بمسح واكتشاف النص من الصور داخل ملف PDF ويعيد إضافته كنص",
|
||||
"settings": {
|
||||
"title": "إعدادات",
|
||||
"ocrMode": {
|
||||
"label": "وضع التعرف الضوئي على الحروف"
|
||||
},
|
||||
"languages": {
|
||||
"label": "اللغات"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "وضع التعرف الضوئي على الحروف"
|
||||
},
|
||||
"languages": {
|
||||
"title": "اللغات"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "صورة,صورة فوتوغرافية,حفظ,أرشيف,ملف مضغوط,التقاط,انتزاع",
|
||||
@@ -924,7 +1008,7 @@
|
||||
"sharedSigs": "توقيعات مشتركة",
|
||||
"noSavedSigs": "لم يتم العثور على توقيعات محفوظة",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "حذف",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "تسطيح",
|
||||
"header": "تسطيح ملفات PDF",
|
||||
"flattenOnlyForms": "تسطيح النماذج فقط",
|
||||
"submit": "تسطيح"
|
||||
"submit": "تسطيح",
|
||||
"steps": {
|
||||
"settings": "إعدادات"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "تسطيح النماذج فقط"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "إصلاح,استعادة,تصحيح,استرداد",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "يستخدم WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "لغة الترميز,محتوى الويب,تحويل,تحويل",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "تحويل"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "معلومات,بيانات,إحصائيات,إحصاءات",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "متقدم"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "إضافة"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "صفحات",
|
||||
"placeholder": "(مثال: 1,2,8 أو 4,7,12-16 أو 2n-1)"
|
||||
},
|
||||
"export": "تصدير"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,استخراج الجدول,استخراج,تحويل"
|
||||
@@ -1261,8 +1366,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "السبب",
|
||||
"location": "الموقع",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1386,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "الإصدار",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "الوضع التلقائي - يضبط الجودة تلقائيًا للحصول على ملف PDF بالحجم المحدد",
|
||||
"5": "حجم PDF المتوقع (على سبيل المثال 25 ميجا بايت، 10.8 ميجا بايت، 25 كيلو بايت)"
|
||||
},
|
||||
"submit": "ضغط"
|
||||
"submit": "ضغط",
|
||||
"method": {
|
||||
"filesize": "حجم الملف"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "تنزيل",
|
||||
"convert": {
|
||||
"title": "تحويل",
|
||||
"settings": "إعدادات",
|
||||
"color": "لون",
|
||||
"greyscale": "تدرج الرمادي",
|
||||
"fillPage": "ملء الصفحة",
|
||||
"pdfaDigitalSignatureWarning": "يحتوي PDF على توقيع رقمي. سيتم إزالة هذا في الخطوة التالية.",
|
||||
"grayscale": "تدرج الرمادي"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "تحديد الكل",
|
||||
"deselectAll": "إلغاء تحديد الكل"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "توقيع"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "جارٍ التحميل...",
|
||||
"or": "أو"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "الاسم",
|
||||
"fileVersion": "الإصدار",
|
||||
"selectAll": "تحديد الكل",
|
||||
"deselectAll": "إلغاء تحديد الكل",
|
||||
"deleteSelected": "حذف المحدد",
|
||||
"download": "تنزيل",
|
||||
"delete": "حذف"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "تنظيف PDF",
|
||||
"steps": {
|
||||
"settings": "إعدادات"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "تغيير الأذونات",
|
||||
"submit": "تغيير الأذونات",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "منع تجميع المستند"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "منع استخراج المحتوى"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "منع الاستخراج للوصول"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "منع ملء النموذج"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "منع التعديل"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "منع تعديل التعليق التوضيحي"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "منع الطباعة"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "منع طباعة التنسيقات المختلفة"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "تغيير الأذونات"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Yüklənir...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Qabaqcıl Rəng Seçimləri",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Çevir"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Səhifələri çıxar"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Sil",
|
||||
"desc": "PDF Sənədindən istəmədiyin şəkilləri sil."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Şəkli Sil",
|
||||
"desc": "Fayl ölçüsünü azaltmaq üçün PDF-dən şəkil sil"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Say/Ölçüyə Əsasən Avtomatik Ayır",
|
||||
"desc": "PDF-i ölçüyə, səhifə sayına və ya sənəd sayına əsasən bir neçə PDF-ə ayır."
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Rəngləri/Kontrastı Tənzimlə",
|
||||
"desc": "PDF-in kontrastını, parlaqlığını, rəng doyğunluğunu tənzimlə"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Qabaqcıl Rəng Seçimləri",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "İcazələri Dəyişdir"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Böl",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Sil",
|
||||
"dragDropMessage": "Seçilmiş Səhifə(lər)",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Ada görə çeşidləyin",
|
||||
"sortByDate": "Tarixə görə çeşidləyin",
|
||||
"removeCertSign": "Birləşdirilmiş faylda rəqəmsal imza silinsin?",
|
||||
"submit": "Birləşdirin"
|
||||
"submit": "Birləşdirin",
|
||||
"sortBy": {
|
||||
"filename": "Fayl Adı"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Səhifə əməliyyarları,böl,Çoxlu Səhifə,kəs,server-tərəf",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Sənəd #6: Səhifə 10"
|
||||
},
|
||||
"splitPages": "Bölünəcək Səhifələri Daxil Edin:",
|
||||
"submit": "Bölün"
|
||||
"submit": "Bölün",
|
||||
"steps": {
|
||||
"settings": "Parametrlər"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Fayl Ölçüsü"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Fayl Ölçüsü"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server-tərəf",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Mətn",
|
||||
"2": "Şəkil"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Yazı"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Şrift Ölçüsü"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Dəyiş"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Səhifələri təmizlə,səhifələri sil"
|
||||
"tags": "Səhifələri təmizlə,səhifələri sil",
|
||||
"title": "Sil",
|
||||
"submit": "Sil"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "təhlükəsiz,təhlükəsizlik",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Sənəd açıldıqdan sonra onunla nə edilə biləcəyini limitləndir (Bütün oxuyucular dəstəkləmir)",
|
||||
"16": "Sənədin özünün açılmağını limitləndirir"
|
||||
},
|
||||
"submit": "Şifrlə"
|
||||
"submit": "Şifrlə",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "İcazələri Dəyişdir"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "təhlükəsiz,deşifr,təhlükəsizlik,kodu aç,kodu sil",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Deşifr Üçün PDF-i Seç",
|
||||
"2": "Şifr"
|
||||
},
|
||||
"submit": "Sil"
|
||||
"submit": "Sil",
|
||||
"desc": "PDF Sənədindən şifr qorumasını götür.",
|
||||
"password": {
|
||||
"stepTitle": "Şifri Sil",
|
||||
"label": "Cari Şifr"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "sıx,balaca,kiçik"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Bunu digər dillər üçün necə istifadə etmək və/və ya docker-də istifadə etməmək üçün bu dokumentasiyanı oxuyun",
|
||||
"credit": "Bu servis OST (OCR) üçün \"OCRmyPDF\" və \"Tesseract\" istifadə edir.",
|
||||
"submit": "PDF-i OST ilə işlə"
|
||||
"submit": "PDF-i OST ilə işlə",
|
||||
"desc": "Skanları təmizləyir və PDF-in içərisindəki şəkillərdəki yazını tapıb mətn olaraq əlavə edir.",
|
||||
"settings": {
|
||||
"title": "Parametrlər",
|
||||
"ocrMode": {
|
||||
"label": "OST (OCR) Rejimi"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Dillər"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OST (OCR) Rejimi"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Dillər"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "şəkil,foto,saxla,arxiv,zip,çək,götür",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Düzləşdirin",
|
||||
"header": "PDF-i düzləşdirin",
|
||||
"flattenOnlyForms": "Yalnız formaları düzəldin",
|
||||
"submit": "Düzləşdirin"
|
||||
"submit": "Düzləşdirin",
|
||||
"steps": {
|
||||
"settings": "Parametrlər"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Yalnız formaları düzəldin"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "düzəlt,bərpa et,korreksiya et,geri qaytar",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "WeasyPrint İstifadə Edir"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "işarələmə,web-məzmun,dəyişmə,çevirmə",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Çevir"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informasiy,data,məlumatlar,statistika",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Qabaqcıl"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Əlavə et"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Səhifələr",
|
||||
"placeholder": "(məsələn, 1,2,8 və ya 4,7,12-16 və ya 2n-1)"
|
||||
},
|
||||
"export": "İxrac Et"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Cədvəl xaricetmə,xaric et,çevir"
|
||||
@@ -1261,8 +1366,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Səbəb",
|
||||
"location": "Məkan",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1386,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Versiya",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Avto mod - PDF-in dəqiq ölçüsünü əldə etmək üçün keyfiyyəti avtomatik tənzimləyir",
|
||||
"5": "Gözlənilən PDF Ölçüsü (məsələn, 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Sıxışdır"
|
||||
"submit": "Sıxışdır",
|
||||
"method": {
|
||||
"filesize": "Fayl Ölçüsü"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1665,81 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Endir",
|
||||
"convert": {
|
||||
"title": "Çevir",
|
||||
"settings": "Parametrlər",
|
||||
"color": "Rəng",
|
||||
"greyscale": "Boz Tonlama",
|
||||
"fillPage": "Səhifəni Doldur",
|
||||
"pdfaDigitalSignatureWarning": "PDF Rəqəmsal İmza Ehtiva Edir.Bu, növbəti addımda silinəcək.",
|
||||
"grayscale": "Boz Tonlama"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Hamısını Seç",
|
||||
"deselectAll": "Hamısını Seçməni Ləğv Et"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "İmzala"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Yüklənir..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Ad",
|
||||
"fileVersion": "Versiya",
|
||||
"selectAll": "Hamısını Seç",
|
||||
"deselectAll": "Hamısını Seçməni Ləğv Et",
|
||||
"deleteSelected": "Seçilmişi Sil",
|
||||
"download": "Endir",
|
||||
"delete": "Sil"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF-i Təmizlə",
|
||||
"steps": {
|
||||
"settings": "Parametrlər"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "İcazələri Dəyişdir",
|
||||
"submit": "İcazələri Dəyişdir",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Sənədin Yığılmasının Qarşısını Al"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Məzmunun Çıxarılmasının Qarşısını Al"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Əlçatanlıq Üçün Çıxarılmasının Qarşısını Alın"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Formanın Doldurulmasının Qarşısını Alır"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Modifikasiyanın Qarşısını Al"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Annotasiyanın Dəyişdirilməsinin Qarşısını Almaq"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Çapın Qarşısını Al"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Fərqli Formatlarda Çapın Qarşısını Al"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "İcazələri Dəyişdir"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@
|
||||
"scanHelp": "Помощ за сканиране на папки",
|
||||
"deletePrompt": "Сигурни ли сте, че искате да изтриете pipeline",
|
||||
"tags": "автоматизиране,последователност,чрез скриптове,пакетен процес",
|
||||
"title": "Pipeline"
|
||||
"title": "Pipeline (Разширено)"
|
||||
},
|
||||
"pipelineOptions": {
|
||||
"header": "Pipeline Конфигурация",
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Зареждане на...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Замяна и обръщане на цвят",
|
||||
"desc": "Заменете цвета на текста и фона в PDF и обърнете пълния цвят на PDF, за да намалите размера на файла"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Преобразуване"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Извличане на страници"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Премахване",
|
||||
"desc": "Изтрийте нежеланите страници от вашия PDF документ."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Премахване на изображение",
|
||||
"desc": "Премахнете изображението от PDF, за да намалите размера на файла"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Автоматично разделяне по размер/брой",
|
||||
"desc": "Разделете един PDF на множество документи въз основа на размер, брой страници или брой документи"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Коригиране на цветове/контраст",
|
||||
"desc": "Коригиране на контраста, наситеността и яркостта на PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Замяна и обръщане на цвят",
|
||||
"desc": "Заменете цвета на текста и фона в PDF и обърнете пълния цвят на PDF, за да намалите размера на файла"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Промяна на правата"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Сортиране по име",
|
||||
"sortByDate": "Сортиране по дата",
|
||||
"removeCertSign": "Премахване на цифровия подпис в обединения файл?",
|
||||
"submit": "Обединяване"
|
||||
"submit": "Обединяване",
|
||||
"sortBy": {
|
||||
"filename": "Име на файл"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Операции на страницата,разделяне,Множество страници,изрязване,сървърна страна",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Документ #6: Страница 10"
|
||||
},
|
||||
"splitPages": "Въведете страници за разделяне:",
|
||||
"submit": "Разделяне"
|
||||
"submit": "Разделяне",
|
||||
"steps": {
|
||||
"settings": "Настройки"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Размер на файла"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Размер на файла"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "от страната на сървъра",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Текст",
|
||||
"2": "Изображение"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Текст"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Размер на шрифт"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Промени"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Премахване на страници,изтриване на страници"
|
||||
"tags": "Премахване на страници,изтриване на страници",
|
||||
"title": "Премахване",
|
||||
"submit": "Премахване"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "сигурен,сигурност",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Ограничава какво може да се прави с документа, след като бъде отворен (не се поддържа от всички четци)",
|
||||
"16": "Ограничава отварянето на самия документ"
|
||||
},
|
||||
"submit": "Шифроване"
|
||||
"submit": "Шифроване",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Промяна на правата"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "сигурно,декриптиране,сигурност,отмяна на парола,изтриване на парола",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Изберете PDF за Декриптиране",
|
||||
"2": "Парола"
|
||||
},
|
||||
"submit": "Премахване"
|
||||
"submit": "Премахване",
|
||||
"desc": "Премахнете защитата с парола от вашия PDF документ.",
|
||||
"password": {
|
||||
"stepTitle": "Премахване на парола",
|
||||
"label": "Текуща парола"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "мачкам,малък,мъничък"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Моля, прочетете тази документация за това как да използвате това за други езици и/или да не използвате в docker",
|
||||
"credit": "Тази услуга използва qpdf и Tesseract за OCR.",
|
||||
"submit": "Обработка на PDF чрез OCR"
|
||||
"submit": "Обработка на PDF чрез OCR",
|
||||
"desc": "Почиства, сканира и открива текст от изображения в PDF и го добавя отново като текст.",
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"ocrMode": {
|
||||
"label": "OCR режим"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Езици"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR режим"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Езици"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "изображение,снимка,запазване,архивиране,архив,заснемане,грабване",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Изравнете",
|
||||
"header": "Изравнете PDF-и",
|
||||
"flattenOnlyForms": "Изравнете само форми",
|
||||
"submit": "Изравнете"
|
||||
"submit": "Изравнете",
|
||||
"steps": {
|
||||
"settings": "Настройки"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Изравнете само форми"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "поправка,възстановяване,корекция,възстановяване",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Показване на слоеве (щракнете два пъти, за да върнете всички слоеве в състояние по подразбиране)",
|
||||
"colourPicker": "Избор на цвят",
|
||||
"findCurrentOutlineItem": "Намиране на текущия елемент от контура",
|
||||
"applyChanges": "Прилагане на промените"
|
||||
"applyChanges": "Прилагане на промените",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Разширено"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Добави"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Страници"
|
||||
},
|
||||
"export": "Експорт"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,извличане на таблица,извличане,конвертиране"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "Автоматичен режим - Автоматично настройва качеството, за да получи PDF с точен размер",
|
||||
"5": "Очакван PDF размер (напр. 25МБ, 10.8МБ, 25КБ)"
|
||||
},
|
||||
"submit": "Компресиране"
|
||||
"submit": "Компресиране",
|
||||
"method": {
|
||||
"filesize": "Размер на файла"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Този файл е защитен с парола. Моля, въведете паролата:",
|
||||
@@ -1557,5 +1664,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Изтегли",
|
||||
"convert": {
|
||||
"title": "Преобразуване",
|
||||
"settings": "Настройки",
|
||||
"color": "Цвят",
|
||||
"greyscale": "Скала на сивото",
|
||||
"fillPage": "Попълване на страница",
|
||||
"pdfaDigitalSignatureWarning": "PDF файлът съдържа цифров подпис. Това ще бъде премахнато в следващата стъпка.",
|
||||
"grayscale": "Скала на сивото"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Изберете всички",
|
||||
"deselectAll": "Отмяна на избора на всички"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Подпишете"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Зареждане на...",
|
||||
"or": "или"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Име",
|
||||
"fileVersion": "Версия",
|
||||
"selectAll": "Изберете всички",
|
||||
"deselectAll": "Отмяна на избора на всички",
|
||||
"deleteSelected": "Изтриване на избраното",
|
||||
"download": "Изтегли",
|
||||
"delete": "Изтрий"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Дезинфектирай PDF",
|
||||
"steps": {
|
||||
"settings": "Настройки"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Промяна на правата",
|
||||
"submit": "Промяна на правата",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Предотвратяване на сглобяването на документ"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Предотвратете извличането на съдържание"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Предотвратете извличането за достъпност"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Предотвратяване на попълване на формуляр"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Предотвратяване на модификация"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Предотвратяване на модификация на анотация"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Предотвратявам на отпечатването"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Предотвратете отпечатването на различни формати"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Промяна на правата"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Carregant...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Reemplaça i Inverteix Color",
|
||||
"desc": "Reemplaça el color del text i el fons en un PDF i inverteix tot el color del PDF per reduir la mida del fitxer"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Converteix"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extreu Pàgines"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Elimina",
|
||||
"desc": "Elimina pàgines del document PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Elimina imatge",
|
||||
"desc": "Elimina imatges d'un PDF per reduir la mida del fitxer"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Divisió Automàtica per Mida/Quantitat",
|
||||
"desc": "Divideix un únic PDF en múltiples documents basant-se en la mida, el nombre de pàgines o el nombre de documents"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Ajusta Colors/Contrast",
|
||||
"desc": "Ajusta colors/contrast, saturació i brillantor"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Reemplaça i Inverteix Color",
|
||||
"desc": "Reemplaça el color del text i el fons en un PDF i inverteix tot el color del PDF per reduir la mida del fitxer"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Canviar Permissos"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Ordena per nom",
|
||||
"sortByDate": "Ordena per data",
|
||||
"removeCertSign": "Eliminar la signatura digital en el fitxer fusionat?",
|
||||
"submit": "Fusiona"
|
||||
"submit": "Fusiona",
|
||||
"sortBy": {
|
||||
"filename": "Nom del Fitxer"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "operacions de pàgina,divideix,Multi-Pàgina,talla,servidor",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Document #6: Pàgina 10"
|
||||
},
|
||||
"splitPages": "Introdueix les pàgines per dividir-les:",
|
||||
"submit": "Divideix"
|
||||
"submit": "Divideix",
|
||||
"steps": {
|
||||
"settings": "Opcions"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Mida del Fitxer"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Mida del Fitxer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "servidor",
|
||||
@@ -712,6 +760,9 @@
|
||||
"type": {
|
||||
"1": "Text",
|
||||
"2": "Imatge"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Mida del tipus de lletra"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +785,9 @@
|
||||
"submit": "Canviar Permissos"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "eliminar pàgines,suprimir pàgines"
|
||||
"tags": "eliminar pàgines,suprimir pàgines",
|
||||
"title": "Elimina",
|
||||
"submit": "Elimina"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "segur,seguretat",
|
||||
@@ -758,7 +811,12 @@
|
||||
"15": "Restringeix el que es pot fer amb el document un cop obert (No compatible amb tots els lectors)",
|
||||
"16": "Restringeix l'obertura del document"
|
||||
},
|
||||
"submit": "Encripta"
|
||||
"submit": "Encripta",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Canviar Permissos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "segur,desencripta,seguretat,eliminar contrasenya,suprimir contrasenya",
|
||||
@@ -768,7 +826,12 @@
|
||||
"1": "Selecciona el PDF a Desxifrar",
|
||||
"2": "Contrasenya"
|
||||
},
|
||||
"submit": "Eliminar Contrasenya"
|
||||
"submit": "Eliminar Contrasenya",
|
||||
"desc": "Elimina la contrasenya del document PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Elimina Contrasenya",
|
||||
"label": "Contrasenya Actual"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "estrènyer,petit,minúscul"
|
||||
@@ -829,7 +892,25 @@
|
||||
},
|
||||
"help": "Llegeix aquesta documentació sobre com utilitzar-la per a altres idiomes i/o no utilitzar-la a Docker",
|
||||
"credit": "Aquest servei fa servir qpdf i Tesseract per a OCR.",
|
||||
"submit": "Processa PDF amb OCR"
|
||||
"submit": "Processa PDF amb OCR",
|
||||
"desc": "Neteja escanejats i detecta text d'imatges dins d'un PDF, tornant-lo a afegir com a text.",
|
||||
"settings": {
|
||||
"title": "Opcions",
|
||||
"ocrMode": {
|
||||
"label": "Mode OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Idiomes"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Mode OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Idiomes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "imatge,foto,desa,arxiva,zip,captura,agafa",
|
||||
@@ -924,7 +1005,7 @@
|
||||
"sharedSigs": "Signatures Compartides",
|
||||
"noSavedSigs": "No s'han trobat signatures desades",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Esborra",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1019,13 @@
|
||||
"title": "Aplanar",
|
||||
"header": "Aplana els PDF",
|
||||
"flattenOnlyForms": "Aplana només els formularis",
|
||||
"submit": "Aplanar"
|
||||
"submit": "Aplanar",
|
||||
"steps": {
|
||||
"settings": "Opcions"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Aplana només els formularis"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "repara,restaura,correcció,recupera",
|
||||
@@ -1109,7 +1196,7 @@
|
||||
"tags": "marcatge,contingut web,transformació,convertir,md",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Converteix"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informació,dades,estadístiques,estadístiques",
|
||||
@@ -1174,7 +1261,22 @@
|
||||
"showLayers": "Mostra les capes (doble clic per restablir totes les capes a l'estat per defecte)",
|
||||
"colourPicker": "Selector de colors",
|
||||
"findCurrentOutlineItem": "Troba l'element actual de l'esquema",
|
||||
"applyChanges": "Aplica els canvis"
|
||||
"applyChanges": "Aplica els canvis",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avançat"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Afegeix"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Pàgines",
|
||||
"placeholder": "(p. ex. 1,2,8 o 4,7,12-16 o 2n-1)"
|
||||
},
|
||||
"export": "Exporta"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Extracció de taules,extreure,convertir"
|
||||
@@ -1386,7 +1488,10 @@
|
||||
"4": "Mode automàtic: ajusta automàticament la qualitat perquè el PDF tingui la mida exacta",
|
||||
"5": "Mida esperada del PDF (p. ex. 25 MB, 10,8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Comprimir"
|
||||
"submit": "Comprimir",
|
||||
"method": {
|
||||
"filesize": "Mida del Fitxer"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Aquest fitxer està protegit amb contrasenya. Si us plau, introdueix la contrasenya:",
|
||||
@@ -1557,5 +1662,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Descarrega",
|
||||
"convert": {
|
||||
"title": "Converteix",
|
||||
"settings": "Opcions",
|
||||
"color": "Color",
|
||||
"greyscale": "Escala de Grisos",
|
||||
"fillPage": "Omple la Pàgina",
|
||||
"pdfaDigitalSignatureWarning": "El PDF conté una signatura digital. Aquesta serà eliminada en el següent pas.",
|
||||
"grayscale": "Escala de Grisos"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Selecciona-ho tot",
|
||||
"deselectAll": "Desselecciona-ho tot"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Signa"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Carregant...",
|
||||
"or": "o"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nom",
|
||||
"fileVersion": "Versió",
|
||||
"selectAll": "Selecciona-ho tot",
|
||||
"deselectAll": "Desselecciona-ho tot",
|
||||
"deleteSelected": "Suprimeix seleccionades",
|
||||
"download": "Descarrega",
|
||||
"delete": "Esborra"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Neteja PDF",
|
||||
"steps": {
|
||||
"settings": "Opcions"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Canviar Permissos",
|
||||
"submit": "Canviar Permissos",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Evita el muntatge del document"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Evita l'extracció de contingut"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Evita l'extracció de contingut per accessibilitat"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Evita emplenar formularis"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Evita modificacions"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Evita modificacions d'annotacions"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Evita impressió"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Evita impressió en diferents formats"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Canviar Permissos"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Načítání...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Nahrazení a inverze barev",
|
||||
"desc": "Úprava barev textu a pozadí v PDF nebo jejich inverze ke snížení velikosti souboru"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Převést"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extrahovat stránky"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Odstranit",
|
||||
"desc": "Smazat nežádoucí stránky z vašeho PDF dokumentu."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Odstranit obrázek",
|
||||
"desc": "Odstranit obrázek z PDF pro zmenšení velikosti souboru"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Automaticky rozdělit podle velikosti/počtu",
|
||||
"desc": "Rozdělí jeden PDF na více dokumentů podle velikosti, počtu stránek nebo počtu dokumentů"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Upravit barvy/kontrast",
|
||||
"desc": "Upravit kontrast, sytost a jas PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Nahrazení a inverze barev",
|
||||
"desc": "Úprava barev textu a pozadí v PDF nebo jejich inverze ke snížení velikosti souboru"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Změnit oprávnění"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Seřadit podle názvu",
|
||||
"sortByDate": "Seřadit podle data",
|
||||
"removeCertSign": "Odstranit digitální podpis v sloučeném souboru?",
|
||||
"submit": "Sloučit"
|
||||
"submit": "Sloučit",
|
||||
"sortBy": {
|
||||
"filename": "Název souboru"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operace se stránkami,rozdělit,Více stránek,vyjmout,serverová strana",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: Stránka 10"
|
||||
},
|
||||
"splitPages": "Zadejte stránky pro rozdělení:",
|
||||
"submit": "Rozdělit"
|
||||
"submit": "Rozdělit",
|
||||
"steps": {
|
||||
"settings": "Nastavení"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Velikost souboru"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Velikost souboru"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "serverová strana",
|
||||
@@ -712,6 +760,9 @@
|
||||
"type": {
|
||||
"1": "Text",
|
||||
"2": "Obrázek"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Velikost písma"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +785,9 @@
|
||||
"submit": "Změnit"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Odstranit stránky,smazat stránky"
|
||||
"tags": "Odstranit stránky,smazat stránky",
|
||||
"title": "Odstranit",
|
||||
"submit": "Odstranit"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "zabezpečit,bezpečnost",
|
||||
@@ -758,7 +811,12 @@
|
||||
"15": "Omezuje, co lze s dokumentem dělat po jeho otevření (Není podporováno všemi čtečkami)",
|
||||
"16": "Omezuje samotné otevření dokumentu"
|
||||
},
|
||||
"submit": "Zašifrovat"
|
||||
"submit": "Zašifrovat",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Změnit oprávnění"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "zabezpečit,dešifrovat,bezpečnost,odstranit heslo,smazat heslo",
|
||||
@@ -768,7 +826,12 @@
|
||||
"1": "Vyberte PDF k dešifrování",
|
||||
"2": "Heslo"
|
||||
},
|
||||
"submit": "Odstranit"
|
||||
"submit": "Odstranit",
|
||||
"desc": "Odstranit ochranu heslem z vašeho PDF dokumentu.",
|
||||
"password": {
|
||||
"stepTitle": "Odstranit heslo",
|
||||
"label": "Současné heslo"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "stlačit,malý,drobný"
|
||||
@@ -829,7 +892,25 @@
|
||||
},
|
||||
"help": "Přečtěte si prosím tuto dokumentaci o použití pro jiné jazyky a/nebo použití mimo Docker",
|
||||
"credit": "Tato služba používá qpdf a Tesseract pro OCR.",
|
||||
"submit": "Zpracovat PDF pomocí OCR"
|
||||
"submit": "Zpracovat PDF pomocí OCR",
|
||||
"desc": "Vyčistí skeny a detekuje text z obrázků v PDF a znovu ho přidá jako text.",
|
||||
"settings": {
|
||||
"title": "Nastavení",
|
||||
"ocrMode": {
|
||||
"label": "Režim OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Jazyky"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Režim OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Jazyky"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "obrázek,fotka,uložit,archiv,zip,zachytit,získat",
|
||||
@@ -938,7 +1019,13 @@
|
||||
"title": "Zploštit",
|
||||
"header": "Zploštit PDF",
|
||||
"flattenOnlyForms": "Zploštit pouze formuláře",
|
||||
"submit": "Zploštit"
|
||||
"submit": "Zploštit",
|
||||
"steps": {
|
||||
"settings": "Nastavení"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Zploštit pouze formuláře"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "opravit,obnovit,korekce,obnovit",
|
||||
@@ -1174,7 +1261,22 @@
|
||||
"showLayers": "Zobrazit vrstvy (dvojklik pro obnovení všech vrstev do výchozího stavu)",
|
||||
"colourPicker": "Výběr barvy",
|
||||
"findCurrentOutlineItem": "Najít aktuální položku osnovy",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Pokročilé"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Přidat"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Stránky",
|
||||
"placeholder": "(např. 1,2,8 nebo 4,7,12-16 nebo 2n-1)"
|
||||
},
|
||||
"export": "Exportovat"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Extrakce tabulek,extrahovat,převést"
|
||||
@@ -1386,7 +1488,10 @@
|
||||
"4": "Automatický režim - Automaticky upravuje kvalitu pro dosažení přesné velikosti PDF",
|
||||
"5": "Očekávaná velikost PDF (např. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Komprimovat"
|
||||
"submit": "Komprimovat",
|
||||
"method": {
|
||||
"filesize": "Velikost souboru"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Tento soubor je chráněn heslem. Zadejte prosím heslo:",
|
||||
@@ -1557,5 +1662,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Stáhnout",
|
||||
"convert": {
|
||||
"title": "Převést",
|
||||
"settings": "Nastavení",
|
||||
"color": "Barva",
|
||||
"greyscale": "Stupně šedi",
|
||||
"fillPage": "Vyplnit stránku",
|
||||
"pdfaDigitalSignatureWarning": "PDF obsahuje digitální podpis, který bude v dalším kroku odstraněn.",
|
||||
"grayscale": "Stupně šedi"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Vybrat vše",
|
||||
"deselectAll": "Zrušit výběr všeho"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Podepsat"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Načítání...",
|
||||
"or": "nebo"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Jméno",
|
||||
"fileVersion": "Verze",
|
||||
"selectAll": "Vybrat vše",
|
||||
"deselectAll": "Zrušit výběr všeho",
|
||||
"deleteSelected": "Smazat vybrané",
|
||||
"download": "Stáhnout",
|
||||
"delete": "Smazat"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Sanitizovat PDF",
|
||||
"steps": {
|
||||
"settings": "Nastavení"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Změnit oprávnění",
|
||||
"submit": "Změnit oprávnění",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Zabránit sestavení dokumentu"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Zabránit extrakci obsahu"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Zabránit extrakci pro přístupnost"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Zabránit vyplňování formulářů"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Zabránit úpravám"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Zabránit úpravám poznámek"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Zabránit tisku"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Zabránit tisku v různých formátech"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Změnit oprávnění"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@
|
||||
"scanHelp": "Mappe Scanning Hjælp",
|
||||
"deletePrompt": "Er du sikker på, at du vil slette pipeline",
|
||||
"tags": "automatisér,sekvens,scriptet,batch-proces",
|
||||
"title": "Pipeline"
|
||||
"title": "Pipeline (Avanceret)"
|
||||
},
|
||||
"pipelineOptions": {
|
||||
"header": "Pipeline Konfiguration",
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Laster...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Erstatt farve for tekst og baggrund i en PDF og omgivende farve til fuld farve af PDF for at redusere filstørrelsen."
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konvertér"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Udtræk Sider"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Fjern",
|
||||
"desc": "Slet uønskede sider fra dit PDF-dokument."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Fjern billede",
|
||||
"desc": "Fjern billede fra PDF for at reducere filstørrelse"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Auto Opdel efter Størrelse/Antal",
|
||||
"desc": "Opdel en enkelt PDF i flere dokumenter baseret på størrelse, sideantal eller dokumentantal"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Justér Farver/Kontrast",
|
||||
"desc": "Justér Kontrast, Mætning og Lysstyrke af en PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Erstatt farve for tekst og baggrund i en PDF og omgivende farve til fuld farve af PDF for at redusere filstørrelsen."
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Ændre Tilladelser"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -594,7 +626,7 @@
|
||||
"deselectAll": "Deselect All",
|
||||
"selectPages": "Page Select",
|
||||
"selectedPages": "Selected Pages",
|
||||
"page": "Page",
|
||||
"page": "Sidenummer",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"downloadAll": "Export",
|
||||
"downloadSelected": "Export Selected",
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Opdel",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Slet",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sortér efter navn",
|
||||
"sortByDate": "Sortér efter dato",
|
||||
"removeCertSign": "Fjern digital signatur i den flettede fil?",
|
||||
"submit": "Flet"
|
||||
"submit": "Flet",
|
||||
"sortBy": {
|
||||
"filename": "Filnavn"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Sideoperationer,opdel,Multi Side,klip,server side",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: Side 10"
|
||||
},
|
||||
"splitPages": "Indtast sider at opdele på:",
|
||||
"submit": "Opdel"
|
||||
"submit": "Opdel",
|
||||
"steps": {
|
||||
"settings": "Indstillinger"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Filstørrelse"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Filstørrelse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server side",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Tekst",
|
||||
"2": "Billede"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Tekst"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Skriftstørrelse"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Ændre"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Fjern sider,slet sider"
|
||||
"tags": "Fjern sider,slet sider",
|
||||
"title": "Fjern",
|
||||
"submit": "Fjern"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "sikker,sikkerhed",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Begrænser hvad der kan gøres med dokumentet, når det er åbnet (Understøttes ikke af alle læsere)",
|
||||
"16": "Begrænser åbningen af selve dokumentet"
|
||||
},
|
||||
"submit": "Kryptér"
|
||||
"submit": "Kryptér",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Ændre Tilladelser"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "sikker,Dekryptér,sikkerhed,fjern adgangskode,slet adgangskode",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Vælg PDF til Dekryptering",
|
||||
"2": "Adgangskode"
|
||||
},
|
||||
"submit": "Fjern"
|
||||
"submit": "Fjern",
|
||||
"desc": "Fjern adgangskodebeskyttelse fra dit PDF-dokument.",
|
||||
"password": {
|
||||
"stepTitle": "Fjern Adgangskode",
|
||||
"label": "Nuværende Adgangskode"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "klem,lille,tiny"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Læs venligst denne dokumentation om, hvordan man bruger dette til andre sprog og/eller brug uden for docker",
|
||||
"credit": "Denne tjeneste bruger qpdf og Tesseract til OCR.",
|
||||
"submit": "Behandl PDF med OCR"
|
||||
"submit": "Behandl PDF med OCR",
|
||||
"desc": "Oprydning af scanninger og genkender tekst fra billeder i en PDF og tilføjer den igen som tekst.",
|
||||
"settings": {
|
||||
"title": "Indstillinger",
|
||||
"ocrMode": {
|
||||
"label": "OCR-tilstand"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Sprog"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR-tilstand"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Sprog"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "billede,foto,gem,arkiv,zip,fang,grib",
|
||||
@@ -924,7 +1008,7 @@
|
||||
"sharedSigs": "Delte Signaturer",
|
||||
"noSavedSigs": "Ingen Gemte Signaturer Fundet",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Slet",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Udjævn",
|
||||
"header": "Udjævn PDF",
|
||||
"flattenOnlyForms": "Udjævn kun formularer",
|
||||
"submit": "Udjævn"
|
||||
"submit": "Udjævn",
|
||||
"steps": {
|
||||
"settings": "Indstillinger"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Udjævn kun formularer"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fix,gendan,korrektion,genvind",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Bruger WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,webindhold,transformation,konvertér",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Konvertér"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "information,data,statistik,statistikker",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avanceret"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Tilføj"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Sideantal",
|
||||
"placeholder": "(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Tabeludtrækning,udtræk,konvertér"
|
||||
@@ -1261,8 +1365,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Årsag",
|
||||
"location": "Placering",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "Auto tilstand - Justerer automatisk kvaliteten for at få PDF'en til en præcis størrelse",
|
||||
"5": "Forventet PDF-størrelse (f.eks. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Komprimer"
|
||||
"submit": "Komprimer",
|
||||
"method": {
|
||||
"filesize": "Filstørrelse"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1664,71 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konvertér",
|
||||
"settings": "Indstillinger",
|
||||
"color": "Farve",
|
||||
"greyscale": "Gråtone",
|
||||
"fillPage": "Udfyld Side",
|
||||
"pdfaDigitalSignatureWarning": "PDF'en indeholder en digital signatur. Dette vil blive fjernet i næste trin.",
|
||||
"grayscale": "Gråtone"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Underskriv"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Laster..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Navn",
|
||||
"delete": "Slet"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Rens PDF",
|
||||
"steps": {
|
||||
"settings": "Indstillinger"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Ændre Tilladelser",
|
||||
"submit": "Ændre Tilladelser",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Forhindre samling af dokument"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Forhindre indholdsudtrækning"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Forhindre udtrækning for tilgængelighed"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Forhindre udfyldning af formular"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Forhindre ændring"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Forhindre anmærkningsændring"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Forhindre udskrivning"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Forhindre udskrivning af forskellige formater"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Ændre Tilladelser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "PDF komprimieren um die Dateigröße zu reduzieren"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "Schreibgeschützte PDF-Formfelder entfernen",
|
||||
"desc": "Entfernen Sie die schreibgeschützte Eigenschaft von Formularfeldern in einem PDF-Dokument."
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "Metadaten ändern",
|
||||
@@ -578,6 +578,43 @@
|
||||
"replace-color": {
|
||||
"title": "Farbe ersetzen und invertieren",
|
||||
"desc": "Ersetzen Sie die Farbe des Texts und Hintergrund der PDF-Datei und invertieren Sie die komplette Farbe der PDF-Datei, um die Dateigröße zu reduzieren"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Umwandeln"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Anhänge hinzufügen",
|
||||
"desc": "Eingebettete Dateien (Anhänge) zu einem PDF hinzufügen oder daraus entfernen"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "Inhaltsverzeichnis bearbeiten",
|
||||
"desc": "Hinzufügen oder Bearbeiten von Lesezeichen und Inhaltsverzeichnissen in PDF-Dokumenten"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Seiten extrahieren"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Entfernen",
|
||||
"desc": "Ungewollte Seiten aus dem PDF entfernen"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Bild entfernen",
|
||||
"desc": "Bild aus PDF entfernen, um die Dateigröße zu verringern"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Teilen nach Größe/Anzahl",
|
||||
"desc": "Teilen Sie ein einzelnes PDF basierend auf Größe, Seitenanzahl oder Dokumentanzahl in mehrere Dokumente auf"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Farben/Kontrast anpassen",
|
||||
"desc": "Kontrast, Sättigung und Helligkeit einer PDF anpassen"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Farbe ersetzen und invertieren",
|
||||
"desc": "Ersetzen Sie die Farbe des Texts und Hintergrund der PDF-Datei und invertieren Sie die komplette Farbe der PDF-Datei, um die Dateigröße zu reduzieren"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Berechtigungen ändern"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "Nach Namen sortieren",
|
||||
"sortByDate": "Nach Datum sortieren",
|
||||
"removeCertSign": "Digitale Signatur in der zusammengeführten Datei entfernen?",
|
||||
"submit": "Zusammenführen"
|
||||
"submit": "Zusammenführen",
|
||||
"sortBy": {
|
||||
"filename": "Dateiname"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "seitenoperationen,teilen,mehrseitig,ausschneiden,serverseitig",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "Dokument #6: Seite 10"
|
||||
},
|
||||
"splitPages": "Geben Sie die Seiten an, an denen aufgeteilt werden soll:",
|
||||
"submit": "Aufteilen"
|
||||
"submit": "Aufteilen",
|
||||
"steps": {
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Dateigröße"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Dateigröße"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "serverseitig",
|
||||
@@ -712,6 +765,9 @@
|
||||
"type": {
|
||||
"1": "Text",
|
||||
"2": "Bild"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Schriftgröße"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +790,9 @@
|
||||
"submit": "Ändern"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "seiten entfernen,seiten löschen"
|
||||
"tags": "seiten entfernen,seiten löschen",
|
||||
"title": "Entfernen",
|
||||
"submit": "Entfernen"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "sicher,sicherheit",
|
||||
@@ -758,7 +816,12 @@
|
||||
"15": "Schränkt ein, was mit dem Dokument gemacht werden kann, sobald es geöffnet ist (wird nicht von allen Leseprogrammen unterstützt)",
|
||||
"16": "Schränkt das Öffnen des Dokuments selbst ein"
|
||||
},
|
||||
"submit": "Verschlüsseln"
|
||||
"submit": "Verschlüsseln",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Berechtigungen ändern"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "sichern,entschlüsseln,sicherheit,passwort aufheben,passwort löschen",
|
||||
@@ -768,15 +831,20 @@
|
||||
"1": "Das zu entschlüsselnde PDF auswählen",
|
||||
"2": "Passwort"
|
||||
},
|
||||
"submit": "Entfernen"
|
||||
"submit": "Entfernen",
|
||||
"desc": "Den Passwortschutz eines PDFs entfernen",
|
||||
"password": {
|
||||
"stepTitle": "Passwort entfernen",
|
||||
"label": "Aktuelles Passwort"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "komprimieren,verkleinern,minimieren"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"tags": "entfernen,löschen,form,feld,schreibgeschützt",
|
||||
"title": "Entfernen Sie schreibgeschützte Formfelder",
|
||||
"header": "Schreibgeschützte PDF-Formfelder entfernen",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -829,7 +897,25 @@
|
||||
},
|
||||
"help": "Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können",
|
||||
"credit": "Dieser Dienst verwendet qpdf und Tesseract für OCR.",
|
||||
"submit": "PDF mit OCR verarbeiten"
|
||||
"submit": "PDF mit OCR verarbeiten",
|
||||
"desc": "Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu",
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
"ocrMode": {
|
||||
"label": "OCR-Modus"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Sprachen"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR-Modus"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Sprachen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "bild,foto,speichern,archivieren,zippen,erfassen,greifen",
|
||||
@@ -938,7 +1024,13 @@
|
||||
"title": "Abflachen",
|
||||
"header": "PDFs reduzieren",
|
||||
"flattenOnlyForms": "Nur Formulare abflachen",
|
||||
"submit": "Abflachen"
|
||||
"submit": "Abflachen",
|
||||
"steps": {
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Nur Formulare abflachen"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "reparieren,wiederherstellen,korrigieren,wiederherstellen",
|
||||
@@ -1174,7 +1266,22 @@
|
||||
"showLayers": "Ebenen anzeigen (Doppelklick, um alle Ebenen auf den Standardzustand zurückzusetzen)",
|
||||
"colourPicker": "Farbauswahl",
|
||||
"findCurrentOutlineItem": "Aktuell gewähltes Element finden",
|
||||
"applyChanges": "Änderungen übernehmen"
|
||||
"applyChanges": "Änderungen übernehmen",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Erweiterte Funktionen"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Signieren"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Seiten",
|
||||
"placeholder": "(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)"
|
||||
},
|
||||
"export": "Downloaden"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,tabelle,extrahieren"
|
||||
@@ -1386,7 +1493,10 @@
|
||||
"4": "Automatischer Modus – Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen",
|
||||
"5": "Erwartete PDF-Größe (z.B. 25 MB, 10,8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Komprimieren"
|
||||
"submit": "Komprimieren",
|
||||
"method": {
|
||||
"filesize": "Dateigröße"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Diese Datei ist passwortgeschützt. Bitte geben Sie das Passwort ein:",
|
||||
@@ -1557,5 +1667,81 @@
|
||||
"description": "Diese Cookies helfen uns zu verstehen, wie unsere Tools genutzt werden, damit wir uns darauf konzentrieren können, die Funktionen zu entwickeln, die unserer Community am meisten am Herzen liegen. Seien Sie beruhigt – Stirling PDF kann und wird niemals den Inhalt der Dokumente verfolgen, mit denen Sie arbeiten."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Herunterladen",
|
||||
"undo": "Rückgängig",
|
||||
"convert": {
|
||||
"title": "Umwandeln",
|
||||
"settings": "Einstellungen",
|
||||
"color": "Farbe",
|
||||
"greyscale": "Graustufen",
|
||||
"fillPage": "Seite füllen",
|
||||
"pdfaDigitalSignatureWarning": "Das PDF enthält eine digitale Signatur. Sie wird im nächsten Schritt entfernt.",
|
||||
"grayscale": "Graustufen"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "einbetten, anhängen, datei, anhang, anhänge",
|
||||
"title": "Anhänge hinzufügen",
|
||||
"header": "Anhänge hinzufügen",
|
||||
"submit": "Anhänge hinzufügen"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Alle auswählen",
|
||||
"deselectAll": "Auswahl aufheben"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Signieren"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Laden...",
|
||||
"or": "oder"
|
||||
},
|
||||
"fileManager": {
|
||||
"selectAll": "Alle auswählen",
|
||||
"deselectAll": "Auswahl aufheben",
|
||||
"deleteSelected": "Auswahl löschen",
|
||||
"download": "Herunterladen",
|
||||
"delete": "Löschen"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF Bereinigen",
|
||||
"steps": {
|
||||
"settings": "Einstellungen"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Berechtigungen ändern",
|
||||
"submit": "Berechtigungen ändern",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Das zusammensetzen des PDFs verhindern"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Inhaltsextrahierung verhindern"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Inhaltsextrahierung zur Barrierefreiheit verhindern"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Ausfüllen des Formulars verhindern"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Modifizierung verhindern"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Ändern von Kommentaren verhindern"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Drucken verhindern"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Drucken verschiedener Formate verhindern"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Berechtigungen ändern"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Φόρτωση...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Αντικατάσταση και αναστροφή χρώματος",
|
||||
"desc": "Αντικατάσταση χρώματος για κείμενο και φόντο σε PDF και αναστροφή πλήρους χρώματος για μείωση μεγέθους αρχείου"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Μετατροπή"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Εξαγωγή σελίδων"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Αφαίρεση",
|
||||
"desc": "Διαγραφή ανεπιθύμητων σελίδων από το έγγραφο PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Αφαίρεση εικόνας",
|
||||
"desc": "Αφαίρεση εικόνας από PDF για μείωση μεγέθους αρχείου"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Αυτόματος διαχωρισμός ανά μέγεθος/πλήθος",
|
||||
"desc": "Διαχωρισμός ενός PDF σε πολλαπλά έγγραφα βάσει μεγέθους, αριθμού σελίδων ή αριθμού εγγράφων"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Προσαρμογή χρωμάτων/αντίθεσης",
|
||||
"desc": "Προσαρμογή αντίθεσης, κορεσμού και φωτεινότητας ενός PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Αντικατάσταση και αναστροφή χρώματος",
|
||||
"desc": "Αντικατάσταση χρώματος για κείμενο και φόντο σε PDF και αναστροφή πλήρους χρώματος για μείωση μεγέθους αρχείου"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Αλλαγή δικαιωμάτων"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Ταξινόμηση κατά όνομα",
|
||||
"sortByDate": "Ταξινόμηση κατά ημερομηνία",
|
||||
"removeCertSign": "Αφαίρεση ψηφιακής υπογραφής στο συγχωνευμένο αρχείο;",
|
||||
"submit": "Συγχώνευση"
|
||||
"submit": "Συγχώνευση",
|
||||
"sortBy": {
|
||||
"filename": "Όνομα αρχείου"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "λειτουργίες σελίδων,διαίρεση,πολλαπλές σελίδες,κοπή,πλευρά διακομιστή",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Έγγραφο #6: Σελίδα 10"
|
||||
},
|
||||
"splitPages": "Εισάγετε σελίδες για διαχωρισμό:",
|
||||
"submit": "Διαχωρισμός"
|
||||
"submit": "Διαχωρισμός",
|
||||
"steps": {
|
||||
"settings": "Ρυθμίσεις"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Μέγεθος αρχείου"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Μέγεθος αρχείου"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "πλευρά διακομιστή",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Κείμενο",
|
||||
"2": "Εικόνα"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Κείμενο"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Μέγεθος γραμματοσειράς"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Αλλαγή"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "αφαίρεση σελίδων,διαγραφή σελίδων"
|
||||
"tags": "αφαίρεση σελίδων,διαγραφή σελίδων",
|
||||
"title": "Αφαίρεση",
|
||||
"submit": "Αφαίρεση"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "ασφάλεια,ασφάλεια",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Περιορίζει τι μπορεί να γίνει με το έγγραφο μετά το άνοιγμά του (Δεν υποστηρίζεται από όλα τα προγράμματα ανάγνωσης)",
|
||||
"16": "Περιορίζει το άνοιγμα του ίδιου του εγγράφου"
|
||||
},
|
||||
"submit": "Κρυπτογράφηση"
|
||||
"submit": "Κρυπτογράφηση",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Αλλαγή δικαιωμάτων"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "ασφάλεια,αποκρυπτογράφηση,ασφάλεια,αφαίρεση κωδικού,διαγραφή κωδικού",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Επιλέξτε PDF για αποκρυπτογράφηση",
|
||||
"2": "Κωδικός"
|
||||
},
|
||||
"submit": "Αφαίρεση"
|
||||
"submit": "Αφαίρεση",
|
||||
"desc": "Αφαίρεση προστασίας κωδικού από το έγγραφο PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Αφαίρεση κωδικού",
|
||||
"label": "Τρέχων κωδικός"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "συμπίεση,μικρό,μικροσκοπικό"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Παρακαλώ διαβάστε αυτή την τεκμηρίωση για το πώς να το χρησιμοποιήσετε για άλλες γλώσσες ή/και χρήση εκτός docker",
|
||||
"credit": "Αυτή η υπηρεσία χρησιμοποιεί qpdf και Tesseract για OCR.",
|
||||
"submit": "Επεξεργασία PDF με OCR"
|
||||
"submit": "Επεξεργασία PDF με OCR",
|
||||
"desc": "Καθαρισμός σαρώσεων και ανίχνευση κειμένου από εικόνες μέσα σε PDF και επαναπροσθήκη ως κείμενο.",
|
||||
"settings": {
|
||||
"title": "Ρυθμίσεις",
|
||||
"ocrMode": {
|
||||
"label": "Λειτουργία OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Γλώσσες"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Λειτουργία OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Γλώσσες"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "εικόνα,φωτογραφία,αποθήκευση,αρχείο,zip,σύλληψη,λήψη",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Ισοπέδωση",
|
||||
"header": "Ισοπέδωση PDF",
|
||||
"flattenOnlyForms": "Ισοπέδωση μόνο φορμών",
|
||||
"submit": "Ισοπέδωση"
|
||||
"submit": "Ισοπέδωση",
|
||||
"steps": {
|
||||
"settings": "Ρυθμίσεις"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Ισοπέδωση μόνο φορμών"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "επιδιόρθωση,αποκατάσταση,διόρθωση,ανάκτηση",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Χρησιμοποιεί το WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,περιεχόμενο-web,μετατροπή,μετατροπή",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Μετατροπή"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "πληροφορίες,δεδομένα,στατιστικά,στατιστική",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Εμφάνιση επιπέδων (διπλό κλικ για επαναφορά όλων των επιπέδων στην προεπιλεγμένη κατάσταση)",
|
||||
"colourPicker": "Επιλογέας χρώματος",
|
||||
"findCurrentOutlineItem": "Εύρεση τρέχοντος στοιχείου περιγράμματος",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Προχωρημένα"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Προσθήκη"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Σελίδες",
|
||||
"placeholder": "(π.χ. 1,2,8 ή 4,7,12-16 ή 2n-1)"
|
||||
},
|
||||
"export": "Εξαγωγή"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,εξαγωγή πίνακα,εξαγωγή,μετατροπή"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Αυτόματη λειτουργία - Αυτόματη προσαρμογή ποιότητας για επίτευξη ακριβούς μεγέθους PDF",
|
||||
"5": "Αναμενόμενο μέγεθος PDF (π.χ. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Συμπίεση"
|
||||
"submit": "Συμπίεση",
|
||||
"method": {
|
||||
"filesize": "Μέγεθος αρχείου"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Αυτό το αρχείο προστατεύεται με κωδικό πρόσβασης. Παρακαλώ εισάγετε τον κωδικό:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Λήψη",
|
||||
"convert": {
|
||||
"title": "Μετατροπή",
|
||||
"settings": "Ρυθμίσεις",
|
||||
"color": "Χρώμα",
|
||||
"greyscale": "Κλίμακα του γκρι",
|
||||
"fillPage": "Γέμισμα σελίδας",
|
||||
"pdfaDigitalSignatureWarning": "Το PDF περιέχει ψηφιακή υπογραφή. Αυτή θα αφαιρεθεί στο επόμενο βήμα.",
|
||||
"grayscale": "Κλίμακα του γκρι"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Επιλογή όλων",
|
||||
"deselectAll": "Αποεπιλογή όλων"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Υπογραφή"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Φόρτωση...",
|
||||
"or": "ή"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Όνομα",
|
||||
"fileVersion": "Έκδοση",
|
||||
"selectAll": "Επιλογή όλων",
|
||||
"deselectAll": "Αποεπιλογή όλων",
|
||||
"deleteSelected": "Διαγραφή επιλεγμένων",
|
||||
"download": "Λήψη",
|
||||
"delete": "Διαγραφή"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Εξυγίανση PDF",
|
||||
"steps": {
|
||||
"settings": "Ρυθμίσεις"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Αλλαγή δικαιωμάτων",
|
||||
"submit": "Αλλαγή δικαιωμάτων",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Αποτροπή συναρμολόγησης εγγράφου"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Αποτροπή εξαγωγής περιεχομένου"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Αποτροπή εξαγωγής για προσβασιμότητα"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Αποτροπή συμπλήρωσης φόρμας"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Αποτροπή τροποποίησης"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Αποτροπή τροποποίησης σχολιασμών"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Αποτροπή εκτύπωσης"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Αποτροπή εκτύπωσης σε διαφορετικές μορφές"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Αλλαγή δικαιωμάτων"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,7 @@
|
||||
"bored": "Bored Waiting?",
|
||||
"alphabet": "Alphabet",
|
||||
"downloadPdf": "Download PDF",
|
||||
|
||||
"text": "Text",
|
||||
"font": "Font",
|
||||
"selectFillter": "-- Select --",
|
||||
@@ -607,6 +608,10 @@
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
}
|
||||
},
|
||||
"landing": {
|
||||
"addFiles": "Add Files",
|
||||
"uploadFromComputer": "Upload from computer"
|
||||
},
|
||||
"viewPdf": {
|
||||
"tags": "view,read,annotate,text,image,highlight,edit",
|
||||
"title": "View/Edit PDF",
|
||||
@@ -738,7 +743,72 @@
|
||||
"submit": "Change"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
"tags": "Remove pages,delete pages",
|
||||
"title": "Remove Pages",
|
||||
"pageNumbers": "Pages to Remove",
|
||||
"pageNumbersPlaceholder": "e.g. 1,3,5-7",
|
||||
"pageNumbersHelp": "Enter page numbers separated by commas, or ranges like 1-5. Example: 1,3,5-7",
|
||||
"filenamePrefix": "pages_removed",
|
||||
"files": {
|
||||
"placeholder": "Select a PDF file in the main view to get started"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Page Selection"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while removing pages."
|
||||
},
|
||||
"results": {
|
||||
"title": "Page Removal Results"
|
||||
},
|
||||
"submit": "Remove Pages"
|
||||
},
|
||||
"pageSelection": {
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Page Selection Guide"
|
||||
},
|
||||
"basic": {
|
||||
"title": "Basic Usage",
|
||||
"text": "Select specific pages from your PDF document using simple syntax.",
|
||||
"bullet1": "Individual pages: 1,3,5",
|
||||
"bullet2": "Page ranges: 3-6 or 10-15",
|
||||
"bullet3": "All pages: all"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced Features",
|
||||
"expandText": "▶ Show advanced options",
|
||||
"collapseText": "▼ Hide advanced options",
|
||||
"mathematical": {
|
||||
"title": "Mathematical Functions",
|
||||
"text": "Use mathematical expressions to select page patterns:",
|
||||
"bullet1": "2n - all even pages (2, 4, 6, 8...)",
|
||||
"bullet2": "2n+1 - all odd pages (1, 3, 5, 7...)",
|
||||
"bullet3": "3n - every 3rd page (3, 6, 9, 12...)",
|
||||
"bullet4": "4n-1 - pages 3, 7, 11, 15..."
|
||||
},
|
||||
"ranges": {
|
||||
"title": "Open-ended Ranges",
|
||||
"text": "Select from a starting point to the end:",
|
||||
"bullet1": "5- selects pages 5 to end of document",
|
||||
"bullet2": "10- selects pages 10 to end"
|
||||
},
|
||||
"combinations": {
|
||||
"title": "Complex Combinations",
|
||||
"text": "Combine different selection methods:",
|
||||
"bullet1": "1,3-5,8,2n - pages 1, 3-5, 8, and all even pages",
|
||||
"bullet2": "10-,2n+1 - pages 10 to end plus all odd pages",
|
||||
"bullet3": "1-5,15-,3n - pages 1-5, 15 to end, and every 3rd page"
|
||||
}
|
||||
},
|
||||
"tips": {
|
||||
"title": "Tips",
|
||||
"text": "Keep these guidelines in mind:",
|
||||
"bullet1": "Page numbers start from 1 (not 0)",
|
||||
"bullet2": "Spaces are automatically removed",
|
||||
"bullet3": "Invalid expressions are ignored"
|
||||
}
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
@@ -747,7 +817,18 @@
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"submit": "Remove"
|
||||
"submit": "Unlock Forms",
|
||||
"description": "This tool will remove read-only restrictions from PDF form fields, making them editable and fillable.",
|
||||
"filenamePrefix": "unlocked_forms",
|
||||
"files": {
|
||||
"placeholder": "Select a PDF file in the main view to get started"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while unlocking PDF forms."
|
||||
},
|
||||
"results": {
|
||||
"title": "Unlocked Forms Results"
|
||||
}
|
||||
},
|
||||
"changeMetadata": {
|
||||
"tags": "Title,author,date,creation,time,publisher,producer,stats",
|
||||
@@ -995,7 +1076,18 @@
|
||||
"title": "Remove Certificate Signature",
|
||||
"header": "Remove the digital certificate from the PDF",
|
||||
"selectPDF": "Select a PDF file:",
|
||||
"submit": "Remove Signature"
|
||||
"submit": "Remove Signature",
|
||||
"description": "This tool will remove digital certificate signatures from your PDF document.",
|
||||
"filenamePrefix": "unsigned",
|
||||
"files": {
|
||||
"placeholder": "Select a PDF file in the main view to get started"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while removing certificate signatures."
|
||||
},
|
||||
"results": {
|
||||
"title": "Certificate Removal Results"
|
||||
}
|
||||
},
|
||||
"pageLayout": {
|
||||
"tags": "merge,composite,single-view,organize",
|
||||
@@ -1021,7 +1113,28 @@
|
||||
"tags": "auto-detect,header-based,organize,relabel",
|
||||
"title": "Auto Rename",
|
||||
"header": "Auto Rename PDF",
|
||||
"submit": "Auto Rename"
|
||||
"submit": "Auto Rename",
|
||||
"files": {
|
||||
"placeholder": "Select a PDF file in the main view to get started"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while auto-renaming the PDF."
|
||||
},
|
||||
"results": {
|
||||
"title": "Auto-Rename Results"
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "How Auto-Rename Works"
|
||||
},
|
||||
"howItWorks": {
|
||||
"title": "Smart Renaming",
|
||||
"text": "Automatically finds the best title from your PDF content and uses it as the filename.",
|
||||
"bullet1": "Looks for text that appears to be a title or heading",
|
||||
"bullet2": "Creates a clean, valid filename from the detected title",
|
||||
"bullet3": "Keeps the original name if no suitable title is found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"tags": "color-correction,tune,modify,enhance"
|
||||
@@ -1323,7 +1436,18 @@
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF To Single Page",
|
||||
"header": "PDF To Single Page",
|
||||
"submit": "Convert To Single Page"
|
||||
"submit": "Convert To Single Page",
|
||||
"description": "This tool will merge all pages of your PDF into one large single page. The width will remain the same as the original pages, but the height will be the sum of all page heights.",
|
||||
"filenamePrefix": "single_page",
|
||||
"files": {
|
||||
"placeholder": "Select a PDF file in the main view to get started"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while converting to single page."
|
||||
},
|
||||
"results": {
|
||||
"title": "Single Page Results"
|
||||
}
|
||||
},
|
||||
"pageExtracter": {
|
||||
"title": "Extract Pages",
|
||||
@@ -1970,6 +2094,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightRail": {
|
||||
"closePdf": "Close PDF",
|
||||
"closeSelected": "Close Selected Files",
|
||||
"selectAll": "Select All",
|
||||
"deselectAll": "Deselect All",
|
||||
"selectByNumber": "Select by Page Numbers",
|
||||
"deleteSelected": "Delete Selected Pages",
|
||||
"toggleTheme": "Toggle Theme",
|
||||
"exportAll": "Export PDF",
|
||||
"downloadSelected": "Download Selected Files",
|
||||
"downloadAll": "Download All"
|
||||
},
|
||||
"removePassword": {
|
||||
"title": "Remove Password",
|
||||
"desc": "Remove password protection from your PDF document.",
|
||||
@@ -1991,5 +2127,20 @@
|
||||
"results": {
|
||||
"title": "Decrypted PDFs"
|
||||
}
|
||||
},
|
||||
"automation": {
|
||||
"suggested": {
|
||||
"securePdfIngestion": "Secure PDF Ingestion",
|
||||
"securePdfIngestionDesc": "Comprehensive PDF processing workflow that sanitizes documents, applies OCR with cleanup, converts to PDF/A format for long-term archival, and optimizes file size.",
|
||||
"emailPreparation": "Email Preparation",
|
||||
"emailPreparationDesc": "Optimizes PDFs for email distribution by compressing files, splitting large documents into 20MB chunks for email compatibility, and removing metadata for privacy.",
|
||||
"secureWorkflow": "Security Workflow",
|
||||
"secureWorkflowDesc": "Secures PDF documents by removing potentially malicious content like JavaScript and embedded files, then adds password protection to prevent unauthorized access. Password is set to 'password' by default.",
|
||||
"processImages": "Process Images",
|
||||
"processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."
|
||||
}
|
||||
},
|
||||
"automate": {
|
||||
"copyToSaved": "Copy to Saved"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Reemplazar e Invertir Color",
|
||||
"desc": "Reemplaza el color del texto y el fondo en el PDF e invierte el color completo del PDF para reducir el tamaño del archivo"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Convertir"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extraer Páginas"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Eliminar",
|
||||
"desc": "Eliminar páginas no deseadas del documento PDF"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Eliminar imagen",
|
||||
"desc": "Eliminar imagen del PDF> para reducir el tamaño de archivo"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Auto dividir por tamaño/conteo",
|
||||
"desc": "Divide un solo PDF en múltiples documentos según su tamaño, número de páginas, o número de documento"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Ajustar Color/Contraste",
|
||||
"desc": "Ajustar Contraste, Saturación y Brillo de un PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Reemplazar e Invertir Color",
|
||||
"desc": "Reemplaza el color del texto y el fondo en el PDF e invierte el color completo del PDF para reducir el tamaño del archivo"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Cambiar permisos"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Ordenar por nombre",
|
||||
"sortByDate": "Ordenar por fecha",
|
||||
"removeCertSign": "¿Eliminar la firma digital en el archivo unido?",
|
||||
"submit": "Unir"
|
||||
"submit": "Unir",
|
||||
"sortBy": {
|
||||
"filename": "Nombre de Archivo"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operaciones de página,dividir,Multi-página,cortar,Backend",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Documento #6: Página 10"
|
||||
},
|
||||
"splitPages": "Introducir las páginas para dividir:",
|
||||
"submit": "Dividir"
|
||||
"submit": "Dividir",
|
||||
"steps": {
|
||||
"settings": "Configuración"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Tamaño de archivo"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Tamaño de archivo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "Backend",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Texto",
|
||||
"2": "Imagen"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Texto"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Tamaño de Letra"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Cambiar"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Borrar páginas,eliminar páginas"
|
||||
"tags": "Borrar páginas,eliminar páginas",
|
||||
"title": "Eliminar",
|
||||
"submit": "Eliminar"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "seguro,seguridad",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Restringir qué se puede hacer con el documento una vez abierto (no soportado por todos los lectores)",
|
||||
"16": "Restringir la apertura del propio documento"
|
||||
},
|
||||
"submit": "Encriptar"
|
||||
"submit": "Encriptar",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Cambiar permisos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "seguro,Desencriptar,seguridad,quitar contraseña,eliminar contraseña",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Seleccionar PDF para desencriptar",
|
||||
"2": "Contraseña"
|
||||
},
|
||||
"submit": "Eliminar"
|
||||
"submit": "Eliminar",
|
||||
"desc": "Eliminar la contraseña del documento PDF",
|
||||
"password": {
|
||||
"stepTitle": "Eliminar contraseña",
|
||||
"label": "Contraseña actual"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "aplastar,pequeño,diminuto"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en Docker",
|
||||
"credit": "Este servicio utiliza qpdf y Tesseract para OCR",
|
||||
"submit": "Procesar PDF con OCR"
|
||||
"submit": "Procesar PDF con OCR",
|
||||
"desc": "Tareas de limpieza y detectar texto en imágenes dentro de un PDF y volver a incrustarlo como texto",
|
||||
"settings": {
|
||||
"title": "Configuración",
|
||||
"ocrMode": {
|
||||
"label": "Modo OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Idiomas"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Modo OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Idiomas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "imagen,fotografía,guardar,archivo,zip,capturar,coger",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Aplanar",
|
||||
"header": "Acoplar archivos PDF",
|
||||
"flattenOnlyForms": "Aplanar sólo formularios",
|
||||
"submit": "Aplanar"
|
||||
"submit": "Aplanar",
|
||||
"steps": {
|
||||
"settings": "Configuración"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Aplanar sólo formularios"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "reparar,restaurar,corregir,recuperar",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Mostrar Capas (doble clic para restablecer las capas a su estado inicial)",
|
||||
"colourPicker": "Selector de color",
|
||||
"findCurrentOutlineItem": "Resaltar el marcador",
|
||||
"applyChanges": "Aplicar cambios"
|
||||
"applyChanges": "Aplicar cambios",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avanzado"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Agregar"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Páginas",
|
||||
"placeholder": "(por ejemplo 1,2,8 o 4,7,12-16 o 2n-1)"
|
||||
},
|
||||
"export": "Exportar"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Extraer tabla,extraer,convertir"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Modo automático: ajusta automáticamente la calidad para que el PDF tenga el tamaño exacto",
|
||||
"5": "Tamaño esperado del PDF (por ejemplo, 25 MB, 10.8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Comprimir"
|
||||
"submit": "Comprimir",
|
||||
"method": {
|
||||
"filesize": "Tamaño de archivo"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Este archivo está protegido con contraseña. Introdúzca la contraseña:",
|
||||
@@ -1557,5 +1665,83 @@
|
||||
"description": "Estas cookies nos ayudan a entender cómo se están utilizando nuestras herramientas, para que podamos centrarnos en desarrollar las funciones que nuestra comunidad valora más. Tenga la seguridad de que Stirling PDF no puede y nunca podrá rastrear el contenido de los documentos con los que trabaja."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Descargar",
|
||||
"undo": "Deshacer",
|
||||
"convert": {
|
||||
"title": "Convertir",
|
||||
"settings": "Configuración",
|
||||
"color": "Color",
|
||||
"greyscale": "Escala de grises",
|
||||
"fillPage": "Ocupar toda la página",
|
||||
"pdfaDigitalSignatureWarning": "El PDF contiene una firma digital. Ésta se eliminará en el siguiente paso.",
|
||||
"grayscale": "Escala de grises"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Seleccionar Todo",
|
||||
"deselectAll": "Deseleccionar Todo"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Firmar"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Cargando...",
|
||||
"or": "o"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nombre",
|
||||
"fileVersion": "Versión",
|
||||
"selectAll": "Seleccionar Todo",
|
||||
"deselectAll": "Deseleccionar Todo",
|
||||
"deleteSelected": "Borrar selecionado(s)",
|
||||
"download": "Descargar",
|
||||
"delete": "Borrar"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Limpiar archivo PDF",
|
||||
"steps": {
|
||||
"settings": "Configuración"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Cambiar permisos",
|
||||
"submit": "Cambiar permisos",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Impedir el ensamblaje del documento"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Impedir la extracción de contenido"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Impedir la extracción para la accesibilidad"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Impedir rellenar formulario"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Impedir modificación"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Impedir modificación de anotaciones"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Impedir imprimir"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Impedir imprimir diferentes formatos"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Cambiar permisos"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@
|
||||
"uploadButton": "Upload Custom",
|
||||
"configureButton": "Configure",
|
||||
"defaultOption": "Custom",
|
||||
"submitButton": "Submit",
|
||||
"submitButton": "Bidali",
|
||||
"help": "Pipeline Help",
|
||||
"scanHelp": "Folder Scanning Help",
|
||||
"deletePrompt": "Are you sure you want to delete pipeline",
|
||||
@@ -147,7 +147,7 @@
|
||||
"selectOperation": "Select Operation",
|
||||
"addOperationButton": "Add operation",
|
||||
"pipelineHeader": "Pipeline:",
|
||||
"saveButton": "Download",
|
||||
"saveButton": "Distira",
|
||||
"validateButton": "Validate"
|
||||
},
|
||||
"enterpriseEdition": {
|
||||
@@ -175,7 +175,7 @@
|
||||
"search": "Search",
|
||||
"sections": {
|
||||
"organize": "Organize",
|
||||
"convertTo": "Convert to PDF",
|
||||
"convertTo": "PDF bihurtu",
|
||||
"convertFrom": "Convert from PDF",
|
||||
"security": "Sign & Security",
|
||||
"advance": "Advanced",
|
||||
@@ -209,10 +209,10 @@
|
||||
"title": "Change Credentials",
|
||||
"header": "Update Your Account Details",
|
||||
"changePassword": "You are using default login credentials. Please enter a new password",
|
||||
"newUsername": "New Username",
|
||||
"newUsername": "Erabiltzaile izen berria",
|
||||
"oldPassword": "Current Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"newPassword": "Pasahitz berria",
|
||||
"confirmNewPassword": "Konfirmatu pasahitz berria",
|
||||
"submit": "Submit Changes"
|
||||
},
|
||||
"account": {
|
||||
@@ -578,6 +578,37 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Bihurtu"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Atera orriak"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Ezabatu",
|
||||
"desc": "Ezabatu nahi ez dituzun orrialdeak PDF dokumentutik"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Remove image",
|
||||
"desc": "Remove image from PDF to reduce file size"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"desc": "Split a single PDF into multiple documents based on size, page count, or document count"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Koloreak/kontrastea doitu",
|
||||
"desc": "PDF baten kontrastea, saturazioa eta distira doitzea"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Aldatu baimenak"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -602,10 +633,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Zatitu",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "ezabatu",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -634,7 +665,10 @@
|
||||
"8": "#6 Dokumentua: 10. orrialdeak"
|
||||
},
|
||||
"splitPages": "Sartu orrialdeak zatitzeko:",
|
||||
"submit": "Zatitu"
|
||||
"submit": "Zatitu",
|
||||
"steps": {
|
||||
"settings": "Ezarpenak"
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server side",
|
||||
@@ -644,10 +678,10 @@
|
||||
"submit": "Biratu"
|
||||
},
|
||||
"imageToPdf": {
|
||||
"tags": "conversion,img,jpg,picture,photo"
|
||||
"tags": "conversion,img,jpg,picture,photo,psd,photoshop"
|
||||
},
|
||||
"pdfToImage": {
|
||||
"tags": "conversion,img,jpg,picture,photo",
|
||||
"tags": "conversion,img,jpg,picture,photo,psd,photoshop",
|
||||
"title": "PDFa irudi bihurtu",
|
||||
"header": "PDFa irudi bihurtu",
|
||||
"selectText": "Irudi-formatua",
|
||||
@@ -712,6 +746,9 @@
|
||||
"type": {
|
||||
"1": "Text",
|
||||
"2": "Image"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Testua"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +771,9 @@
|
||||
"submit": "Aldatu"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
"tags": "Remove pages,delete pages",
|
||||
"title": "Ezabatu",
|
||||
"submit": "Ezabatu"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "secure,security",
|
||||
@@ -758,7 +797,12 @@
|
||||
"15": "Mugatu zer egin daitekeen dokumentuarekin behin zabalduta (Irakurle guztiek onartu gabe)",
|
||||
"16": "Mugatu dokumentu bera zabaltzeko aukera"
|
||||
},
|
||||
"submit": "Enkriptatu"
|
||||
"submit": "Enkriptatu",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Aldatu baimenak"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
@@ -768,7 +812,11 @@
|
||||
"1": "Hautatu PDFa desenkriptatzeko",
|
||||
"2": "Pasahitza"
|
||||
},
|
||||
"submit": "Ezabatu"
|
||||
"submit": "Ezabatu",
|
||||
"desc": "Ezabatu pasahitza PDF dokumentutik",
|
||||
"password": {
|
||||
"stepTitle": "Ezabatu pasahitza"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
@@ -829,7 +877,19 @@
|
||||
},
|
||||
"help": "Irakurri honen erabilerari buruzko dokumentazioa beste hizkuntza batzuetarako eta/edo ez erabili Docker-en",
|
||||
"credit": "Zerbitzu honek qpdf eta OCR-rako Tesseract erabiltzen ditu",
|
||||
"submit": "PDF prozesatu OCR-rekin"
|
||||
"submit": "PDF prozesatu OCR-rekin",
|
||||
"desc": "Garbiketa-eskaneatzeak eta irudi-testuak detektatu PDF baten barruan eta berriz ere gehitu testu gisa",
|
||||
"settings": {
|
||||
"title": "Ezarpenak",
|
||||
"ocrMode": {
|
||||
"label": "OCR modua"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR modua"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "picture,photo,save,archive,zip,capture,grab",
|
||||
@@ -924,7 +984,7 @@
|
||||
"sharedSigs": "Shared Signatures",
|
||||
"noSavedSigs": "No saved signatures found",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "ezabatu",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +998,10 @@
|
||||
"title": "Lautu",
|
||||
"header": "Akoplatu PDF fitxategiak",
|
||||
"flattenOnlyForms": "Flatten only forms",
|
||||
"submit": "Lautu"
|
||||
"submit": "Lautu",
|
||||
"steps": {
|
||||
"settings": "Ezarpenak"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fix,restore,correction,recover",
|
||||
@@ -1106,10 +1169,10 @@
|
||||
"credit": "WeasyPrint darabil"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,web-content,transformation,convert",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Bihurtu"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "infomation,data,stats,statistics",
|
||||
@@ -1148,7 +1211,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1237,12 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"wordsToRedact": {
|
||||
"add": "Gehitu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Table Extraction,extract,convert"
|
||||
@@ -1206,7 +1274,7 @@
|
||||
"foreground": "Foreground",
|
||||
"background": "Background"
|
||||
},
|
||||
"submit": "Submit"
|
||||
"submit": "Bidali"
|
||||
},
|
||||
"split-by-sections": {
|
||||
"tags": "Section Split, Divide, Customize",
|
||||
@@ -1220,7 +1288,7 @@
|
||||
"label": "Vertical Divisions",
|
||||
"placeholder": "Enter number of vertical divisions"
|
||||
},
|
||||
"submit": "Split PDF",
|
||||
"submit": "Zatitu PDFa",
|
||||
"merge": "Merge Into One PDF"
|
||||
},
|
||||
"AddStampRequest": {
|
||||
@@ -1230,16 +1298,16 @@
|
||||
"stampType": "Stamp Type",
|
||||
"stampText": "Stamp Text",
|
||||
"stampImage": "Stamp Image",
|
||||
"alphabet": "Alphabet",
|
||||
"alphabet": "Alfabetoa",
|
||||
"fontSize": "Font/Image Size",
|
||||
"rotation": "Rotation",
|
||||
"opacity": "Opacity",
|
||||
"position": "Position",
|
||||
"position": "Posizioa",
|
||||
"overrideX": "Override X Coordinate",
|
||||
"overrideY": "Override Y Coordinate",
|
||||
"customMargin": "Custom Margin",
|
||||
"customColor": "Custom Text Color",
|
||||
"submit": "Submit"
|
||||
"submit": "Bidali"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"tags": "Remove Image,Page operations,Back end,server side"
|
||||
@@ -1261,8 +1329,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Arrazoia",
|
||||
"location": "Kokalekua",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1442,7 +1510,7 @@
|
||||
"label": "Enter Value",
|
||||
"placeholder": "Enter size (e.g., 2MB or 3KB) or count (e.g., 5)"
|
||||
},
|
||||
"submit": "Submit"
|
||||
"submit": "Bidali"
|
||||
},
|
||||
"printFile": {
|
||||
"title": "Print File",
|
||||
@@ -1501,7 +1569,7 @@
|
||||
"3": "Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.",
|
||||
"4": "Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs."
|
||||
},
|
||||
"submit": "Split PDF"
|
||||
"submit": "Zatitu PDFa"
|
||||
},
|
||||
"fileChooser": {
|
||||
"click": "Click",
|
||||
@@ -1557,5 +1625,68 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Distira",
|
||||
"convert": {
|
||||
"title": "Bihurtu",
|
||||
"settings": "Ezarpenak",
|
||||
"color": "Color",
|
||||
"greyscale": "Gris-eskala",
|
||||
"grayscale": "Gris-eskala"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Sinatu"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Izena",
|
||||
"download": "Distira",
|
||||
"delete": "ezabatu"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF-a desinfektatu",
|
||||
"steps": {
|
||||
"settings": "Ezarpenak"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Aldatu baimenak",
|
||||
"submit": "Aldatu baimenak",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Galarazi dokumentuaren mihiztaketa"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Galarazi edukia ateratzea"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Galarazi ateratzea irisgarritasunerako"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Galarazi inprimakia betetzea"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Galarazi aldaketak egitea"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Galarazi oharrak aldatzea"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Galarazi inprimatzea"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Galarazi zenbait formatu inprimatzea"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Aldatu baimenak"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "در حال بارگذاری...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "جایگزینی و معکوس کردن رنگ",
|
||||
"desc": "جایگزینی رنگ متن و پسزمینه در PDF و معکوس کردن کل رنگها برای کاهش حجم فایل"
|
||||
},
|
||||
"convert": {
|
||||
"title": "تبدیل"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "استخراج صفحات"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "حذف",
|
||||
"desc": "حذف صفحات ناخواسته از سند PDF شما."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "حذف تصویر",
|
||||
"desc": "حذف تصاویر از PDF برای کاهش حجم فایل"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "تقسیم خودکار بر اساس اندازه/تعداد",
|
||||
"desc": "تقسیم یک PDF به چند سند بر اساس اندازه، تعداد صفحات، یا تعداد اسناد"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "تنظیم رنگها/کنتراست",
|
||||
"desc": "تنظیم کنتراست، اشباع و روشنایی یک PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "جایگزینی و معکوس کردن رنگ",
|
||||
"desc": "جایگزینی رنگ متن و پسزمینه در PDF و معکوس کردن کل رنگها برای کاهش حجم فایل"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "تغییر مجوزها"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "مرتبسازی بر اساس نام",
|
||||
"sortByDate": "مرتبسازی بر اساس تاریخ",
|
||||
"removeCertSign": "حذف امضای دیجیتال در فایل ادغامشده؟",
|
||||
"submit": "ادغام"
|
||||
"submit": "ادغام",
|
||||
"sortBy": {
|
||||
"filename": "نام فایل"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "عملیات صفحات،تقسیم،چند صفحه،برش،سمت سرور",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "سند #6: صفحه ۱۰"
|
||||
},
|
||||
"splitPages": "صفحات برای تقسیم را وارد کنید:",
|
||||
"submit": "تقسیم"
|
||||
"submit": "تقسیم",
|
||||
"steps": {
|
||||
"settings": "تنظیمات"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "اندازه فایل"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "اندازه فایل"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "سمت سرور",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "متن",
|
||||
"2": "تصویر"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "متن"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "اندازه فونت"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "تغییر"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "حذف صفحات،پاک کردن صفحات"
|
||||
"tags": "حذف صفحات،پاک کردن صفحات",
|
||||
"title": "حذف",
|
||||
"submit": "حذف"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "امن،امنیت",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "محدودیتهایی که میتوان بر روی سند اعمال کرد هنگامی که باز است (پشتیبانیشده توسط همه خوانندگان نیست)",
|
||||
"16": "محدودیتهای باز شدن خود سند"
|
||||
},
|
||||
"submit": "رمزنگاری"
|
||||
"submit": "رمزنگاری",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "تغییر مجوزها"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "امن،رمزگشایی،امنیت،حذف رمز عبور",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "PDFی را برای رمزگشایی انتخاب کنید",
|
||||
"2": "گذرواژه"
|
||||
},
|
||||
"submit": "حذف"
|
||||
"submit": "حذف",
|
||||
"desc": "حذف حفاظت رمز عبور از سند PDF شما.",
|
||||
"password": {
|
||||
"stepTitle": "حذف رمز عبور",
|
||||
"label": "رمز عبور فعلی"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "فشرده،کوچک،ریز"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "لطفاً این مستندات را بخوانید تا نحوه استفاده از این سرویس برای زبانهای دیگر و/یا استفاده از آن که در داخل داکر نیست را بدانید",
|
||||
"credit": "این سرویس از qpdf و Tesseract برای OCR استفاده میکند.",
|
||||
"submit": "پردازش PDF با OCR"
|
||||
"submit": "پردازش PDF با OCR",
|
||||
"desc": "پاکسازی اسکنها و تشخیص متن از تصاویر درون یک فایل PDF و بازافزودن آن به عنوان متن.",
|
||||
"settings": {
|
||||
"title": "تنظیمات",
|
||||
"ocrMode": {
|
||||
"label": "حالت OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "زبانها"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "حالت OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "زبانها"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "عکس،عکس،ذخیره،آرشیو،زیپ،گرفتن،برداشتن",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "یکپارچهسازی",
|
||||
"header": "یکپارچهسازی PDFها",
|
||||
"flattenOnlyForms": "فقط فرمها را یکپارچه کن",
|
||||
"submit": "یکپارچهسازی"
|
||||
"submit": "یکپارچهسازی",
|
||||
"steps": {
|
||||
"settings": "تنظیمات"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "فقط فرمها را یکپارچه کن"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "ترمیم،بازیابی،اصلاح،بازیابی",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "از WeasyPrint استفاده میکند"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "مارکآپ،محتوای وب،تبدیل،تغییر",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "تبدیل"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "اطلاعات،داده،آمار،آمارها",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "پیشرفته"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "اضافه کردن"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "صفحات",
|
||||
"placeholder": "(مثال: 1,2,8 یا 4,7,12-16 یا 2n-1)"
|
||||
},
|
||||
"export": "صادر کردن"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV، استخراج جدول، استخراج، تبدیل"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "حالت خودکار - کیفیت را به طور خودکار تنظیم میکند تا PDF به اندازه دقیق برسد",
|
||||
"5": "اندازه PDF مورد انتظار (مثلاً ۲۵MB، ۱۰.۸MB، ۲۵KB)"
|
||||
},
|
||||
"submit": "فشردهسازی"
|
||||
"submit": "فشردهسازی",
|
||||
"method": {
|
||||
"filesize": "اندازه فایل"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "دانلود",
|
||||
"convert": {
|
||||
"title": "تبدیل",
|
||||
"settings": "تنظیمات",
|
||||
"color": "رنگ",
|
||||
"greyscale": "خاکستری",
|
||||
"fillPage": "پر کردن صفحه",
|
||||
"pdfaDigitalSignatureWarning": "PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.",
|
||||
"grayscale": "خاکستری"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "انتخاب همه",
|
||||
"deselectAll": "لغو انتخاب همه"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "امضا"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "در حال بارگذاری...",
|
||||
"or": "یا"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "نام",
|
||||
"fileVersion": "نسخه",
|
||||
"selectAll": "انتخاب همه",
|
||||
"deselectAll": "لغو انتخاب همه",
|
||||
"deleteSelected": "حذف انتخاب شدهها",
|
||||
"download": "دانلود",
|
||||
"delete": "حذف"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "پاکسازی PDF",
|
||||
"steps": {
|
||||
"settings": "تنظیمات"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "تغییر مجوزها",
|
||||
"submit": "تغییر مجوزها",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "جلوگیری از مونتاژ سند"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "جلوگیری از استخراج محتوا"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "جلوگیری از استخراج برای دسترسیپذیری"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "جلوگیری از پرکردن فرم"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "جلوگیری از اصلاح"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "جلوگیری از اصلاح حاشیهنویسی"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "جلوگیری از چاپ"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "جلوگیری از چاپ فرمتهای مختلف"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "تغییر مجوزها"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Á lódáil...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Athchuir agus Inbhéartaigh Dath",
|
||||
"desc": "Athchuir dath an téacs agus an chúlra i bhformáid PDF agus inbhéartaigh dath iomlán pdf chun méid comhaid a laghdú"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Tiontaigh"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Leathanaigh Sliocht"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Bain",
|
||||
"desc": "Scrios leathanaigh nach dteastaíonn ó do dhoiciméad PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Bain íomhá",
|
||||
"desc": "Bain íomhá de PDF chun méid comhaid a laghdú"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Auto Scoilte de réir Méid/Comhaireamh",
|
||||
"desc": "Scoilt PDF amháin i ndoiciméid iolracha bunaithe ar mhéid, líon na leathanach, nó comhaireamh doiciméad"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Coigeartaigh Dathanna/Codarsnacht",
|
||||
"desc": "Coigeartaigh Codarsnacht, Sáithiú agus Gile PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Athchuir agus Inbhéartaigh Dath",
|
||||
"desc": "Athchuir dath an téacs agus an chúlra i bhformáid PDF agus inbhéartaigh dath iomlán pdf chun méid comhaid a laghdú"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Athrú Ceadanna"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sórtáil de réir ainm",
|
||||
"sortByDate": "Sórtáil de réir dáta",
|
||||
"removeCertSign": "Bain síniú digiteach sa chomhad cumaiscthe?",
|
||||
"submit": "Cumaisc"
|
||||
"submit": "Cumaisc",
|
||||
"sortBy": {
|
||||
"filename": "Ainm comhaid"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Oibríochtaí leathanach, roinnt, Leathanach Il, gearrtha, taobh freastalaí",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Doiciméad #6: Leathanach 10"
|
||||
},
|
||||
"splitPages": "Iontráil leathanaigh le scoilt ar:",
|
||||
"submit": "Scoilt"
|
||||
"submit": "Scoilt",
|
||||
"steps": {
|
||||
"settings": "Socruithe"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Méid an Chomhaid"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Méid an Chomhaid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "taobh freastalaí",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Téacs",
|
||||
"2": "Íomha"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Téacs"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Méid an Chló"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Athrú"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Bain leathanaigh, scrios leathanaigh"
|
||||
"tags": "Bain leathanaigh, scrios leathanaigh",
|
||||
"title": "Bain",
|
||||
"submit": "Bain"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "slán, slándáil",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Cuireann sé srian lenar féidir a dhéanamh leis an doiciméad nuair a osclaítear é (Ní thacaíonn gach léitheoir leis)",
|
||||
"16": "Cuireann sé srian le hoscailt an doiciméid féin"
|
||||
},
|
||||
"submit": "Criptigh"
|
||||
"submit": "Criptigh",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Athrú Ceadanna"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "slán, Díchriptiú, slándáil, Unpassword, scrios pasfhocal",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Roghnaigh PDF le Díchriptiú",
|
||||
"2": "Pasfhocal"
|
||||
},
|
||||
"submit": "Bain"
|
||||
"submit": "Bain",
|
||||
"desc": "Bain cosaint phasfhocal ó do dhoiciméad PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Bain Pasfhocal",
|
||||
"label": "Pasfhocal reatha"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish, beag, beag bídeach"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Léigh le do thoil an doiciméadú seo ar conas é seo a úsáid do theangacha eile agus/nó úsáid nach bhfuil i ndugairí",
|
||||
"credit": "Úsáideann an tseirbhís seo qpdf agus Tesseract le haghaidh OCR.",
|
||||
"submit": "Próiseáil PDF le OCR"
|
||||
"submit": "Próiseáil PDF le OCR",
|
||||
"desc": "Scanann glantachán agus aimsíonn sé téacs ó íomhánna laistigh de PDF agus cuireann sé isteach arís é mar théacs.",
|
||||
"settings": {
|
||||
"title": "Socruithe",
|
||||
"ocrMode": {
|
||||
"label": "Mód OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Teangacha"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Mód OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Teangacha"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "pictiúr, grianghraf, shábháil, cartlann, zip, gabháil, grab",
|
||||
@@ -935,10 +1019,16 @@
|
||||
},
|
||||
"flatten": {
|
||||
"tags": "statach, díghníomhachtú, neamh-idirghníomhach, sruthlíniú",
|
||||
"title": "Flatten",
|
||||
"title": "Comhcheangail",
|
||||
"header": "PDF cothromú",
|
||||
"flattenOnlyForms": "Flatten foirmeacha amháin",
|
||||
"submit": "Flatten"
|
||||
"submit": "Comhcheangail",
|
||||
"steps": {
|
||||
"settings": "Socruithe"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Flatten foirmeacha amháin"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "deisiú, athchóiriú, ceartú, aisghabháil",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Taispeáin Sraitheanna (cliceáil faoi dhó chun gach sraith a athshocrú go dtí an staid réamhshocraithe)",
|
||||
"colourPicker": "Roghnóir Dathanna",
|
||||
"findCurrentOutlineItem": "Faigh imlíne reatha",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Casta"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Cuir"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Leathanaigh",
|
||||
"placeholder": "(m.sh. 1,2,8 nó 4,7,12-16 nó 2n-1)"
|
||||
},
|
||||
"export": "Easpórtáil"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, Eastóscadh Tábla, sliocht, tiontú"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Mód uathoibríoch - Coigeartaíonn Auto cáilíocht chun PDF a fháil go dtí an méid cruinn",
|
||||
"5": "Méid PDF a bhfuiltear ag súil leis (m.sh. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Comhbhrúigh"
|
||||
"submit": "Comhbhrúigh",
|
||||
"method": {
|
||||
"filesize": "Méid an Chomhaid"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Tá an comhad seo cosanta ag pasfhocal. Cuir isteach an pasfhocal le do thoil:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Íosluchtaigh",
|
||||
"convert": {
|
||||
"title": "Tiontaigh",
|
||||
"settings": "Socruithe",
|
||||
"color": "Dath",
|
||||
"greyscale": "Scála Liath",
|
||||
"fillPage": "Líon Leathanach",
|
||||
"pdfaDigitalSignatureWarning": "Tá síniú digiteach ar an PDF. Bainfear é seo sa chéad chéim eile.",
|
||||
"grayscale": "Scála Liath"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Roghnaigh Uile",
|
||||
"deselectAll": "Díroghnaigh Uile"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Comhartha"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Á lódáil...",
|
||||
"or": "nó"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Ainm",
|
||||
"fileVersion": "Leagan",
|
||||
"selectAll": "Roghnaigh Uile",
|
||||
"deselectAll": "Díroghnaigh Uile",
|
||||
"deleteSelected": "Scrios Roghnaithe",
|
||||
"download": "Íosluchtaigh",
|
||||
"delete": "Scrios"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF sláintíocht",
|
||||
"steps": {
|
||||
"settings": "Socruithe"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Athrú Ceadanna",
|
||||
"submit": "Athrú Ceadanna",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Cosc a chur le chéile doiciméad"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Cosc a chur ar eastóscadh ábhar"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Cosc a chur ar eastóscadh le haghaidh inrochtaineachta"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Cosc ar fhoirm a líonadh"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Cosc a chur ar mhodhnú"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Cosc a chur ar mhodhnú anótála"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Cosc a chur ar phriontáil"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Cosc a chur ar phriontáil bhformáidí éagsúla"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Athrú Ceadanna"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "लोड हो रहा है...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "रंग बदलें और उल्टा करें",
|
||||
"desc": "PDF में टेक्स्ट और पृष्ठभूमि के लिए रंग बदलें और फ़ाइल आकार कम करने के लिए पूर्ण रंग को उल्टा करें"
|
||||
},
|
||||
"convert": {
|
||||
"title": "बदलें"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "पृष्ठ निकालें"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "निकालें",
|
||||
"desc": "अपने PDF दस्तावेज से अवांछित पृष्ठ हटाएं।"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "छवि हटाएं",
|
||||
"desc": "फ़ाइल आकार कम करने के लिए PDF से छवि हटाएं"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "आकार/संख्या के आधार पर स्वतः विभाजित करें",
|
||||
"desc": "एक PDF को आकार, पृष्ठ संख्या, या दस्तावेज़ संख्या के आधार पर कई दस्तावेज़ों में विभाजित करें"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "रंग/कंट्रास्ट समायोजित करें",
|
||||
"desc": "PDF का कंट्रास्ट, संतृप्ति और चमक समायोजित करें"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "रंग बदलें और उल्टा करें",
|
||||
"desc": "PDF में टेक्स्ट और पृष्ठभूमि के लिए रंग बदलें और फ़ाइल आकार कम करने के लिए पूर्ण रंग को उल्टा करें"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "अनुमतियां बदलें"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "नाम से क्रमबद्ध करें",
|
||||
"sortByDate": "तिथि से क्रमबद्ध करें",
|
||||
"removeCertSign": "मर्ज की गई फ़ाइल में डिजिटल हस्ताक्षर हटाएं?",
|
||||
"submit": "मर्ज करें"
|
||||
"submit": "मर्ज करें",
|
||||
"sortBy": {
|
||||
"filename": "फ़ाइल नाम"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "पेज ऑपरेशन्स,डिवाइड,मल्टी पेज,कट,सर्वर साइड",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "दस्तावेज़ #6: पृष्ठ 10"
|
||||
},
|
||||
"splitPages": "विभाजन करने के लिए पृष्ठ दर्ज करें:",
|
||||
"submit": "विभाजित करें"
|
||||
"submit": "विभाजित करें",
|
||||
"steps": {
|
||||
"settings": "सेटिंग्स"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "फ़ाइल आकार"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "फ़ाइल आकार"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "सर्वर साइड",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "टेक्स्ट",
|
||||
"2": "छवि"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "टेक्स्ट"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "फ़ॉन्ट आकार"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "बदलें"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "पृष्ठ निकालें,पृष्ठ हटाएं"
|
||||
"tags": "पृष्ठ निकालें,पृष्ठ हटाएं",
|
||||
"title": "निकालें",
|
||||
"submit": "निकालें"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "सुरक्षित,सुरक्षा",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "एक बार खुलने के बाद दस्तावेज़ के साथ क्या किया जा सकता है को प्रतिबंधित करता है (सभी पाठकों द्वारा समर्थित नहीं)",
|
||||
"16": "दस्तावेज़ को खोलने को प्रतिबंधित करता है"
|
||||
},
|
||||
"submit": "एन्क्रिप्ट करें"
|
||||
"submit": "एन्क्रिप्ट करें",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "अनुमतियां बदलें"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "सुरक्षित,डिक्रिप्ट,सुरक्षा,अनपासवर्ड,पासवर्ड हटाएं",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "डिक्रिप्ट करने के लिए PDF चुनें",
|
||||
"2": "पासवर्ड"
|
||||
},
|
||||
"submit": "हटाएं"
|
||||
"submit": "हटाएं",
|
||||
"desc": "अपने PDF दस्तावेज से पासवर्ड सुरक्षा हटाएं।",
|
||||
"password": {
|
||||
"stepTitle": "पासवर्ड हटाएं",
|
||||
"label": "वर्तमान पासवर्ड"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "कम्प्रेस,छोटा,छोटा"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "कृपया अन्य भाषाओं के लिए उपयोग और/या डॉकर में उपयोग न करने के बारे में यह दस्तावेज़ीकरण पढ़ें",
|
||||
"credit": "यह सेवा OCR के लिए qpdf और Tesseract का उपयोग करती है।",
|
||||
"submit": "OCR के साथ PDF प्रोसेस करें"
|
||||
"submit": "OCR के साथ PDF प्रोसेस करें",
|
||||
"desc": "स्कैन को साफ करें और PDF के अंदर छवियों से टेक्स्ट का पता लगाएं और उसे टेक्स्ट के रूप में फिर से जोड़ें।",
|
||||
"settings": {
|
||||
"title": "सेटिंग्स",
|
||||
"ocrMode": {
|
||||
"label": "OCR मोड"
|
||||
},
|
||||
"languages": {
|
||||
"label": "भाषाएं"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR मोड"
|
||||
},
|
||||
"languages": {
|
||||
"title": "भाषाएं"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "चित्र,फोटो,सहेजें,संग्रह,ज़िप,कैप्चर,ग्रैब",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "समतल करें",
|
||||
"header": "PDF समतल करें",
|
||||
"flattenOnlyForms": "केवल फ़ॉर्म समतल करें",
|
||||
"submit": "समतल करें"
|
||||
"submit": "समतल करें",
|
||||
"steps": {
|
||||
"settings": "सेटिंग्स"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "केवल फ़ॉर्म समतल करें"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "ठीक करें,पुनर्स्थापित करें,सुधार,पुनर्प्राप्त करें",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "WeasyPrint का उपयोग करता है"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "मार्कअप,वेब-सामग्री,रूपांतरण,बदलें",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "बदलें"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "जानकारी,डेटा,आँकड़े,सांख्यिकी",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "लेयर्स दिखाएं (सभी लेयर्स को डिफ़ॉल्ट स्थिति में रीसेट करने के लिए डबल-क्लिक करें)",
|
||||
"colourPicker": "रंग चयनकर्ता",
|
||||
"findCurrentOutlineItem": "वर्तमान आउटलाइन आइटम खोजें",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "उन्नत"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "जोड़ें"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "पृष्ठ",
|
||||
"placeholder": "(जैसे 1,2,8 या 4,7,12-16 या 2n-1)"
|
||||
},
|
||||
"export": "निर्यात करें"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,तालिका निष्कर्षण,निकालें,बदलें"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "स्वतः मोड - PDF को सटीक आकार प्राप्त करने के लिए गुणवत्ता को स्वतः समायोजित करता है",
|
||||
"5": "अपेक्षित PDF आकार (जैसे 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "कम्प्रेस करें"
|
||||
"submit": "कम्प्रेस करें",
|
||||
"method": {
|
||||
"filesize": "फ़ाइल आकार"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "यह फ़ाइल पासवर्ड से सुरक्षित है। कृपया पासवर्ड दर्ज करें:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "डाउनलोड करें",
|
||||
"convert": {
|
||||
"title": "बदलें",
|
||||
"settings": "सेटिंग्स",
|
||||
"color": "रंग",
|
||||
"greyscale": "ग्रेस्केल",
|
||||
"fillPage": "पृष्ठ भरें",
|
||||
"pdfaDigitalSignatureWarning": "PDF में एक डिजिटल हस्ताक्षर है। यह अगले चरण में हटा दिया जाएगा।",
|
||||
"grayscale": "ग्रेस्केल"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "सभी चुनें",
|
||||
"deselectAll": "सभी अचयनित करें"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "हस्ताक्षर करें"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "लोड हो रहा है...",
|
||||
"or": "या"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "नाम",
|
||||
"fileVersion": "संस्करण",
|
||||
"selectAll": "सभी चुनें",
|
||||
"deselectAll": "सभी अचयनित करें",
|
||||
"deleteSelected": "चयनित हटाएं",
|
||||
"download": "डाउनलोड करें",
|
||||
"delete": "हटाएं"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF सैनिटाइज़ करें",
|
||||
"steps": {
|
||||
"settings": "सेटिंग्स"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "अनुमतियां बदलें",
|
||||
"submit": "अनुमतियां बदलें",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "दस्तावेज़ असेंबली को रोकें"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "सामग्री निष्कर्षण को रोकें"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "पहुंच योग्यता के लिए निष्कर्षण को रोकें"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "फॉर्म भरने को रोकें"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "संशोधन को रोकें"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "टिप्पणी संशोधन को रोकें"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "प्रिंटिंग को रोकें"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "विभिन्न प्रारूपों में प्रिंटिंग को रोकें"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "अनुमतियां बदलें"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Učitavanje...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -476,7 +476,7 @@
|
||||
"desc": "Promijenite veličinu/razmjer stranice i/ili njezin sadržaj."
|
||||
},
|
||||
"pipeline": {
|
||||
"title": "Pipeline",
|
||||
"title": "Tok rada",
|
||||
"desc": "Izvršite više radnji na PDF-ovima definiranjem skripti u pipeline-u"
|
||||
},
|
||||
"addPageNumbers": {
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Zamenite boju teksta i pozadine u PDF-u te inverzirajte cijeli PDF kako bi se smanjila veličina datoteke."
|
||||
},
|
||||
"convert": {
|
||||
"title": "Pretvori"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Izdvojiti stranice"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Ukloniti",
|
||||
"desc": "Izbrišite neželjene stranice iz svog PDF dokumenta."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Ukloni sliku",
|
||||
"desc": "Ukloni sliku iz PDF-a kako bi se smanjio veličina datoteke"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Automatska podjela po veličini/broju",
|
||||
"desc": "Podijelite jedan PDF na više dokumenata na temelju veličine, broja stranica ili broja dokumenata"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Podesi boje/kontrast",
|
||||
"desc": "Podesite kontrast, zasićenost i svjetlinu PDF-a"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Zamenite boju teksta i pozadine u PDF-u te inverzirajte cijeli PDF kako bi se smanjila veličina datoteke."
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Promjena dopuštenja"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -594,7 +626,7 @@
|
||||
"deselectAll": "Deselect All",
|
||||
"selectPages": "Page Select",
|
||||
"selectedPages": "Selected Pages",
|
||||
"page": "Page",
|
||||
"page": "Stranica",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"downloadAll": "Export",
|
||||
"downloadSelected": "Export Selected",
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Razdvajanje",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Izbriši",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Poredaj po imenu",
|
||||
"sortByDate": "Poredaj po datumu",
|
||||
"removeCertSign": "Ukloniti digitalni potpis u kombiniranom datoteku?",
|
||||
"submit": "Spajanje"
|
||||
"submit": "Spajanje",
|
||||
"sortBy": {
|
||||
"filename": "Ime datoteke"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operacije stranice, dijeljenje, više stranica, rezanje,poslužiteljska strana",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: Stranice 10"
|
||||
},
|
||||
"splitPages": "Unesite stranice za razdvajanje:",
|
||||
"submit": "Razdvoji"
|
||||
"submit": "Razdvoji",
|
||||
"steps": {
|
||||
"settings": "Postavke"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Veličina datoteke"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Veličina datoteke"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "poslužiteljska strana",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Tekst",
|
||||
"2": "Slika"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Tekst"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Veličina pisma"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Promijeniti"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Ukloni stranice,izbriši stranice"
|
||||
"tags": "Ukloni stranice,izbriši stranice",
|
||||
"title": "Ukloniti",
|
||||
"submit": "Ukloniti"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "sigurno, sigurnost",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Ograničava što se može učiniti s dokumentom nakon što se otvori (ne podržavaju svi čitači)",
|
||||
"16": "Ograničava otvaranje samog dokumenta"
|
||||
},
|
||||
"submit": "Šifriraj"
|
||||
"submit": "Šifriraj",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Promjena dopuštenja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "sigurno, dešifriranje, sigurnost, poništi lozinku, izbriši lozinku",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Odaberite PDF za dekriptiranje",
|
||||
"2": "Zaporka"
|
||||
},
|
||||
"submit": "Ukloniti"
|
||||
"submit": "Ukloniti",
|
||||
"desc": "Uklonite zaštitu lozinkom sa svog PDF dokumenta..",
|
||||
"password": {
|
||||
"stepTitle": "Ukloni lozinku",
|
||||
"label": "Trenutna zaporka"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish, mali, maleni"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Pročitajte ovu dokumentaciju o tome kako ovo koristiti za druge jezike i/ili koristiti ne u dockeru",
|
||||
"credit": "Ova usluga koristi qpdf i Tesseract za OCR.",
|
||||
"submit": "Obradi PDF sa OCR-om"
|
||||
"submit": "Obradi PDF sa OCR-om",
|
||||
"desc": "Čišćenje skenira i otkriva tekst sa slika unutar PDF-a i ponovno ga dodaje kao tekst.",
|
||||
"settings": {
|
||||
"title": "Postavke",
|
||||
"ocrMode": {
|
||||
"label": "OCR način"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Jezici"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR način"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Jezici"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "slika, fotografija, spremanje, arhiva, zip, snimanje, zgrabi",
|
||||
@@ -924,7 +1008,7 @@
|
||||
"sharedSigs": "Dijeljeni potpisi",
|
||||
"noSavedSigs": "Nema sacuvanih potpisa pronađenih",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Izbriši",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Izravnati",
|
||||
"header": "Izravnati pdf",
|
||||
"flattenOnlyForms": "Izravnati samo obrasce",
|
||||
"submit": "Izravnati"
|
||||
"submit": "Izravnati",
|
||||
"steps": {
|
||||
"settings": "Postavke"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Izravnati samo obrasce"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "popravi,vrati,korekcija,obnovi",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Koristi WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "oznake,web-sadržaj,transformacija,konvertiranje",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Pretvori"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informacije,podaci,statistike",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Napredno"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Dodaj"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Stranice",
|
||||
"placeholder": "(t.j. 1,2,8 ili 4,7,12-16 ili 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Izdvajanje tabela,izdvajanje,pretvaranje"
|
||||
@@ -1261,8 +1365,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Razlog",
|
||||
"location": "Mjesto",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1385,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Verzija",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "Automatski način - Automatski prilagođava kvalitetu kako bi PDF dobio točnu veličinu",
|
||||
"5": "Očekivana veličina PDF-a (npr. 25 MB, 10,8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Kompresiraj"
|
||||
"submit": "Kompresiraj",
|
||||
"method": {
|
||||
"filesize": "Veličina datoteke"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1664,74 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Preuzmi datoteku",
|
||||
"convert": {
|
||||
"title": "Pretvori",
|
||||
"settings": "Postavke",
|
||||
"color": "Boja",
|
||||
"greyscale": "Sivi tonovi",
|
||||
"fillPage": "Ispuni stranicu",
|
||||
"pdfaDigitalSignatureWarning": "PDF sadrži digitalni potpis. U sledećem koraku će biti uklonjen.",
|
||||
"grayscale": "Sivi tonovi"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Potpisati"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Učitavanje..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Ime",
|
||||
"fileVersion": "Verzija",
|
||||
"download": "Preuzmi datoteku",
|
||||
"delete": "Izbriši"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Sanirajte PDF",
|
||||
"steps": {
|
||||
"settings": "Postavke"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Promjena dopuštenja",
|
||||
"submit": "Promjena dopuštenja",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Spriječiti sastavljanje dokumenta"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Spriječiti izdvajanje sadržaja"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Spriječite izvlačenje radi pristupačnosti"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Spriječiti ispunjavanje obrasca"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Spriječiti izmjene"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Spriječi modificiranje napomena"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Spriječiti ispis"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Spriječite ispis različitih formata"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Promjena dopuštenja"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
"multiPdfDropPrompt": "Válassza ki (vagy húzza ide) az összes szükséges PDF-fájlt",
|
||||
"imgPrompt": "Kép kiválasztása",
|
||||
"genericSubmit": "Küldés",
|
||||
"uploadLimit": "Maximum file size:",
|
||||
"uploadLimit": "Maximális fájlméret:",
|
||||
"uploadLimitExceededSingular": "túl nagy. A maximálisan megengedett méret",
|
||||
"uploadLimitExceededPlural": "túl nagyok. A maximálisan megengedett méretek",
|
||||
"processTimeWarning": "Figyelmeztetés: A folyamat akár egy percig is eltarthat a fájlmérettől függően",
|
||||
@@ -166,7 +166,7 @@
|
||||
},
|
||||
"navbar": {
|
||||
"favorite": "Kedvencek",
|
||||
"recent": "New and recently updated",
|
||||
"recent": "Új és nemrég frissített",
|
||||
"darkmode": "Sötét mód",
|
||||
"language": "Nyelvek",
|
||||
"settings": "Beállítások",
|
||||
@@ -578,6 +578,43 @@
|
||||
"replace-color": {
|
||||
"title": "Színek cseréje és invertálása",
|
||||
"desc": "PDF szöveg és háttérszíneinek cseréje és teljes színinvertálás a fájlméret csökkentése érdekében"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konvertálás"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Csatolmányok hozzáadása a PDF-hez",
|
||||
"desc": "Csatolmányok (beágyazott fájlok) hozzáadása vagy eltávolítása a PDF-ből"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "Szerkesztés vagy hozzáadás a PDF tartalomjegyzékéhez",
|
||||
"desc": "PDF dokumentumokban könyvjelzők és tartalomjegyzék hozzáadása vagy szerkesztése"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Oldalak kinyerése"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Eltávolítás",
|
||||
"desc": "Felesleges oldalak törlése a PDF dokumentumból."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Képek eltávolítása",
|
||||
"desc": "Képek eltávolítása PDF-ből a fájlméret csökkentése érdekében"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Automatikus felosztás méret/darabszám szerint",
|
||||
"desc": "Egyetlen PDF felosztása több dokumentumra méret, oldalszám vagy dokumentumszám alapján"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Színek/kontraszt beállítása",
|
||||
"desc": "PDF kontraszt, telítettség és fényerő beállítása"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Színek cseréje és invertálása",
|
||||
"desc": "PDF szöveg és háttérszíneinek cseréje és teljes színinvertálás a fájlméret csökkentése érdekében"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Jogosultságok módosítása"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "Rendezés név szerint",
|
||||
"sortByDate": "Rendezés dátum szerint",
|
||||
"removeCertSign": "Digitális aláírás eltávolítása az egyesített fájlban?",
|
||||
"submit": "Egyesítés"
|
||||
"submit": "Egyesítés",
|
||||
"sortBy": {
|
||||
"filename": "Fájlnév"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Oldalműveletek,felosztás,Több oldal,vágás,szerver oldali",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "6. dokumentum: 10. oldal"
|
||||
},
|
||||
"splitPages": "Adja meg a felosztási pontokat:",
|
||||
"submit": "Felosztás"
|
||||
"submit": "Felosztás",
|
||||
"steps": {
|
||||
"settings": "Beállítások"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Fájlméret"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Fájlméret"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "szerver oldali",
|
||||
@@ -712,6 +765,12 @@
|
||||
"type": {
|
||||
"1": "Szöveg",
|
||||
"2": "Kép"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Szöveg"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Betűméret"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +793,9 @@
|
||||
"submit": "Módosítás"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Oldalak eltávolítása,oldalak törlése"
|
||||
"tags": "Oldalak eltávolítása,oldalak törlése",
|
||||
"title": "Eltávolítás",
|
||||
"submit": "Eltávolítás"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "biztonság,védelem",
|
||||
@@ -758,7 +819,12 @@
|
||||
"15": "Korlátozza, hogy mi végezhető el a dokumentum megnyitása után (Nem minden olvasó támogatja)",
|
||||
"16": "Korlátozza a dokumentum megnyithatságát"
|
||||
},
|
||||
"submit": "Titkosítás"
|
||||
"submit": "Titkosítás",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Jogosultságok módosítása"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "biztonság,feloldás,védelem,jelszó törlése",
|
||||
@@ -768,7 +834,12 @@
|
||||
"1": "Válassza ki a visszafejtendő PDF-et",
|
||||
"2": "Jelszó"
|
||||
},
|
||||
"submit": "Eltávolítás"
|
||||
"submit": "Eltávolítás",
|
||||
"desc": "Jelszavas védelem eltávolítása a PDF dokumentumból",
|
||||
"password": {
|
||||
"stepTitle": "Jelszó eltávolítása",
|
||||
"label": "Jelenlegi jelszó"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "tömörítés,kicsi,kompakt"
|
||||
@@ -829,7 +900,25 @@
|
||||
},
|
||||
"help": "Kérjük, olvassa el ezt a dokumentációt más nyelvek használatáról és/vagy nem Docker környezetben való használatról",
|
||||
"credit": "Ez a szolgáltatás a qpdf és Tesseract OCR használatával működik.",
|
||||
"submit": "PDF feldolgozása OCR-rel"
|
||||
"submit": "PDF feldolgozása OCR-rel",
|
||||
"desc": "Szkennelt dokumentumok tisztítása és szövegfelismerés képekből, majd visszaadása szerkeszthető szövegként",
|
||||
"settings": {
|
||||
"title": "Beállítások",
|
||||
"ocrMode": {
|
||||
"label": "OCR mód"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Nyelvek"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR mód"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Nyelvek"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "kép,fotó,mentés,archívum,tömörítés,kinyerés,gyűjtés",
|
||||
@@ -930,15 +1019,21 @@
|
||||
"next": "Következő oldal",
|
||||
"previous": "Előző oldal",
|
||||
"maintainRatio": "Képarány fenntartása váltása",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
"undo": "Visszavonás",
|
||||
"redo": "Újra"
|
||||
},
|
||||
"flatten": {
|
||||
"tags": "statikus,deaktiválás,nem interaktív,egyszerűsítés",
|
||||
"title": "Lapítás",
|
||||
"header": "PDF-ek lapítása",
|
||||
"flattenOnlyForms": "Csak űrlapok lapítása",
|
||||
"submit": "Lapítás"
|
||||
"submit": "Lapítás",
|
||||
"steps": {
|
||||
"settings": "Beállítások"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Csak űrlapok lapítása"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "javítás,helyreállítás,korrekció,visszaállítás",
|
||||
@@ -1174,7 +1269,22 @@
|
||||
"showLayers": "Rétegek megjelenítése (dupla kattintás az összes réteg alaphelyzetbe állításához)",
|
||||
"colourPicker": "Színválasztó",
|
||||
"findCurrentOutlineItem": "Keresés a jelenlegi vázlatban",
|
||||
"applyChanges": "Változtatások mentése"
|
||||
"applyChanges": "Változtatások mentése",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Haladó"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Hozzáadás"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Oldal",
|
||||
"placeholder": "(pl. 1,2,8 vagy 4,7,12-16 vagy 2n-1)"
|
||||
},
|
||||
"export": "Exportálás"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Táblázat kinyerése,kinyerés,konvertálás"
|
||||
@@ -1386,7 +1496,10 @@
|
||||
"4": "Automatikus mód - Automatikusan állítja a minőséget a megadott PDF méret eléréséhez",
|
||||
"5": "Kívánt PDF méret (pl. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Tömörítés"
|
||||
"submit": "Tömörítés",
|
||||
"method": {
|
||||
"filesize": "Fájlméret"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Ez a fájl jelszóval védett. Kérjük, adja meg a jelszót:",
|
||||
@@ -1557,5 +1670,83 @@
|
||||
"description": "Ezek a sütik segítenek megérteni, hogyan használják eszközeinket, így a közösségünk által leginkább értékelt funkciókra összpontosíthatunk. Nyugodt lehet-a Stirling PDF nem képes és soha nem is fog nyomon követni az Ön által használt dokumentumok tartalmát."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Letöltés",
|
||||
"undo": "Visszavonás",
|
||||
"convert": {
|
||||
"title": "Konvertálás",
|
||||
"settings": "Beállítások",
|
||||
"color": "Szín",
|
||||
"greyscale": "Szürkeárnyalatos",
|
||||
"fillPage": "Oldal kitöltése",
|
||||
"pdfaDigitalSignatureWarning": "A PDF digitális aláírást tartalmaz. Ez a következő lépésben eltávolításra kerül.",
|
||||
"grayscale": "Szürkeárnyalatos"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "beágyazás,csatolás,fájl,csatolmány,csatolmányok",
|
||||
"title": "Mellékletek hozzáadása",
|
||||
"header": "Mellékletek hozzáadása",
|
||||
"submit": "Csatolmányok hozzáadása a PDF-hez"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Összes kijelölése",
|
||||
"deselectAll": "Kijelölés megszüntetése"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Aláírás"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Betöltés...",
|
||||
"or": "vagy"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Név",
|
||||
"fileVersion": "Verzió",
|
||||
"selectAll": "Összes kijelölése",
|
||||
"deselectAll": "Kijelölés megszüntetése",
|
||||
"deleteSelected": "Kijelöltek törlése",
|
||||
"download": "Letöltés",
|
||||
"delete": "Törlés"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF tisztítása",
|
||||
"steps": {
|
||||
"settings": "Beállítások"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Jogosultságok módosítása",
|
||||
"submit": "Jogosultságok módosítása",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Dokumentum egyesítésének megakadályozása"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Tartalom kinyerésének megakadályozása"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Akadálymentesítési célú kinyerés megakadályozása"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Űrlapkitöltés megakadályozása"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Módosítás megakadályozása"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Megjegyzések módosításának megakadályozása"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Nyomtatás megakadályozása"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Különböző formátumú nyomtatás megakadályozása"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Jogosultságok módosítása"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Mengambil data...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Ganti dan Inversi Warna",
|
||||
"desc": "Ganti warna untuk teks dan latar belakang dalam PDF dan inversi seluruh warna PDF untuk mengurangi ukuran file"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konversi"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Ekstrak Halaman"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Menghapus",
|
||||
"desc": "Menghapus halaman yang tidak diinginkan dari dokumen PDF Anda."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Hapus Gambar",
|
||||
"desc": "Hapus gambar dari PDF untuk mengurangi ukuran file"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Pemisahan Otomatis berdasarkan Ukuran/Hitungan",
|
||||
"desc": "Membagi satu PDF menjadi beberapa dokumen berdasarkan ukuran, jumlah halaman, atau jumlah dokumen"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Menyesuaikan Warna/Kontras",
|
||||
"desc": "Sesuaikan Kontras, Saturasi, dan Kecerahan PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Ganti dan Inversi Warna",
|
||||
"desc": "Ganti warna untuk teks dan latar belakang dalam PDF dan inversi seluruh warna PDF untuk mengurangi ukuran file"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Ganti Perizinan"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -594,7 +626,7 @@
|
||||
"deselectAll": "Deselect All",
|
||||
"selectPages": "Page Select",
|
||||
"selectedPages": "Selected Pages",
|
||||
"page": "Page",
|
||||
"page": "Halaman",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"downloadAll": "Export",
|
||||
"downloadSelected": "Export Selected",
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Membagi",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Hapus",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sortir berdasarkan nama",
|
||||
"sortByDate": "Sortir berdasrkan tanggal",
|
||||
"removeCertSign": "Hapus tanda tangan digital dalam file yang dicampur?",
|
||||
"submit": "Gabungkan"
|
||||
"submit": "Gabungkan",
|
||||
"sortBy": {
|
||||
"filename": "Nama Berkas"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Pengoperasian halaman,membagi,Multi Halaman,memotong,sisi server",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokumen #6: Halaman 10"
|
||||
},
|
||||
"splitPages": "Masukkan halaman yang akan dipisah:",
|
||||
"submit": "Pisahkan"
|
||||
"submit": "Pisahkan",
|
||||
"steps": {
|
||||
"settings": "Pengaturan"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Ukuran Berkas"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Ukuran Berkas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "sisi server",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Teks",
|
||||
"2": "Gambar"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Teks"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Ukuran Fonta"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Ganti"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Menghapus halaman,menghapus halaman"
|
||||
"tags": "Menghapus halaman,menghapus halaman",
|
||||
"title": "Menghapus",
|
||||
"submit": "Menghapus"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "aman,Keamanan",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Membatasi apa yang dapat dilakukan dengan dokumen setelah dibuka (Tidak didukung oleh semua pembaca)",
|
||||
"16": "Membatasi pembukaan dokumen itu sendiri"
|
||||
},
|
||||
"submit": "Enkripsi"
|
||||
"submit": "Enkripsi",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Ganti Perizinan"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "aman,Dekripsi,keamanan,buka kata sandi,hapus kata sandi",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Pilih PDF yang akan di Dekrip",
|
||||
"2": "Kata Sandi"
|
||||
},
|
||||
"submit": "Hapus"
|
||||
"submit": "Hapus",
|
||||
"desc": "Menghapus perlindungan kata sandi dari dokumen PDF Anda.",
|
||||
"password": {
|
||||
"stepTitle": "Hapus Kata Sandi",
|
||||
"label": "Kata Sandi Saat Ini"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "remas, kecil, mini"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Silakan baca dokumentasi ini tentang cara menggunakan ini untuk bahasa lain dan/atau penggunaan yang tidak ada di docker",
|
||||
"credit": "Layanan ini menggunakan qpdf dan Tesseract untuk OCR.",
|
||||
"submit": "Memproses PDF dengan OCR"
|
||||
"submit": "Memproses PDF dengan OCR",
|
||||
"desc": "Memindai dan mendeteksi teks dari gambar di dalam PDF dan menambahkannya kembali sebagai teks.",
|
||||
"settings": {
|
||||
"title": "Pengaturan",
|
||||
"ocrMode": {
|
||||
"label": "Mode OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Bahasa"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Mode OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Bahasa"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "gambar, foto, simpan, arsip, zip, tangkap, ambil",
|
||||
@@ -924,7 +1008,7 @@
|
||||
"sharedSigs": "Tanda Tangan Berbagi",
|
||||
"noSavedSigs": "Tidak ditemukan tanda tangan yang disimpan",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Hapus",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Ratakan",
|
||||
"header": "Ratakan PDF",
|
||||
"flattenOnlyForms": "Ratakan hanya formulir",
|
||||
"submit": "Ratakan"
|
||||
"submit": "Ratakan",
|
||||
"steps": {
|
||||
"settings": "Pengaturan"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Ratakan hanya formulir"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "perbaiki, pulihkan, koreksi, pulihkan",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Menggunakan WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup, konten web, transformasi, konversi",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Konversi"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informasi, data, statistik, statistik",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Langkah Lanjut"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Tambah"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Halaman-halaman",
|
||||
"placeholder": "(misalnya 1,2,8 atau 4,7,12-16 atau 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, Ekstraksi Tabel, ekstrak, konversi"
|
||||
@@ -1261,8 +1365,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Alasan",
|
||||
"location": "Lokasi",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1385,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Versi",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "Mode Otomatis - Menyesuaikan kualitas secara otomatis untuk mendapatkan PDF dengan ukuran yang tepat",
|
||||
"5": "Ukuran PDF yang diharapkan (mis. 25MB, 10,8MB, 25KB)"
|
||||
},
|
||||
"submit": "Kompres"
|
||||
"submit": "Kompres",
|
||||
"method": {
|
||||
"filesize": "Ukuran Berkas"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1664,74 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Unduh",
|
||||
"convert": {
|
||||
"title": "Konversi",
|
||||
"settings": "Pengaturan",
|
||||
"color": "Warna",
|
||||
"greyscale": "Skala abu-abu",
|
||||
"fillPage": "Isi Halaman",
|
||||
"pdfaDigitalSignatureWarning": "PDF ini mengandung tanda tangan digital. Ini akan dihapus pada langkah berikutnya.",
|
||||
"grayscale": "Skala abu-abu"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Tanda Tangan"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Mengambil data..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nama",
|
||||
"fileVersion": "Versi",
|
||||
"download": "Unduh",
|
||||
"delete": "Hapus"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Bersihkan PDF",
|
||||
"steps": {
|
||||
"settings": "Pengaturan"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Ganti Perizinan",
|
||||
"submit": "Ganti Perizinan",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Pencegahan untuk penyusunan dokumen"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Pencegahan untuk ekstraksi konten"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Pencegahan ekstraksi untuk aksesibilitas"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Pencegahan untuk mengisi formulir"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Pencegahan untuk pengubahan"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Pencegahan untuk perubahan anotasi"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Pencegahan untuk mencetak"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Pencegahan untuk mencetak format yang berbeda"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Ganti Perizinan"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,6 +578,43 @@
|
||||
"replace-color": {
|
||||
"title": "Sostituisci e inverti il colore",
|
||||
"desc": "Sostituisci il colore del testo e dello sfondo nel PDF e inverti il colore completo del PDF per ridurre le dimensioni del file"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Converti"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Aggiungi allegati",
|
||||
"desc": "Aggiungere o rimuovere file incorporati (allegati) da/a un PDF"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "Modifica indice",
|
||||
"desc": "Aggiungi o modifica segnalibri e sommario nei documenti PDF"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Estrai pagine"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Rimuovi",
|
||||
"desc": "Elimina alcune pagine dal PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Rimuovi immagine",
|
||||
"desc": "Rimuovi le immagini dal PDF per ridurre la dimensione del file"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Divisione automatica per dimensione/numero",
|
||||
"desc": "Dividi un singolo PDF in più documenti in base alle dimensioni, al numero di pagine o al numero di documenti"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Regola colori/contrasto",
|
||||
"desc": "Regola contrasto, saturazione e luminosità di un PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Sostituisci e inverti il colore",
|
||||
"desc": "Sostituisci il colore del testo e dello sfondo nel PDF e inverti il colore completo del PDF per ridurre le dimensioni del file"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Cambia Permessi"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "Ordina per nome",
|
||||
"sortByDate": "Ordina per data",
|
||||
"removeCertSign": "Rimuovere la firma digitale nel file unito?",
|
||||
"submit": "Unisci"
|
||||
"submit": "Unisci",
|
||||
"sortBy": {
|
||||
"filename": "Nome file"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operazioni sulla pagina,divisione,multi pagina,taglio,lato server",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "Documento #6: Pagine 10"
|
||||
},
|
||||
"splitPages": "Inserisci pagine a cui dividere:",
|
||||
"submit": "Dividi"
|
||||
"submit": "Dividi",
|
||||
"steps": {
|
||||
"settings": "Impostazioni"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Dimensione"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Dimensione"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "lato server",
|
||||
@@ -712,6 +765,12 @@
|
||||
"type": {
|
||||
"1": "Testo",
|
||||
"2": "Immagine"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Testo"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Dimensione del font"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +793,9 @@
|
||||
"submit": "Cambia Permessi"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Rimuovere pagine,eliminare pagine"
|
||||
"tags": "Rimuovere pagine,eliminare pagine",
|
||||
"title": "Rimuovi",
|
||||
"submit": "Rimuovi"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "sicuro,sicurezza",
|
||||
@@ -758,7 +819,12 @@
|
||||
"15": "Limita le operazioni eseguibili con il documento una volta aperto (non supportato da tutti i lettori)",
|
||||
"16": "Limita l'apertura del documento stesso"
|
||||
},
|
||||
"submit": "Crittografa"
|
||||
"submit": "Crittografa",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Cambia Permessi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "Decriptare,proteggere,rimuovere la password,eliminare la password",
|
||||
@@ -768,7 +834,12 @@
|
||||
"1": "Seleziona PDF da decrittare",
|
||||
"2": "Password"
|
||||
},
|
||||
"submit": "Rimuovi Password"
|
||||
"submit": "Rimuovi Password",
|
||||
"desc": "Rimuovi la password dal tuo PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Rimuovi Password",
|
||||
"label": "Password attuale"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "comprimere,piccolo,minuscolo"
|
||||
@@ -829,7 +900,25 @@
|
||||
},
|
||||
"help": "Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker",
|
||||
"credit": "Questo servizio utilizza Qpdf e Tesseract per l'OCR.",
|
||||
"submit": "Scansiona testo nel PDF con OCR"
|
||||
"submit": "Scansiona testo nel PDF con OCR",
|
||||
"desc": "Pulisci scansioni ed estrai testo da immagini, convertendo le immagini in testo puro.",
|
||||
"settings": {
|
||||
"title": "Impostazioni",
|
||||
"ocrMode": {
|
||||
"label": "Modalità OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Lingue"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Modalità OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Lingue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "immagine,foto,salva,archivio,zip,catturare,prendere",
|
||||
@@ -938,7 +1027,13 @@
|
||||
"title": "Appiattire",
|
||||
"header": "Appiattisci PDF",
|
||||
"flattenOnlyForms": "Appiattisci solo i moduli",
|
||||
"submit": "Appiattisci"
|
||||
"submit": "Appiattisci",
|
||||
"steps": {
|
||||
"settings": "Impostazioni"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Appiattisci solo i moduli"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "aggiustare,ripristinare,correggere,recuperare",
|
||||
@@ -1174,7 +1269,22 @@
|
||||
"showLayers": "Mostra livelli (fare doppio clic per ripristinare tutti i livelli allo stato predefinito)",
|
||||
"colourPicker": "Selettore colore",
|
||||
"findCurrentOutlineItem": "Trova l'elemento di contorno corrente",
|
||||
"applyChanges": "Applica modifiche"
|
||||
"applyChanges": "Applica modifiche",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avanzate"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Aggiungi"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Pagine",
|
||||
"placeholder": "(es. 1,2,8 o 4,7,12-16 o 2n-1)"
|
||||
},
|
||||
"export": "Esporta"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Estrazione tabella,estrai,converti"
|
||||
@@ -1368,7 +1478,7 @@
|
||||
"contrast": "Contrasto:",
|
||||
"brightness": "Luminosità:",
|
||||
"saturation": "Saturazione:",
|
||||
"download": "Download"
|
||||
"download": "Salva"
|
||||
},
|
||||
"compress": {
|
||||
"title": "Comprimi",
|
||||
@@ -1386,7 +1496,10 @@
|
||||
"4": "Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF",
|
||||
"5": "Dimensioni PDF previste (ad es. 25 MB, 10,8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Comprimi"
|
||||
"submit": "Comprimi",
|
||||
"method": {
|
||||
"filesize": "Dimensione"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Questo file è protetto da password. Inserisci la password:",
|
||||
@@ -1557,5 +1670,83 @@
|
||||
"description": "Questi cookie ci aiutano a capire come vengono utilizzati i nostri strumenti, così possiamo concentrarci sullo sviluppo delle funzionalità che la nostra community apprezza di più. Non preoccuparti: Stirling PDF non può e non traccerà mai il contenuto dei documenti con cui lavori."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Salva",
|
||||
"undo": "Annulla",
|
||||
"convert": {
|
||||
"title": "Converti",
|
||||
"settings": "Impostazioni",
|
||||
"color": "Colore",
|
||||
"greyscale": "Scala di grigi",
|
||||
"fillPage": "Riempi la pagina",
|
||||
"pdfaDigitalSignatureWarning": "Il PDF contiene una firma digitale. Questo verrà rimosso nel passaggio successivo.",
|
||||
"grayscale": "Scala di grigi"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "incorporare,allegare,file,allegato,allegati",
|
||||
"title": "Aggiungere allegati",
|
||||
"header": "Aggiungi allegati",
|
||||
"submit": "Aggiungi allegati"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Seleziona tutto",
|
||||
"deselectAll": "Deseleziona tutto"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Firma"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Caricamento...",
|
||||
"or": "o"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nome",
|
||||
"fileVersion": "Versione",
|
||||
"selectAll": "Seleziona tutto",
|
||||
"deselectAll": "Deseleziona tutto",
|
||||
"deleteSelected": "Elimina selezionata",
|
||||
"download": "Salva",
|
||||
"delete": "Elimina"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Pulire PDF",
|
||||
"steps": {
|
||||
"settings": "Impostazioni"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Cambia Permessi",
|
||||
"submit": "Cambia Permessi",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Previeni assemblaggio del documento"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Previeni estrazione del contenuto"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Previeni estrazione per accessibilità"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Previeni compilazione dei moduli"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Previeni modifiche"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Previeni annotazioni"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Previeni stampa"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Previeni stampa in diversi formati"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Cambia Permessi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,14 +118,14 @@
|
||||
"addToDoc": "ドキュメントに追加",
|
||||
"reset": "リセット",
|
||||
"apply": "適用",
|
||||
"noFileSelected": "No file selected. Please upload one.",
|
||||
"noFileSelected": "ファイルが選択されていません。アップロードしてください。",
|
||||
"legal": {
|
||||
"privacy": "プライバシーポリシー",
|
||||
"terms": "利用規約",
|
||||
"accessibility": "アクセシビリティ",
|
||||
"cookie": "Cookieポリシー",
|
||||
"impressum": "著作権利者情報",
|
||||
"showCookieBanner": "Cookie Preferences"
|
||||
"showCookieBanner": "クッキーの設定"
|
||||
},
|
||||
"pipeline": {
|
||||
"header": "パイプラインメニュー (Beta)",
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "PDFを圧縮してファイルサイズを小さくします。"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "PDFフォームのロックを解除",
|
||||
"desc": "PDFドキュメント内のフォームフィールドの読み取り専用プロパティを削除します。"
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "メタデータの変更",
|
||||
@@ -578,6 +578,43 @@
|
||||
"replace-color": {
|
||||
"title": "色の置換と反転",
|
||||
"desc": "PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。"
|
||||
},
|
||||
"convert": {
|
||||
"title": "変換"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "添付ファイルの追加",
|
||||
"desc": "PDFに埋め込みファイル(添付ファイル)を追加または削除します"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "目次の編集",
|
||||
"desc": "PDFドキュメントにしおりと目次を追加または編集します"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "ページの抽出"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "削除",
|
||||
"desc": "PDFから不要なページを削除します。"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "画像の削除",
|
||||
"desc": "PDFから画像を削除してファイルサイズを小さくします"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "サイズ・数による自動分割",
|
||||
"desc": "サイズ・ページ数またはドキュメント数に基づいて、1つのPDFを複数のドキュメントに分割します。"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "色/コントラストの調整",
|
||||
"desc": "PDFのコントラスト、彩度、明るさを調整します。"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "色の置換と反転",
|
||||
"desc": "PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "権限の変更"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "名前で並べ替え",
|
||||
"sortByDate": "日付で並べ替え",
|
||||
"removeCertSign": "結合されたファイル内のデジタル署名を削除しますか?",
|
||||
"submit": "結合"
|
||||
"submit": "結合",
|
||||
"sortBy": {
|
||||
"filename": "ファイル名"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Page operations,divide,Multi Page,cut,server side",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "ドキュメント #6: ページ 10"
|
||||
},
|
||||
"splitPages": "分割するページ番号を入力:",
|
||||
"submit": "分割"
|
||||
"submit": "分割",
|
||||
"steps": {
|
||||
"settings": "設定"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "ファイルサイズ"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "ファイルサイズ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server side",
|
||||
@@ -644,10 +697,10 @@
|
||||
"submit": "回転"
|
||||
},
|
||||
"imageToPdf": {
|
||||
"tags": "conversion,img,jpg,picture,photo"
|
||||
"tags": "conversion,img,jpg,picture,photo,psd,photoshop"
|
||||
},
|
||||
"pdfToImage": {
|
||||
"tags": "conversion,img,jpg,picture,photo",
|
||||
"tags": "conversion,img,jpg,picture,photo,psd,photoshop",
|
||||
"title": "PDFを画像に変換",
|
||||
"header": "PDFを画像に変換",
|
||||
"selectText": "画像の形式",
|
||||
@@ -712,6 +765,12 @@
|
||||
"type": {
|
||||
"1": "テキスト",
|
||||
"2": "画像"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "テキスト"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "フォントサイズ"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +793,9 @@
|
||||
"submit": "変更"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
"tags": "Remove pages,delete pages",
|
||||
"title": "削除",
|
||||
"submit": "削除"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "secure,security",
|
||||
@@ -758,7 +819,12 @@
|
||||
"15": "ドキュメントを開いた後に実行できる操作を制限します (すべてのリーダーでサポートされているわけではありません)",
|
||||
"16": "ドキュメントを開くことを制限します"
|
||||
},
|
||||
"submit": "暗号化"
|
||||
"submit": "暗号化",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "権限の変更"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
@@ -768,15 +834,20 @@
|
||||
"1": "復号化するPDFを選択",
|
||||
"2": "パスワード"
|
||||
},
|
||||
"submit": "削除"
|
||||
"submit": "削除",
|
||||
"desc": "PDFからパスワードの削除します。",
|
||||
"password": {
|
||||
"stepTitle": "パスワードの削除",
|
||||
"label": "現在のパスワード"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"title": "フォームフィールドから読み取り専用を削除",
|
||||
"header": "PDFフォームのロックを解除",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -829,7 +900,25 @@
|
||||
},
|
||||
"help": "他の言語でこれを使用する方法やDocker以外で使用する方法についてはこのドキュメントをお読みください。",
|
||||
"credit": "本サービスにはOCRにqpdfとTesseractを使用しています。",
|
||||
"submit": "OCRでPDFを処理する"
|
||||
"submit": "OCRでPDFを処理する",
|
||||
"desc": "クリーンアップはPDF内の画像からテキストを検出してテキストとして再追加します。",
|
||||
"settings": {
|
||||
"title": "設定",
|
||||
"ocrMode": {
|
||||
"label": "OCRモード"
|
||||
},
|
||||
"languages": {
|
||||
"label": "言語"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCRモード"
|
||||
},
|
||||
"languages": {
|
||||
"title": "言語"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "picture,photo,save,archive,zip,capture,grab",
|
||||
@@ -938,7 +1027,13 @@
|
||||
"title": "平坦化",
|
||||
"header": "PDFを平坦化する",
|
||||
"flattenOnlyForms": "フォームのみを平坦にする",
|
||||
"submit": "平坦化"
|
||||
"submit": "平坦化",
|
||||
"steps": {
|
||||
"settings": "設定"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "フォームのみを平坦にする"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fix,restore,correction,recover",
|
||||
@@ -1174,7 +1269,22 @@
|
||||
"showLayers": "レイヤーを表示(ダブルクリックするとすべてのレイヤーがデフォルトの状態にリセットされます)",
|
||||
"colourPicker": "カラー選択",
|
||||
"findCurrentOutlineItem": "現在のアウトライン項目を検索",
|
||||
"applyChanges": "変更を適用"
|
||||
"applyChanges": "変更を適用",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "アドバンスド"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "追加"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "ページ",
|
||||
"placeholder": "(例:1,2,8、4,7,12-16、2n-1)"
|
||||
},
|
||||
"export": "エクスポート"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Table Extraction,extract,convert"
|
||||
@@ -1336,7 +1446,7 @@
|
||||
"alreadyLoggedIn": "すでにログインしています",
|
||||
"alreadyLoggedIn2": "デバイスからログアウトしてもう一度お試しください。",
|
||||
"toManySessions": "アクティブなセッションが多すぎます",
|
||||
"logoutMessage": "You have been logged out."
|
||||
"logoutMessage": "ログアウトしました"
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDFを単一ページに変換",
|
||||
@@ -1386,7 +1496,10 @@
|
||||
"4": "自動モード - PDFを正確なサイズにするために品質を自動調整する。",
|
||||
"5": "PDFサイズ (例:25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "圧縮"
|
||||
"submit": "圧縮",
|
||||
"method": {
|
||||
"filesize": "ファイルサイズ"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "このファイルはパスワードで保護されています。パスワードを入力してください:",
|
||||
@@ -1557,5 +1670,83 @@
|
||||
"description": "これらのCookieはツールがどのように使用されているかを把握するのに役立ちます。これによりコミュニティが最も重視する機能の開発に集中することができます。ご安心ください。Stirling PDFはお客様が操作するドキュメントの内容を追跡することは決してありません。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "ダウンロード",
|
||||
"undo": "元に戻す",
|
||||
"convert": {
|
||||
"title": "変換",
|
||||
"settings": "設定",
|
||||
"color": "色",
|
||||
"greyscale": "グレースケール",
|
||||
"fillPage": "フルページ",
|
||||
"pdfaDigitalSignatureWarning": "PDFにはデジタル署名が含まれています。これは次の手順で削除されます。",
|
||||
"grayscale": "グレースケール"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "添付ファイルの追加",
|
||||
"header": "添付ファイルの追加",
|
||||
"submit": "添付ファイルの追加"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "すべて選択",
|
||||
"deselectAll": "選択を解除"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "署名"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "読込中...",
|
||||
"or": "または"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "名前",
|
||||
"fileVersion": "バージョン",
|
||||
"selectAll": "すべて選択",
|
||||
"deselectAll": "選択を解除",
|
||||
"deleteSelected": "選択項目を削除",
|
||||
"download": "ダウンロード",
|
||||
"delete": "削除"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDFをサニタイズ",
|
||||
"steps": {
|
||||
"settings": "設定"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "権限の変更",
|
||||
"submit": "権限の変更",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "ドキュメントの組立を禁止"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "コンテンツの抽出を禁止"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "アクセシビリティのための抽出を禁止"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "フォームへの入力を禁止"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "変更を禁止"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "注釈の変更を禁止"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "印刷を禁止"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "異なる形式の印刷を禁止"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "権限の変更"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "로딩 중...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "색상 교체 및 반전",
|
||||
"desc": "PDF에서 텍스트와 배경의 색상을 교체하고 파일 크기를 줄이기 위해 전체 PDF 색상을 반전"
|
||||
},
|
||||
"convert": {
|
||||
"title": "변환"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "페이지 추출"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "제거",
|
||||
"desc": "PDF 문서에서 원하지 않는 페이지를 삭제합니다."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "이미지 제거",
|
||||
"desc": "파일 크기를 줄이기 위해 PDF에서 이미지 제거"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "크기/개수별 자동 분할",
|
||||
"desc": "단일 PDF를 크기, 페이지 수 또는 문서 수를 기준으로 여러 문서로 분할"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "색상/대비 조정",
|
||||
"desc": "PDF의 대비, 채도 및 밝기 조정"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "색상 교체 및 반전",
|
||||
"desc": "PDF에서 텍스트와 배경의 색상을 교체하고 파일 크기를 줄이기 위해 전체 PDF 색상을 반전"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "권한 변경"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "이름으로 정렬",
|
||||
"sortByDate": "날짜로 정렬",
|
||||
"removeCertSign": "병합된 파일에서 디지털 서명을 제거하시겠습니까?",
|
||||
"submit": "병합"
|
||||
"submit": "병합",
|
||||
"sortBy": {
|
||||
"filename": "파일 이름"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "페이지 작업,나누기,멀티 페이지,자르기,서버 사이드",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "문서 #6: 10페이지"
|
||||
},
|
||||
"splitPages": "분할할 페이지 입력:",
|
||||
"submit": "분할"
|
||||
"submit": "분할",
|
||||
"steps": {
|
||||
"settings": "설정"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "파일 크기"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "파일 크기"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "서버 사이드",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "텍스트",
|
||||
"2": "이미지"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "텍스트"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "글꼴 크기"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "변경"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "페이지 제거,페이지 삭제"
|
||||
"tags": "페이지 제거,페이지 삭제",
|
||||
"title": "제거",
|
||||
"submit": "제거"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "보안,보안",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "문서가 열린 후 수행할 수 있는 작업 제한 (모든 리더에서 지원되지 않음)",
|
||||
"16": "문서 자체 열기 제한"
|
||||
},
|
||||
"submit": "암호화"
|
||||
"submit": "암호화",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "권한 변경"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "보안,암호 해제,보안,비밀번호 해제,비밀번호 삭제",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "복호화할 PDF 선택",
|
||||
"2": "비밀번호"
|
||||
},
|
||||
"submit": "제거"
|
||||
"submit": "제거",
|
||||
"desc": "PDF 문서에서 비밀번호 보호를 제거합니다.",
|
||||
"password": {
|
||||
"stepTitle": "비밀번호 제거",
|
||||
"label": "현재 비밀번호"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "압축,작게,매우 작게"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "다른 언어 사용 방법 및/또는 Docker에서 사용하지 않는 방법에 대한 문서를 읽어보세요",
|
||||
"credit": "이 서비스는 OCR을 위해 qpdf와 Tesseract를 사용합니다.",
|
||||
"submit": "OCR로 PDF 처리"
|
||||
"submit": "OCR로 PDF 처리",
|
||||
"desc": "스캔을 정리하고 PDF 내 이미지에서 텍스트를 감지하여 다시 텍스트로 추가합니다.",
|
||||
"settings": {
|
||||
"title": "설정",
|
||||
"ocrMode": {
|
||||
"label": "OCR 모드"
|
||||
},
|
||||
"languages": {
|
||||
"label": "언어"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR 모드"
|
||||
},
|
||||
"languages": {
|
||||
"title": "언어"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "사진,저장,아카이브,zip,캡처,가져오기",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "평면화",
|
||||
"header": "PDF 평면화",
|
||||
"flattenOnlyForms": "양식만 평면화",
|
||||
"submit": "평면화"
|
||||
"submit": "평면화",
|
||||
"steps": {
|
||||
"settings": "설정"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "양식만 평면화"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "수정,복원,교정,복구",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "레이어 보기 (더블클릭하여 모든 레이어를 기본 상태로 재설정)",
|
||||
"colourPicker": "색상 선택기",
|
||||
"findCurrentOutlineItem": "현재 개요 항목 찾기",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "고급"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "추가"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "페이지",
|
||||
"placeholder": "(예: 1,2,8 또는 4,7,12-16 또는 2n-1)"
|
||||
},
|
||||
"export": "내보내기"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,테이블-추출,추출,변환"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "자동 모드 - PDF를 정확한 크기로 만들기 위해 품질 자동 조정",
|
||||
"5": "예상 PDF 크기 (예: 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "압축"
|
||||
"submit": "압축",
|
||||
"method": {
|
||||
"filesize": "파일 크기"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "이 파일은 비밀번호로 보호되어 있습니다. 비밀번호를 입력하세요:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "다운로드",
|
||||
"convert": {
|
||||
"title": "변환",
|
||||
"settings": "설정",
|
||||
"color": "색상",
|
||||
"greyscale": "그레이스케일",
|
||||
"fillPage": "페이지 채우기",
|
||||
"pdfaDigitalSignatureWarning": "PDF에 디지털 서명이 포함되어 있습니다. 다음 단계에서 제거됩니다.",
|
||||
"grayscale": "그레이스케일"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "모두 선택",
|
||||
"deselectAll": "모두 선택 해제"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "서명"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "로딩 중...",
|
||||
"or": "또는"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "이름",
|
||||
"fileVersion": "버전",
|
||||
"selectAll": "모두 선택",
|
||||
"deselectAll": "모두 선택 해제",
|
||||
"deleteSelected": "선택 항목 삭제",
|
||||
"download": "다운로드",
|
||||
"delete": "삭제"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF 정리",
|
||||
"steps": {
|
||||
"settings": "설정"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "권한 변경",
|
||||
"submit": "권한 변경",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "문서 조립 방지"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "콘텐츠 추출 방지"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "접근성을 위한 추출 방지"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "양식 작성 방지"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "수정 방지"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "주석 수정 방지"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "인쇄 방지"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "다른 형식으로 인쇄 방지"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "권한 변경"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Laden...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Vervang de kleur van tekst en achtergrond in een PDF en omverkeer de volledige kleur van het document om bestandsgrootte te verkleinen."
|
||||
},
|
||||
"convert": {
|
||||
"title": "Omzetten"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Pagina's extraheren"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Verwijderen",
|
||||
"desc": "Verwijder ongewenste pagina's uit je PDF-document."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Afbeelding verwijderen",
|
||||
"desc": "Afbeeldingen uit PDF verwijderen om het bestandsgrootte te verminderen"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Automatisch splitsen op grootte/aantal",
|
||||
"desc": "Splits een enkele PDF in meerdere documenten op basis van grootte, aantal pagina's of aantal documenten"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Kleuren/contrast aanpassen",
|
||||
"desc": "Pas contrast, verzadiging en helderheid van een PDF aan"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Vervang de kleur van tekst en achtergrond in een PDF en omverkeer de volledige kleur van het document om bestandsgrootte te verkleinen."
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Rechten wijzigen"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -594,7 +626,7 @@
|
||||
"deselectAll": "Deselect All",
|
||||
"selectPages": "Page Select",
|
||||
"selectedPages": "Selected Pages",
|
||||
"page": "Page",
|
||||
"page": "Pagina",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"downloadAll": "Export",
|
||||
"downloadSelected": "Export Selected",
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Splitsen",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Verwijderen",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sorteer op naam",
|
||||
"sortByDate": "Sorteer op datum",
|
||||
"removeCertSign": "Verwijder digitale handtekening in het samengevoegde bestand?",
|
||||
"submit": "Samenvoegen"
|
||||
"submit": "Samenvoegen",
|
||||
"sortBy": {
|
||||
"filename": "Bestandsnaam"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Pagina bewerkingen,verdelen,meerdere pagina's,knippen,serverzijde",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Document #6: Pagina 10"
|
||||
},
|
||||
"splitPages": "Voer pagina's in om op te splitsen:",
|
||||
"submit": "Splitsen"
|
||||
"submit": "Splitsen",
|
||||
"steps": {
|
||||
"settings": "Instellingen"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Bestandsgrootte"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Bestandsgrootte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "serverzijde",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Tekst",
|
||||
"2": "Afbeelding"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Tekst"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Lettertypegrootte"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Wijzigen"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Pagina's verwijderen"
|
||||
"tags": "Pagina's verwijderen",
|
||||
"title": "Verwijderen",
|
||||
"submit": "Verwijderen"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "veilig,beveiliging",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Beperkt wat gedaan kan worden met het document nadat het is geopend (Niet ondersteund door alle lezers)",
|
||||
"16": "Beperkt het openen van het document zelf"
|
||||
},
|
||||
"submit": "Versleutelen"
|
||||
"submit": "Versleutelen",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Rechten wijzigen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "veilig,ontsleutelen,beveiliging,wachtwoord verwijderen",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Selecteer PDF om te decrypteren",
|
||||
"2": "Wachtwoord"
|
||||
},
|
||||
"submit": "Verwijderen"
|
||||
"submit": "Verwijderen",
|
||||
"desc": "Verwijder wachtwoordbeveiliging van je PDF-document.",
|
||||
"password": {
|
||||
"stepTitle": "Wachtwoord verwijderen",
|
||||
"label": "Huidige wachtwoord"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "comprimeren,klein"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Lees deze documentatie over hoe dit te gebruiken voor andere talen en/of gebruik buiten docker",
|
||||
"credit": "Deze dienst maakt gebruik van qpdf en Tesseract voor OCR.",
|
||||
"submit": "Verwerk PDF met OCR"
|
||||
"submit": "Verwerk PDF met OCR",
|
||||
"desc": "Ruim scans op, detecteert tekst van afbeeldingen in een PDF en voegt deze opnieuw toe als tekst.",
|
||||
"settings": {
|
||||
"title": "Instellingen",
|
||||
"ocrMode": {
|
||||
"label": "OCR-modus"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Talen"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR-modus"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Talen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "foto,opslaan,archief,zip,vastleggen,plukken",
|
||||
@@ -924,7 +1008,7 @@
|
||||
"sharedSigs": "Gedeelde Signatuuren",
|
||||
"noSavedSigs": "Geen opgeslagen signatuuren gevonden",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Verwijderen",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Afvlakken",
|
||||
"header": "PDF's afvlakken",
|
||||
"flattenOnlyForms": "Alleen formulieren afvlakken",
|
||||
"submit": "Afvlakken"
|
||||
"submit": "Afvlakken",
|
||||
"steps": {
|
||||
"settings": "Instellingen"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Alleen formulieren afvlakken"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "repareren,herstellen,correctie,terughalen",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Gebruikt WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,web-inhoud,transformatie,omzetten",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Omzetten"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informatie,data,statistieken",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Geavanceerd"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Toevoegen"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Pagen",
|
||||
"placeholder": "(bijv. 1,2,8 of 4,7,12-16 of 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,tabel extractie,extractie,converteren"
|
||||
@@ -1261,8 +1365,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Reden",
|
||||
"location": "Locatie",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1385,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Versie",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "Automatische modus - Past kwaliteit automatisch aan om PDF naar exacte grootte te krijgen",
|
||||
"5": "Verwachte PDF-grootte (bijv. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Comprimeren"
|
||||
"submit": "Comprimeren",
|
||||
"method": {
|
||||
"filesize": "Bestandsgrootte"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1664,74 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Downloaden",
|
||||
"convert": {
|
||||
"title": "Omzetten",
|
||||
"settings": "Instellingen",
|
||||
"color": "Kleur",
|
||||
"greyscale": "Grijstinten",
|
||||
"fillPage": "Pagina vullen",
|
||||
"pdfaDigitalSignatureWarning": "Dit PDF bestand bevat een digitale handtekening. Deze wordt in de volgende stap verwijderd.",
|
||||
"grayscale": "Grijstinten"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Ondertekenen"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Laden..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Naam",
|
||||
"fileVersion": "Versie",
|
||||
"download": "Downloaden",
|
||||
"delete": "Verwijderen"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF opschonen",
|
||||
"steps": {
|
||||
"settings": "Instellingen"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Rechten wijzigen",
|
||||
"submit": "Rechten wijzigen",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Voorkom samenvoegen van document"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Voorkom inhoudsextractie"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Voorkom extractie voor toegankelijkheid"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Voorkom invullen van formulier"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Voorkom wijziging"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Voorkom annotatie wijziging"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Voorkom afdrukken"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Voorkom afdrukken in verschillende formaten"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Rechten wijzigen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@
|
||||
"scanHelp": "Mappe skanning hjelp",
|
||||
"deletePrompt": "Er du sikker på at du vil slette denne pipelinen?",
|
||||
"tags": "automatisere,sekvens,skriptet,batch-prosess",
|
||||
"title": "Pipeline"
|
||||
"title": "Pipeline (Avansert)"
|
||||
},
|
||||
"pipelineOptions": {
|
||||
"header": "Pipeline konfigurasjon",
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Laster...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Erstatt og Inverter Farge",
|
||||
"desc": "Erstatt farge for tekst og bakgrunn i PDF og inverter full farge av pdf for å redusere filstørrelsen"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konverter"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Trekk ut Sider"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Fjern",
|
||||
"desc": "Slett uønskede sider fra din PDF-dokument."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Fjern bilde",
|
||||
"desc": "Fjern bilde fra PDF for å redusere filstørrelsen"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Auto Del etter Størrelse/Antall",
|
||||
"desc": "Del en enkelt PDF i flere dokumenter basert på størrelse, antall sider eller dokumenter"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Juster Farger/Kontrast",
|
||||
"desc": "Juster kontrast, metning og lysstyrke i en PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Erstatt og Inverter Farge",
|
||||
"desc": "Erstatt farge for tekst og bakgrunn i PDF og inverter full farge av pdf for å redusere filstørrelsen"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Endre tillatelser"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -594,7 +626,7 @@
|
||||
"deselectAll": "Deselect All",
|
||||
"selectPages": "Page Select",
|
||||
"selectedPages": "Selected Pages",
|
||||
"page": "Page",
|
||||
"page": "Side",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"downloadAll": "Export",
|
||||
"downloadSelected": "Export Selected",
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Del opp",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Slett",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sorter etter navn",
|
||||
"sortByDate": "Sorter etter dato",
|
||||
"removeCertSign": "Fjern digital signatur i den sammenslåtte filen?",
|
||||
"submit": "Slå sammen"
|
||||
"submit": "Slå sammen",
|
||||
"sortBy": {
|
||||
"filename": "Fil navn"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "sideoperasjoner,del,flersidig,kutt,serverside",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: Side 10"
|
||||
},
|
||||
"splitPages": "Skriv inn sidene som skal deles på:",
|
||||
"submit": "Del"
|
||||
"submit": "Del",
|
||||
"steps": {
|
||||
"settings": "Innstillinger"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Filstørrelse"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Filstørrelse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "serverside",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Tekst",
|
||||
"2": "Bilde"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Tekst"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Skriftstørrelse"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Endre"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "fjern sider,slett sider"
|
||||
"tags": "fjern sider,slett sider",
|
||||
"title": "Fjern",
|
||||
"submit": "Fjern"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "sikker,trygghet",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Begrenser hva som kan gjøres med dokumentet når det er åpnet (Støttes ikke av alle leserprogrammer)",
|
||||
"16": "Begrenser åpningen av dokumentet selv"
|
||||
},
|
||||
"submit": "Krypter"
|
||||
"submit": "Krypter",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Endre tillatelser"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "sikker,dekrypter,trygghet,upassord,slett passord",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Velg PDF for å dekryptere",
|
||||
"2": "Passord"
|
||||
},
|
||||
"submit": "Fjern"
|
||||
"submit": "Fjern",
|
||||
"desc": "Fjern passordbeskyttelse fra din PDF-dokument.",
|
||||
"password": {
|
||||
"stepTitle": "Fjern Passord",
|
||||
"label": "Nåværende Passord"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "komprimer,liten,tiny"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Vennligst les denne dokumentasjonen for hvordan du bruker dette for andre språk og/eller bruk utenfor Docker.",
|
||||
"credit": "Denne tjenesten bruker qpdf og Tesseract for OCR.",
|
||||
"submit": "Behandle PDF med OCR"
|
||||
"submit": "Behandle PDF med OCR",
|
||||
"desc": "Rydd opp skanninger og oppdag tekst fra bilder i en PDF og legg den til som tekst.",
|
||||
"settings": {
|
||||
"title": "Innstillinger",
|
||||
"ocrMode": {
|
||||
"label": "OCR-modus"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Språk"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR-modus"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Språk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "bilde,foto,lagre,arkiv,zip,fangst,hent",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Utjevning",
|
||||
"header": "Utjevning av PDf",
|
||||
"flattenOnlyForms": "Utjevning av kun skjemaer",
|
||||
"submit": "Utjevn"
|
||||
"submit": "Utjevn",
|
||||
"steps": {
|
||||
"settings": "Innstillinger"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Utjevning av kun skjemaer"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fiks,gjenopprett,korreksjon,gjenoppretting",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Bruker WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,web-innhold,transformasjon,konverter",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Konverter"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informasjon,data,statistikk",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Vis lag (dobbeltklikk for å tilbakestille alle lag til standardtilstand)",
|
||||
"colourPicker": "Fargevelger",
|
||||
"findCurrentOutlineItem": "Finn gjeldende punkt i strukturen",
|
||||
"applyChanges": "Bruk endringer"
|
||||
"applyChanges": "Bruk endringer",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avansert"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Legg til"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Sider",
|
||||
"placeholder": "(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,tabelluttrekk,ekstrahere,konvertere"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "Automatisk modus - Justerer automatisk kvaliteten for å få PDF til nøyaktig størrelse",
|
||||
"5": "Forventet PDF-størrelse (f.eks. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Komprimer"
|
||||
"submit": "Komprimer",
|
||||
"method": {
|
||||
"filesize": "Filstørrelse"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1501,7 +1608,7 @@
|
||||
"3": "Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.",
|
||||
"4": "Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs."
|
||||
},
|
||||
"submit": "Split PDF"
|
||||
"submit": "Del PDF"
|
||||
},
|
||||
"fileChooser": {
|
||||
"click": "Click",
|
||||
@@ -1557,5 +1664,75 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Last ned",
|
||||
"undo": "Angre",
|
||||
"convert": {
|
||||
"title": "Konverter",
|
||||
"settings": "Innstillinger",
|
||||
"color": "Farge",
|
||||
"greyscale": "Gråtone",
|
||||
"fillPage": "Fyll side",
|
||||
"pdfaDigitalSignatureWarning": "PDFen inneholder en digital signatur. Denne vil bli fjernet i neste steg.",
|
||||
"grayscale": "Gråtone"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Signer"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Laster..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Navn",
|
||||
"fileVersion": "Versjon",
|
||||
"download": "Last ned",
|
||||
"delete": "Slett"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Rensker PDF",
|
||||
"steps": {
|
||||
"settings": "Innstillinger"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Endre tillatelser",
|
||||
"submit": "Endre tillatelser",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Forhindre sammenstilling av dokumentet"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Forhindre innholdsekstraksjon"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Forhindre ekstraksjon for tilgjengelighet"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Forhindre utfylling av skjema"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Forhindre modifisering"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Forhindre annotasjonsmodifisering"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Forhindre utskrift"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Forhindre utskrift i ulike formater"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Endre tillatelser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,7 +320,7 @@
|
||||
"desc": "Twoja lokalna aplikacja do kompleksowej obsługi Twoich potrzeb związanych z dokumentami PDF.",
|
||||
"searchBar": "Szukaj opcji ...",
|
||||
"viewPdf": {
|
||||
"title": "View/Edit PDF",
|
||||
"title": "Przeglądaj/Edytuj PDF",
|
||||
"desc": "Wyświetl, adnotuj, dodaj tekst lub obrazy"
|
||||
},
|
||||
"setFavorites": "Ustaw ulubione",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Zastąp i Odwróć Kolor",
|
||||
"desc": "Zastąp kolor tekstu i tła w pliku PDF i odwróć pełen kolor pliku PDF, aby zmniejszyć rozmiar pliku"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konwertuj"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Wyciągnij stronę"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Usuń",
|
||||
"desc": "Usuń niechciane strony z dokumentu PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Usuń obraz",
|
||||
"desc": "Usuń obraz z pliku PDF, aby zmniejszyć rozmiar pliku"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Podziel (Rozmiar/Ilość stron)",
|
||||
"desc": "Rozdziela dokument PDF na wiele dokumentów bazując na podanym rozmiarze, ilości stron bądź ilości dokumentów"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Zmień kolor/nasycenie/jasność",
|
||||
"desc": "Zmień kolor/nasycenie/jasność w dokumencie PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Zastąp i Odwróć Kolor",
|
||||
"desc": "Zastąp kolor tekstu i tła w pliku PDF i odwróć pełen kolor pliku PDF, aby zmniejszyć rozmiar pliku"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Zmień uprawnienia"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sortuj po nazwie",
|
||||
"sortByDate": "Sortuj po dacie",
|
||||
"removeCertSign": "Usuń podpis cyfrowy w scalonym pliku?",
|
||||
"submit": "Połącz"
|
||||
"submit": "Połącz",
|
||||
"sortBy": {
|
||||
"filename": "Nazwa pliku"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operacje na stronach, dzielenie, wiele stron, cięcie, po stronie serwera",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: Strona 10"
|
||||
},
|
||||
"splitPages": "Wprowadź strony do podziału na:",
|
||||
"submit": "Podziel"
|
||||
"submit": "Podziel",
|
||||
"steps": {
|
||||
"settings": "Ustawienia"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Rozmiar pliku"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Rozmiar pliku"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "strona serwera",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Tekst",
|
||||
"2": "Obraz"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Tekst"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Rozmiar Czcionki"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Zmień"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Usuń strony,usuwaj strony"
|
||||
"tags": "Usuń strony,usuwaj strony",
|
||||
"title": "Usuń",
|
||||
"submit": "Usuń"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "bezpieczeństwo,ochrona",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Ogranicza akcje, które można wykonać na dokumencie, kiedy jest otwarty (nie wspierany przez wszystkie przeglądarki)",
|
||||
"16": "Ogranicza otwarcie dokumentu"
|
||||
},
|
||||
"submit": "Zablokuj"
|
||||
"submit": "Zablokuj",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Zmień uprawnienia"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "zabezpieczenie,odszyfrowanie,bezpieczeństwo,odhasłowanie,usunięcie hasła",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Wybierz dokument PDF do odszyfrowania",
|
||||
"2": "Hasło"
|
||||
},
|
||||
"submit": "Usuń"
|
||||
"submit": "Usuń",
|
||||
"desc": "Usuń ochronę hasłem z dokumentu PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Usuń hasło",
|
||||
"label": "Obecne hasło"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "zgniatać,mały,malutki"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Przeczytaj tę dokumentację, aby dowiedzieć się, jak używać tego w innych językach i/lub nie używać docker",
|
||||
"credit": "Ta usługa używa qpdf i Tesseract do OCR.",
|
||||
"submit": "Przetwarzaj PDF za pomocą OCR"
|
||||
"submit": "Przetwarzaj PDF za pomocą OCR",
|
||||
"desc": "OCR skanuje i wykrywa tekst z obrazów w dokumencie PDF i zamienia go na tekst.",
|
||||
"settings": {
|
||||
"title": "Ustawienia",
|
||||
"ocrMode": {
|
||||
"label": "Tryb OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Języki"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Tryb OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Języki"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "obraz, zdjęcie, zapisz, archiwum, zip, przechwyć, złap",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Spłaszcz",
|
||||
"header": "Spłaszcz dokument(y) PDF",
|
||||
"flattenOnlyForms": "Spłaszcz tylko formularze",
|
||||
"submit": "Spłaszcz"
|
||||
"submit": "Spłaszcz",
|
||||
"steps": {
|
||||
"settings": "Ustawienia"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Spłaszcz tylko formularze"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "naprawianie, naprawa, przywracanie, poprawianie, odzyskiwanie",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Pokaż warstwy (kliknij dwukrotnie, aby przywrócić domyślny stan warstw)",
|
||||
"colourPicker": "Selektor kolorów",
|
||||
"findCurrentOutlineItem": "Znajdź bieżący element zarysu",
|
||||
"applyChanges": "Zastosuj zmiany"
|
||||
"applyChanges": "Zastosuj zmiany",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Zaawansowane"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Dodaj"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Strony",
|
||||
"placeholder": "(przykład 1,2,8 lub 2n-1)"
|
||||
},
|
||||
"export": "Eksportuj"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, ekstrakcja tabeli, ekstrakcja, konwersja, wydobywanie"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Tryb automatyczny - Automatycznie dostosowuje jakość, aby uzyskać dokładny rozmiar pliku PDF",
|
||||
"5": "Oczekiwany rozmiar pliku PDF (np. 25 MB, 10,8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Kompresuj"
|
||||
"submit": "Kompresuj",
|
||||
"method": {
|
||||
"filesize": "Rozmiar pliku"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Ten plik jest chroniony hasłem. Wprowadź hasło:",
|
||||
@@ -1557,5 +1665,83 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Pobierz",
|
||||
"undo": "Cofnij",
|
||||
"convert": {
|
||||
"title": "Konwertuj",
|
||||
"settings": "Ustawienia",
|
||||
"color": "kolor",
|
||||
"greyscale": "Odcień szarości",
|
||||
"fillPage": "Wypełnij stronę",
|
||||
"pdfaDigitalSignatureWarning": "Dokument zawiera podpis cyfrowy, nie zostanie on wczytany.",
|
||||
"grayscale": "Odcień szarości"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Zaznacz wszystko",
|
||||
"deselectAll": "Odznacz wszystko"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Podpis"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Ładowanie...",
|
||||
"or": "lub"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nazwa",
|
||||
"fileVersion": "Wersja",
|
||||
"selectAll": "Zaznacz wszystko",
|
||||
"deselectAll": "Odznacz wszystko",
|
||||
"deleteSelected": "Usuń zaznaczone",
|
||||
"download": "Pobierz",
|
||||
"delete": "usuń"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Dezynfekuj PDF",
|
||||
"steps": {
|
||||
"settings": "Ustawienia"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Zmień uprawnienia",
|
||||
"submit": "Zmień uprawnienia",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Zablokuj zmiany w dokumencie"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Zablokuj zmiany w treści"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Zablokuj zmiany w celu ułatwienia dostępu"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Zablokuj wypełnianie formularzy"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Zablokuj modyfikacje"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Zablokuj modyfikacje adnotacji"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Zablokuj drukowanie"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Zablokuj drukowanie różnych formatów"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Zmień uprawnienia"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "Comprimir PDFs para reduzir o tamanho do arquivo."
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "Desbloquear Formulários do PDF",
|
||||
"desc": "Remove a propriedade de apenas leitura de formulários em um documento PDF."
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "Alterar Metadados",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Substitui e Inverte Cores",
|
||||
"desc": "Substitui cor do texto e plano de fundo de um PDF e/ou inverte a toda cor do PDF para reduzir o tamanho."
|
||||
},
|
||||
"convert": {
|
||||
"title": "Converter"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extrair Página(s)"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Remover Páginas",
|
||||
"desc": "Excluir páginas indesejadas do seu PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Remover Imagem",
|
||||
"desc": "Remova imagens do PDF para reduzir o tamanho do arquivo."
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Divisão Manual do PDF",
|
||||
"desc": "Divida um PDF em vários, com base no tamanho, contagem de páginas ou contagem de documentos."
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Ajuste Visual do PDF",
|
||||
"desc": "Ajustar Contraste, Saturação e Brilho de um PDF."
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Substitui e Inverte Cores",
|
||||
"desc": "Substitui cor do texto e plano de fundo de um PDF e/ou inverte a toda cor do PDF para reduzir o tamanho."
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Alterar Permissões"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Classificar por Nome",
|
||||
"sortByDate": "Classificar por Data",
|
||||
"removeCertSign": "Remover a assinatura digital do arquivo mesclado?",
|
||||
"submit": "Mesclar"
|
||||
"submit": "Mesclar",
|
||||
"sortBy": {
|
||||
"filename": "Nome do Arquivo"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operações de Página,dividir,Múltiplas Páginas,cortar,lado do servidor",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Documento Nº6: Página 10 e 11 (e mais se tiver)"
|
||||
},
|
||||
"splitPages": "Digite as páginas para a divisão:",
|
||||
"submit": "Dividir"
|
||||
"submit": "Dividir",
|
||||
"steps": {
|
||||
"settings": "Configurações"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Tamanho do Arquivo"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Tamanho do Arquivo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "Lado do servidor",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Texto",
|
||||
"2": "Imagem"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Texto"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Tamanho da Fonte"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Alterar"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remover páginas,excluir páginas"
|
||||
"tags": "Remover páginas,excluir páginas",
|
||||
"title": "Remover Páginas",
|
||||
"submit": "Remover Páginas"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "seguro,segurança",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Restringe o que pode ser feito com o documento depois de aberto (não suportado por todos os leitores).",
|
||||
"16": "Restringe a abertura do próprio documento."
|
||||
},
|
||||
"submit": "Criptografar"
|
||||
"submit": "Criptografar",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Alterar Permissões"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "seguro, descriptografar, segurança, remover senha",
|
||||
@@ -768,15 +829,20 @@
|
||||
"1": "Selecione o PDF para descriptografar:",
|
||||
"2": "Senha"
|
||||
},
|
||||
"submit": "Descriptografar"
|
||||
"submit": "Descriptografar",
|
||||
"desc": "Descriptografar o PDF realizando a remoção da senha.",
|
||||
"password": {
|
||||
"stepTitle": "Desproteger PDF",
|
||||
"label": "Senha Atual"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "compactar,pequeno,mínimo"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"tags": "remoção,apagar,formulario,campo,apenasleitura",
|
||||
"title": "Remover Apenas Leitura de Formulários",
|
||||
"header": "Desbloquear Formulários do PDF",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Por favor, leia a documentação abaixo para saber mais sobre OCR e sua utilização.",
|
||||
"credit": "Este serviço usa Qpdf e Tesseract para OCR.",
|
||||
"submit": "Processar OCR no PDF"
|
||||
"submit": "Processar OCR no PDF",
|
||||
"desc": "Reconhecimento Óptico de Caracteres transforma PDFs com imagens em documentos pesquisáveis e com texto selecionável.",
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
"ocrMode": {
|
||||
"label": "Modo OCR:"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Idiomas"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Modo OCR:"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Idiomas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "imagem,foto,salvar,arquivo,zip,captura,coleta",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Achatar",
|
||||
"header": "Achatar",
|
||||
"flattenOnlyForms": "Achatar apenas formulários interativos.",
|
||||
"submit": "Achatar"
|
||||
"submit": "Achatar",
|
||||
"steps": {
|
||||
"settings": "Configurações"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Achatar apenas formulários interativos."
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "corrigir,restaurar,correção,recuperar",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Mostrar Camadas (duplo clique para restabelecer as camadas para o estado padrão)",
|
||||
"colourPicker": "Seletor de Cores",
|
||||
"findCurrentOutlineItem": "Encontrar item atual",
|
||||
"applyChanges": "Aplicar Alterações"
|
||||
"applyChanges": "Aplicar Alterações",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avançado"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Adicionar"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Páginas",
|
||||
"placeholder": "(por exemplo 1,2,8 ou 4,7,12-16 ou 2n-1)"
|
||||
},
|
||||
"export": "Exportar"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,extração de tabela,extrair,converter"
|
||||
@@ -1336,7 +1441,7 @@
|
||||
"alreadyLoggedIn": "Você já está conectado em",
|
||||
"alreadyLoggedIn2": "aparelhos. Por favor saia dos aparelhos e tente novamente.",
|
||||
"toManySessions": "Você tem muitas sessões ativas",
|
||||
"logoutMessage": "You have been logged out."
|
||||
"logoutMessage": "Você foi desconectado."
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF para Página Única",
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Modo Automático - Ajusta automaticamente a qualidade para atingir o tamanho exato desejado",
|
||||
"5": "Tamanho esperado do PDF (por exemplo, 25 MB, 10,8 MB, 25 KB):"
|
||||
},
|
||||
"submit": "Comprimir"
|
||||
"submit": "Comprimir",
|
||||
"method": {
|
||||
"filesize": "Tamanho do Arquivo"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1665,83 @@
|
||||
"description": "Estes cookies nos ajudam a entender como nossas ferramentas estão sendo utilizadas, para que possamos nos concentrar na construção dos recursos que nossa comunidade mais valoriza. Fique tranquilo: o Stirling PDF não pode e nunca rastreará o conteúdo dos documentos com os quais você manipula."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Baixar (JSON)",
|
||||
"undo": "Desfazer",
|
||||
"convert": {
|
||||
"title": "Converter",
|
||||
"settings": "Configurações",
|
||||
"color": "Cor",
|
||||
"greyscale": "Escala de Cinza",
|
||||
"fillPage": "Preencher a página",
|
||||
"pdfaDigitalSignatureWarning": "O PDF contém uma assinatura digital. Isso será removido na próxima etapa.",
|
||||
"grayscale": "Escala de Cinza"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Selecionar Tudo",
|
||||
"deselectAll": "Desselecionar Tudo"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Assinar"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Carregando...",
|
||||
"or": "ou"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nome",
|
||||
"fileVersion": "Versão",
|
||||
"selectAll": "Selecionar Tudo",
|
||||
"deselectAll": "Desselecionar Tudo",
|
||||
"deleteSelected": "Apagar Selecionados",
|
||||
"download": "Baixar (JSON)",
|
||||
"delete": "Apagar"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Higienizar",
|
||||
"steps": {
|
||||
"settings": "Configurações"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Alterar Permissões",
|
||||
"submit": "Alterar Permissões",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Impedir montagem do documento."
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Impedir extração de conteúdo."
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Impedir extração para acessibilidade."
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Impedir preenchimento de formulário."
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Impedir modificações."
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Impedir modificação de anotações."
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Impedir impressão."
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Impedir impressão de formatos diferentes."
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Alterar Permissões"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "A carregar...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Substituir e Inverter Cor",
|
||||
"desc": "Substituir cor para texto e fundo em PDF e inverter cor completa do pdf para reduzir tamanho do ficheiro"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Converter"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extrair Páginas"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Remover",
|
||||
"desc": "Eliminar páginas indesejadas do seu documento PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Remover imagem",
|
||||
"desc": "Remover imagem do PDF para reduzir tamanho do ficheiro"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Divisão Automática por Tamanho/Contagem",
|
||||
"desc": "Dividir um único PDF em múltiplos documentos baseado em tamanho, contagem de páginas, ou contagem de documentos"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Ajustar Cores/Contraste",
|
||||
"desc": "Ajustar Contraste, Saturação e Brilho de um PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Substituir e Inverter Cor",
|
||||
"desc": "Substituir cor para texto e fundo em PDF e inverter cor completa do pdf para reduzir tamanho do ficheiro"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Alterar Permissões"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Ordenar por nome",
|
||||
"sortByDate": "Ordenar por data",
|
||||
"removeCertSign": "Remover assinatura digital no ficheiro junto?",
|
||||
"submit": "Juntar"
|
||||
"submit": "Juntar",
|
||||
"sortBy": {
|
||||
"filename": "Nome do Ficheiro"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operações de página,dividir,Multi Página,cortar,lado servidor",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Documento #6: Página 10"
|
||||
},
|
||||
"splitPages": "Introduza páginas para dividir:",
|
||||
"submit": "Dividir"
|
||||
"submit": "Dividir",
|
||||
"steps": {
|
||||
"settings": "Definições"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Tamanho do Ficheiro"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Tamanho do Ficheiro"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "lado servidor",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Texto",
|
||||
"2": "Imagem"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Texto"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Tamanho da Fonte"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Alterar"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remover páginas,eliminar páginas"
|
||||
"tags": "Remover páginas,eliminar páginas",
|
||||
"title": "Remover",
|
||||
"submit": "Remover"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "seguro,segurança",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Restringe o que pode ser feito com o documento uma vez aberto (Não suportado por todos os leitores)",
|
||||
"16": "Restringe a abertura do próprio documento"
|
||||
},
|
||||
"submit": "Encriptar"
|
||||
"submit": "Encriptar",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Alterar Permissões"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "seguro,Desencriptar,segurança,sem palavra-passe,eliminar palavra-passe",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Selecione PDF para Desencriptar",
|
||||
"2": "Palavra-passe"
|
||||
},
|
||||
"submit": "Remover"
|
||||
"submit": "Remover",
|
||||
"desc": "Remover proteção por palavra-passe do seu documento PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Remover Palavra-passe",
|
||||
"label": "Palavra-passe Atual"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "comprimir,pequeno,minúsculo"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Por favor leia esta documentação sobre como usar isto para outros idiomas e/ou usar fora do docker",
|
||||
"credit": "Este serviço usa qpdf e Tesseract para OCR.",
|
||||
"submit": "Processar PDF com OCR"
|
||||
"submit": "Processar PDF com OCR",
|
||||
"desc": "Limpa digitalizações e deteta texto de imagens dentro de um PDF e readiciona-o como texto.",
|
||||
"settings": {
|
||||
"title": "Definições",
|
||||
"ocrMode": {
|
||||
"label": "Modo OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Idiomas"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Modo OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Idiomas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "imagem,foto,guardar,arquivo,zip,capturar,extrair",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Achatar",
|
||||
"header": "Achatar PDFs",
|
||||
"flattenOnlyForms": "Achatar apenas formulários",
|
||||
"submit": "Achatar"
|
||||
"submit": "Achatar",
|
||||
"steps": {
|
||||
"settings": "Definições"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Achatar apenas formulários"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "corrigir,restaurar,correção,recuperar",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "Usa WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "marcação,conteúdo-web,transformação,converter",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Converter"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informação,dados,estatísticas,estatísticas",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Mostrar Camadas (duplo clique para repor todas as camadas para o estado predefinido)",
|
||||
"colourPicker": "Seletor de Cor",
|
||||
"findCurrentOutlineItem": "Encontrar item atual do esquema",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avançado"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Adicionar"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Páginas",
|
||||
"placeholder": "(ex. 1,2,8 ou 4,7,12-16 ou 2n-1)"
|
||||
},
|
||||
"export": "Exportar"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Extração de Tabela,extrair,converter"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Modo automático - Ajusta automaticamente a qualidade para obter o PDF com o tamanho exato",
|
||||
"5": "Tamanho esperado do PDF (ex. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Comprimir"
|
||||
"submit": "Comprimir",
|
||||
"method": {
|
||||
"filesize": "Tamanho do Ficheiro"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Este ficheiro está protegido por palavra-passe. Por favor introduza a palavra-passe:",
|
||||
@@ -1557,5 +1665,82 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Transferir",
|
||||
"convert": {
|
||||
"title": "Converter",
|
||||
"settings": "Definições",
|
||||
"color": "Cor",
|
||||
"greyscale": "Escala de Cinza",
|
||||
"fillPage": "Preencher Página",
|
||||
"pdfaDigitalSignatureWarning": "O PDF contém uma assinatura digital. Esta será removida no próximo passo.",
|
||||
"grayscale": "Escala de Cinza"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Selecionar Tudo",
|
||||
"deselectAll": "Desselecionar Tudo"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Assinar"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "A carregar...",
|
||||
"or": "ou"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Nome",
|
||||
"fileVersion": "Versão",
|
||||
"selectAll": "Selecionar Tudo",
|
||||
"deselectAll": "Desselecionar Tudo",
|
||||
"deleteSelected": "Eliminar Selecionadas",
|
||||
"download": "Transferir",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Sanitizar PDF",
|
||||
"steps": {
|
||||
"settings": "Definições"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Alterar Permissões",
|
||||
"submit": "Alterar Permissões",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Impedir montagem do documento"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Impedir extração de conteúdo"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Impedir extração para acessibilidade"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Impedir preenchimento de formulário"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Impedir modificação"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Impedir modificação de anotação"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Impedir impressão"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Impedir impressão em diferentes formatos"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Alterar Permissões"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@
|
||||
"scanHelp": "Ajutor pentru Scanarea Dosarului",
|
||||
"deletePrompt": "Sigur doriți să ștergeți pipeline-ul",
|
||||
"tags": "automatizează,secvență,scriptare,procesare-lot",
|
||||
"title": "Pipeline"
|
||||
"title": "Pipeline (Avansat)"
|
||||
},
|
||||
"pipelineOptions": {
|
||||
"header": "Configurare Pipeline",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Convertește"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extrage Pagini"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Elimină",
|
||||
"desc": "Șterge paginile nedorite din documentul PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Elimină imagine",
|
||||
"desc": "Elimină imaginea din PDF pentru a reduce dimensiunea fișierului"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Despărțire Automată după Dimensiune/Număr",
|
||||
"desc": "Împarte un singur PDF în mai multe documente bazat pe dimensiune, număr de pagini sau număr de documente"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Ajustează Culorile/Contrastul",
|
||||
"desc": "Ajustează Contrastul, Saturația și Luminozitatea unui PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Schimbă Permisiunile"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Desparte",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Șterge",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sortează după nume",
|
||||
"sortByDate": "Sortează după dată",
|
||||
"removeCertSign": "Elimină semnătura digitală în fișierul unificat?",
|
||||
"submit": "Unire"
|
||||
"submit": "Unire",
|
||||
"sortBy": {
|
||||
"filename": "Nume Fișier"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operații pagină,divizare,Pagină Multiplă,tăiere,server side",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Documentul #6: Pagina 10"
|
||||
},
|
||||
"splitPages": "Introduceți paginile pe care să le împărțiți:",
|
||||
"submit": "Împarte"
|
||||
"submit": "Împarte",
|
||||
"steps": {
|
||||
"settings": "Setări"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Dimensiune Fișier"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Dimensiune Fișier"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server side",
|
||||
@@ -712,6 +760,9 @@
|
||||
"type": {
|
||||
"1": "Text",
|
||||
"2": "Imagine"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Dimensiune Font"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +785,9 @@
|
||||
"submit": "Schimbă"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Elimină pagini,șterge pagini"
|
||||
"tags": "Elimină pagini,șterge pagini",
|
||||
"title": "Elimină",
|
||||
"submit": "Elimină"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "securizează,securitate",
|
||||
@@ -758,7 +811,12 @@
|
||||
"15": "Restricționează ce se poate face cu documentul odată ce este deschis (Nu este suportat de toate programele de citire)",
|
||||
"16": "Restricționează deschiderea documentului în sine"
|
||||
},
|
||||
"submit": "Criptează"
|
||||
"submit": "Criptează",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Schimbă Permisiunile"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "securizează,Decriptează,securitate,elimină parola,șterge parola",
|
||||
@@ -768,7 +826,12 @@
|
||||
"1": "Selectează PDF-ul pentru decodificare",
|
||||
"2": "Parolă"
|
||||
},
|
||||
"submit": "Elimină"
|
||||
"submit": "Elimină",
|
||||
"desc": "Elimină protecția cu parolă din documentul PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Elimină Parola",
|
||||
"label": "Parola Curentă"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "comprimă,mic,minuscul"
|
||||
@@ -829,7 +892,25 @@
|
||||
},
|
||||
"help": "Citiți documentația pentru a afla cum să utilizați acest serviciu pentru alte limbi și/sau în afara mediului Docker",
|
||||
"credit": "Acest serviciu utilizează qpdf și Tesseract pentru OCR.",
|
||||
"submit": "Procesează PDF-ul cu OCR"
|
||||
"submit": "Procesează PDF-ul cu OCR",
|
||||
"desc": "Curăță scanările și detectează textul din imaginile dintr-un PDF și îl adaugă ca text.",
|
||||
"settings": {
|
||||
"title": "Setări",
|
||||
"ocrMode": {
|
||||
"label": "Mod OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Limbi"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Mod OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Limbi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "poză,fotografie,salvează,arhivă,zip,captură,extrage",
|
||||
@@ -924,7 +1005,7 @@
|
||||
"sharedSigs": "Shared Signatures",
|
||||
"noSavedSigs": "No saved signatures found",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Șterge",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1019,13 @@
|
||||
"title": "Nivelare",
|
||||
"header": "Nivelează documente PDF",
|
||||
"flattenOnlyForms": "Nivelează doar formularele",
|
||||
"submit": "Nivelează"
|
||||
"submit": "Nivelează",
|
||||
"steps": {
|
||||
"settings": "Setări"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Nivelează doar formularele"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "repară,restaurează,corectare,recuperează",
|
||||
@@ -1106,10 +1193,10 @@
|
||||
"credit": "Folosește WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,conținut-web,transformare,convertește",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Convertește"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informații,date,statistici,statistici",
|
||||
@@ -1148,7 +1235,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1261,20 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avansat"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Adaugă"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"placeholder": "(ex. 1,2,8 sau 4,7,12-16 sau 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Extragere Tabel,extrage,convertește"
|
||||
@@ -1261,8 +1361,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Motivul",
|
||||
"location": "Locația",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1381,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Versiune",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1486,10 @@
|
||||
"4": "Mod automat - ajustează automat calitatea pentru a aduce PDF-ul la dimensiunea exactă",
|
||||
"5": "Dimensiunea PDF așteptată (de ex. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Comprimare"
|
||||
"submit": "Comprimare",
|
||||
"method": {
|
||||
"filesize": "Dimensiune Fișier"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1501,7 +1604,7 @@
|
||||
"3": "Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.",
|
||||
"4": "Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs."
|
||||
},
|
||||
"submit": "Split PDF"
|
||||
"submit": "Împarte PDF"
|
||||
},
|
||||
"fileChooser": {
|
||||
"click": "Click",
|
||||
@@ -1557,5 +1660,71 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Descarcă",
|
||||
"convert": {
|
||||
"title": "Convertește",
|
||||
"settings": "Setări",
|
||||
"color": "Culoare",
|
||||
"greyscale": "Scală de gri",
|
||||
"fillPage": "Umple Pagina",
|
||||
"pdfaDigitalSignatureWarning": "PDF-ul conține o semnătură digitală. Aceasta va fi eliminată în pasul următor.",
|
||||
"grayscale": "Scală de gri"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Semnează"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Numele",
|
||||
"fileVersion": "Versiune",
|
||||
"download": "Descarcă",
|
||||
"delete": "Șterge"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Igienizează PDF",
|
||||
"steps": {
|
||||
"settings": "Setări"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Schimbă Permisiunile",
|
||||
"submit": "Schimbă Permisiunile",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Previne asamblarea documentului"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Previne extragerea conținutului"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Previne extragerea pentru accesibilitate"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Previne completarea formularului"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Previne modificarea"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Previne modificarea adnotărilor"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Previne tipărirea"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Previne tipărirea în formate diferite"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Schimbă Permisiunile"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@
|
||||
"multiPdfDropPrompt": "Выберите (или перетащите) все необходимые PDF-файлы",
|
||||
"imgPrompt": "Выберите изображение(я)",
|
||||
"genericSubmit": "Отправить",
|
||||
"uploadLimit": "Maximum file size:",
|
||||
"uploadLimitExceededSingular": "is too large. Maximum allowed size is",
|
||||
"uploadLimitExceededPlural": "are too large. Maximum allowed size is",
|
||||
"uploadLimit": "Максимальный размер файла:",
|
||||
"uploadLimitExceededSingular": "слишком большой. Максимально допустимый размер",
|
||||
"uploadLimitExceededPlural": "слишком большой. Максимально допустимый размер",
|
||||
"processTimeWarning": "Внимание: Данный процесс может занять до минуты в зависимости от размера файла",
|
||||
"pageOrderPrompt": "Пользовательский порядок страниц (Введите список номеров страниц через запятую или функции типа 2n+1):",
|
||||
"pageSelectionPrompt": "Выбор страниц (Введите список номеров страниц через запятую 1,5,6 или функции типа 2n+1):",
|
||||
@@ -118,14 +118,14 @@
|
||||
"addToDoc": "Добавить в документ",
|
||||
"reset": "Сбросить",
|
||||
"apply": "Применить",
|
||||
"noFileSelected": "No file selected. Please upload one.",
|
||||
"noFileSelected": "Файл не выбран. Пожалуйста, загрузите его.",
|
||||
"legal": {
|
||||
"privacy": "Политика конфиденциальности",
|
||||
"terms": "Условия использования",
|
||||
"accessibility": "Доступность",
|
||||
"cookie": "Политика использования файлов cookie",
|
||||
"impressum": "Выходные данные",
|
||||
"showCookieBanner": "Cookie Preferences"
|
||||
"showCookieBanner": "Настройки файлов cookie"
|
||||
},
|
||||
"pipeline": {
|
||||
"header": "Меню конвейера (Beta)",
|
||||
@@ -265,7 +265,7 @@
|
||||
"disabledUsers": "Отключенные пользователи:",
|
||||
"totalUsers": "Всего пользователей:",
|
||||
"lastRequest": "Последний запрос",
|
||||
"usage": "View Usage"
|
||||
"usage": "Просмотр использования"
|
||||
},
|
||||
"endpointStatistics": {
|
||||
"title": "Статистика конечных точек",
|
||||
@@ -320,17 +320,17 @@
|
||||
"desc": "Ваше локальное решение для всех потребностей, связанных с PDF.",
|
||||
"searchBar": "Поиск функций...",
|
||||
"viewPdf": {
|
||||
"title": "View/Edit PDF",
|
||||
"title": "Смотреть/Редактировать PDF",
|
||||
"desc": "Просмотр, аннотирование, добавление текста или изображений"
|
||||
},
|
||||
"setFavorites": "Set Favourites",
|
||||
"hideFavorites": "Hide Favourites",
|
||||
"showFavorites": "Show Favourites",
|
||||
"legacyHomepage": "Old homepage",
|
||||
"newHomePage": "Try our new homepage!",
|
||||
"alphabetical": "Alphabetical",
|
||||
"globalPopularity": "Global Popularity",
|
||||
"sortBy": "Sort by:",
|
||||
"setFavorites": "Добавить в избранное",
|
||||
"hideFavorites": "Скрыть из избранного",
|
||||
"showFavorites": "Показать избранное",
|
||||
"legacyHomepage": "Старый вид главной страницы",
|
||||
"newHomePage": "Попробуйте нашу новую главную страницу!",
|
||||
"alphabetical": "Алфавиту",
|
||||
"globalPopularity": "Популярности",
|
||||
"sortBy": "Сортировать по:",
|
||||
"multiTool": {
|
||||
"title": "Мультиинструмент PDF",
|
||||
"desc": "Объединение, поворот, переупорядочивание и удаление страниц"
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "Сжимайте PDF-файлы для уменьшения их размера."
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "Разблокировать PDF-формы",
|
||||
"desc": "Удалите свойство 'только для чтения' для полей формы в PDF-документа."
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "Изменить метаданные",
|
||||
@@ -578,11 +578,48 @@
|
||||
"replace-color": {
|
||||
"title": "Замена и инверсия цвета",
|
||||
"desc": "Заменяет цвет текста и фона в PDF и инвертирует все цвета PDF для уменьшения размера файла"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Преобразовать"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Добавлять вложения",
|
||||
"desc": "Добавление или удаление встроенных файлов (вложений) в PDF-файл или из него"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "Редактир оглавления",
|
||||
"desc": "Добавление или редактирование закладок и оглавления в PDF-документах"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Извлечь страницы"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Удалить",
|
||||
"desc": "Удалите ненужные страницы из вашего PDF-документа."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Удалить изображение",
|
||||
"desc": "Удаляет изображения из PDF для уменьшения размера файла"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Авторазделение по размеру/количеству",
|
||||
"desc": "Разделяет один PDF на несколько документов на основе размера, количества страниц или количества документов"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Настройка цветов/контраста",
|
||||
"desc": "Настройка контраста, насыщенности и яркости PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Замена и инверсия цвета",
|
||||
"desc": "Заменяет цвет текста и фона в PDF и инвертирует все цвета PDF для уменьшения размера файла"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Изменить разрешения"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
"tags": "просмотр,чтение,аннотации,текст,изображение",
|
||||
"title": "View/Edit PDF",
|
||||
"title": "Смотреть/Редактировать PDF",
|
||||
"header": "Просмотр PDF"
|
||||
},
|
||||
"multiTool": {
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "Сортировать по имени",
|
||||
"sortByDate": "Сортировать по дате",
|
||||
"removeCertSign": "Удалить цифровую подпись в объединенном файле?",
|
||||
"submit": "Объединить"
|
||||
"submit": "Объединить",
|
||||
"sortBy": {
|
||||
"filename": "Имя файла"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "операции со страницами,разделение,многостраничный,вырезание,серверная часть",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "Документ #6: Страница 10"
|
||||
},
|
||||
"splitPages": "Введите страницы для разделения:",
|
||||
"submit": "Разделить"
|
||||
"submit": "Разделить",
|
||||
"steps": {
|
||||
"settings": "Настройки"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Размер файла"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Размер файла"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "серверная часть",
|
||||
@@ -679,7 +732,7 @@
|
||||
"8": "Удалить последнюю",
|
||||
"9": "Удалить первую и последнюю",
|
||||
"10": "Объединение четных-нечетных",
|
||||
"11": "Duplicate all pages"
|
||||
"11": "Дублировать все страницы"
|
||||
},
|
||||
"placeholder": "(например, 1,3,2 или 4-8,2,10-12 или 2n-1)"
|
||||
},
|
||||
@@ -712,6 +765,12 @@
|
||||
"type": {
|
||||
"1": "Текст",
|
||||
"2": "Изображение"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Текст"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Размер шрифта"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +793,9 @@
|
||||
"submit": "Изменить"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Удалить страницы,удаление страниц"
|
||||
"tags": "Удалить страницы,удаление страниц",
|
||||
"title": "Удалить",
|
||||
"submit": "Удалить"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "безопасность,защита",
|
||||
@@ -758,7 +819,12 @@
|
||||
"15": "Ограничивает действия с документом после его открытия (Поддерживается не всеми программами чтения)",
|
||||
"16": "Ограничивает само открытие документа"
|
||||
},
|
||||
"submit": "Зашифровать"
|
||||
"submit": "Зашифровать",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Изменить разрешения"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "безопасность,расшифровка,защита,удаление пароля",
|
||||
@@ -768,15 +834,20 @@
|
||||
"1": "Выберите PDF для расшифровки",
|
||||
"2": "Пароль"
|
||||
},
|
||||
"submit": "Удалить"
|
||||
"submit": "Удалить",
|
||||
"desc": "Удалите защиту паролем из вашего PDF-документа.",
|
||||
"password": {
|
||||
"stepTitle": "Удалить пароль",
|
||||
"label": "Текущий пароль"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "сжатие,маленький,крошечный"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"tags": "удалить,форма,поле,только для чтения",
|
||||
"title": "Удалить поля формы, доступные только для чтения",
|
||||
"header": "Разблокировать PDF-формы",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -797,7 +868,7 @@
|
||||
"modDate": "Дата изменения (yyyy/MM/dd HH:mm:ss):",
|
||||
"producer": "Производитель:",
|
||||
"subject": "Тема:",
|
||||
"trapped": "Trapped:",
|
||||
"trapped": "Захвачен:",
|
||||
"submit": "Изменить"
|
||||
},
|
||||
"fileToPDF": {
|
||||
@@ -829,7 +900,25 @@
|
||||
},
|
||||
"help": "Пожалуйста, прочтите эту документацию о том, как использовать это для других языков и/или использовать не в Docker",
|
||||
"credit": "Этот сервис использует qpdf и Tesseract для OCR.",
|
||||
"submit": "Обработать PDF с OCR"
|
||||
"submit": "Обработать PDF с OCR",
|
||||
"desc": "Очистка сканов и распознавание текста с изображений в PDF с последующим добавлением его как текст.",
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"ocrMode": {
|
||||
"label": "Режим OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Языки"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Режим OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Языки"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "картинка,фото,сохранение,архив,zip,захват,извлечение",
|
||||
@@ -870,7 +959,7 @@
|
||||
"submit": "Преобразовать"
|
||||
},
|
||||
"PDFToText": {
|
||||
"tags": "richformat,richtextformat,rich text format",
|
||||
"tags": "rtf,richformat,richtextformat,rich text format",
|
||||
"title": "PDF в RTF (текст)",
|
||||
"header": "PDF в RTF (текст)",
|
||||
"selectText": {
|
||||
@@ -930,15 +1019,21 @@
|
||||
"next": "Следующая страница",
|
||||
"previous": "Предыдущая страница",
|
||||
"maintainRatio": "Переключить сохранение пропорций",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
"undo": "Отменить",
|
||||
"redo": "Повторить"
|
||||
},
|
||||
"flatten": {
|
||||
"tags": "статический,деактивация,неинтерактивный,упрощение",
|
||||
"title": "Сведение",
|
||||
"header": "Сведение PDF",
|
||||
"flattenOnlyForms": "Свести только формы",
|
||||
"submit": "Свести"
|
||||
"submit": "Свести",
|
||||
"steps": {
|
||||
"settings": "Настройки"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Свести только формы"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "исправление,восстановление,коррекция,возврат",
|
||||
@@ -1106,10 +1201,10 @@
|
||||
"credit": "Использует WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"tags": "разметка,веб-контент,преобразование,конвертацтя,md",
|
||||
"title": "PDF в Markdown",
|
||||
"header": "PDF в Markdown",
|
||||
"submit": "Преобразовать"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "информация,данные,статистика,параметры",
|
||||
@@ -1125,7 +1220,7 @@
|
||||
"tags": "одна страница"
|
||||
},
|
||||
"showJS": {
|
||||
"tags": "JS",
|
||||
"tags": "JS,Javascript",
|
||||
"title": "Показать Javascript",
|
||||
"header": "Показать Javascript",
|
||||
"downloadJS": "Скачать Javascript",
|
||||
@@ -1174,7 +1269,22 @@
|
||||
"showLayers": "Показать слои (двойной щелчок для сброса всех слоев к состоянию по умолчанию)",
|
||||
"colourPicker": "Выбор цвета",
|
||||
"findCurrentOutlineItem": "Найти текущий элемент структуры",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Расширенные"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Добавить"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Страницы",
|
||||
"placeholder": "(например, 1,2,8 или 4,7,12-16 или 2n-1)"
|
||||
},
|
||||
"export": "Экспорт"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Извлечение таблиц,извлечение,конвертация"
|
||||
@@ -1336,7 +1446,7 @@
|
||||
"alreadyLoggedIn": "Вы уже вошли в",
|
||||
"alreadyLoggedIn2": "устройств(а). Пожалуйста, выйдите из этих устройств и попробуйте снова.",
|
||||
"toManySessions": "У вас слишком много активных сессий",
|
||||
"logoutMessage": "You have been logged out."
|
||||
"logoutMessage": "Вы вышли из системы."
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF в одну страницу",
|
||||
@@ -1355,10 +1465,10 @@
|
||||
"selectText": {
|
||||
"1": "Удалить JavaScript-действия",
|
||||
"2": "Удалить встроенные файлы",
|
||||
"3": "Remove XMP metadata",
|
||||
"3": "Удалить XMP метаданные",
|
||||
"4": "Удалить ссылки",
|
||||
"5": "Удалить шрифты",
|
||||
"6": "Remove Document Info Metadata"
|
||||
"6": "Удалить метаданные с информацией о документе"
|
||||
},
|
||||
"submit": "Очистить PDF"
|
||||
},
|
||||
@@ -1380,13 +1490,16 @@
|
||||
"selectText": {
|
||||
"1": {
|
||||
"_value": "Compression Settings",
|
||||
"1": "1-3 PDF compression,</br> 4-6 lite image compression,</br> 7-9 intense image compression Will dramatically reduce image quality"
|
||||
"1": "1-3 сжатие PDF,</br> 4-6 лёгкое сжатие изображений,</br> 7-9 интенсивное сжатие изображений (значительно снижает качество изображений)"
|
||||
},
|
||||
"2": "Уровень оптимизации:",
|
||||
"4": "Автоматический режим - автоматически настраивает качество для получения точного размера PDF",
|
||||
"5": "Ожидаемый размер PDF (например, 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Сжать"
|
||||
"submit": "Сжать",
|
||||
"method": {
|
||||
"filesize": "Размер файла"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Этот файл защищен паролем. Пожалуйста, введите пароль:",
|
||||
@@ -1472,15 +1585,15 @@
|
||||
"button": "Пройти опрос",
|
||||
"dontShowAgain": "Больше не показывать",
|
||||
"meeting": {
|
||||
"1": "If you're using Stirling PDF at work, we'd love to speak to you. We're offering technical support sessions in exchange for a 15 minute user discovery session.",
|
||||
"2": "This is a chance to:",
|
||||
"3": "Get help with deployment, integrations, or troubleshooting",
|
||||
"4": "Provide direct feedback on performance, edge cases, and feature gaps",
|
||||
"5": "Help us refine Stirling PDF for real-world enterprise use",
|
||||
"6": "If you're interested, you can book time with our team directly. (English speaking only)",
|
||||
"7": "Looking forward to digging into your use cases and making Stirling PDF even better!",
|
||||
"notInterested": "Not a business and/or interested in a meeting?",
|
||||
"button": "Book meeting"
|
||||
"1": "Если вы используете Stirling PDF в своей работе, мы будем рады с вами пообщаться. Мы предлагаем сеансы технической поддержки в обмен на 15-минутный сеанс знакомства с пользователями.",
|
||||
"2": "Это возможность:",
|
||||
"3": "Получить помощь в развертывании, интеграции или устранении неполадок",
|
||||
"4": "Предоставляйте прямые отзывы о производительности, передовых решениях и недостатках в функциях",
|
||||
"5": "Помогите нам усовершенствовать Stirling PDF для использования в реальных корпоративных условиях",
|
||||
"6": "Если вы заинтересованы, вы можете забронировать время у нашей команды напрямую. (Только для говорящих по-английски)",
|
||||
"7": "С нетерпением ждем возможности изучить ваши варианты использования и сделать Stirling PDF еще лучше!",
|
||||
"notInterested": "Не занимаетесь бизнесом и/или не заинтересованы во встрече?",
|
||||
"button": "Забронировать встречу"
|
||||
}
|
||||
},
|
||||
"removeImage": {
|
||||
@@ -1523,39 +1636,117 @@
|
||||
},
|
||||
"cookieBanner": {
|
||||
"popUp": {
|
||||
"title": "How we use Cookies",
|
||||
"title": "Как мы используем файлы cookie",
|
||||
"description": {
|
||||
"1": "We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.",
|
||||
"1": "Мы используем файлы cookie и другие технологии, чтобы улучшить работу Stirling PDF для вас, это помогает нам совершенствовать наши инструменты и создавать функции, которые вам понравятся.",
|
||||
"2": "If you’d rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
|
||||
},
|
||||
"acceptAllBtn": "Okay",
|
||||
"acceptNecessaryBtn": "No Thanks",
|
||||
"showPreferencesBtn": "Manage preferences"
|
||||
"acceptAllBtn": "Хорошо",
|
||||
"acceptNecessaryBtn": "Нет, спасибо",
|
||||
"showPreferencesBtn": "Управление предпочтениями"
|
||||
},
|
||||
"preferencesModal": {
|
||||
"title": "Consent Preferences Center",
|
||||
"acceptAllBtn": "Accept all",
|
||||
"acceptNecessaryBtn": "Reject all",
|
||||
"savePreferencesBtn": "Save preferences",
|
||||
"closeIconLabel": "Close modal",
|
||||
"serviceCounterLabel": "Service|Services",
|
||||
"subtitle": "Cookie Usage",
|
||||
"title": "Центр согласия предпочтений",
|
||||
"acceptAllBtn": "Принять все",
|
||||
"acceptNecessaryBtn": "Отклонить все",
|
||||
"savePreferencesBtn": "Сохранить предпочтения",
|
||||
"closeIconLabel": "Закрыть модальное окно",
|
||||
"serviceCounterLabel": "Сервис|Услуги",
|
||||
"subtitle": "Использование файлов cookie",
|
||||
"description": {
|
||||
"1": "Stirling PDF uses cookies and similar technologies to enhance your experience and understand how our tools are used. This helps us improve performance, develop the features you care about, and provide ongoing support to our users.",
|
||||
"2": "Stirling PDF cannot—and will never—track or access the content of the documents you use.",
|
||||
"3": "Your privacy and trust are at the core of what we do."
|
||||
"1": "Stirling PDF использует файлы cookie и аналогичные технологии, чтобы улучшить ваш опыт и понять, как используются наши инструменты. Это помогает нам повышать производительность, разрабатывать функции, которые вам интересны, и обеспечивать постоянную поддержку наших пользователей.",
|
||||
"2": "Stirling PDF не может — и никогда не будет — отслеживать или получать доступ к содержимому используемых вами документов.",
|
||||
"3": "Ваша конфиденциальность и доверие лежат в основе того, что мы делаем."
|
||||
},
|
||||
"necessary": {
|
||||
"title": {
|
||||
"1": "Strictly Necessary Cookies",
|
||||
"2": "Always Enabled"
|
||||
"1": "Строго необходимые файлы cookie",
|
||||
"2": "Всегда включены"
|
||||
},
|
||||
"description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can’t be turned off."
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytics",
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
"title": "Аналитика",
|
||||
"description": "Эти файлы cookie помогают нам понять, как используются наши инструменты, чтобы мы могли сосредоточиться на создании функций, которые больше всего ценятся нашим сообществом. Будьте уверены — Stirling PDF не может и никогда не будет отслеживать содержание документов, с которыми вы работаете."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Скачать",
|
||||
"undo": "Отменить",
|
||||
"convert": {
|
||||
"title": "Преобразовать",
|
||||
"settings": "Настройки",
|
||||
"color": "Цвет",
|
||||
"greyscale": "Оттенки серого",
|
||||
"fillPage": "Заполнить страницу",
|
||||
"pdfaDigitalSignatureWarning": "PDF содержит цифровую подпись. Она будет удалена на следующем шаге.",
|
||||
"grayscale": "Оттенки серого"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "вставлять,прикреплять,файл,вложение,вложения",
|
||||
"title": "Добавлять вложения",
|
||||
"header": "Добавлять вложения",
|
||||
"submit": "Добавлять вложения"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Выбрать все",
|
||||
"deselectAll": "Отменить выбор всех"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Подпись"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Загрузка...",
|
||||
"or": "или"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Имя",
|
||||
"fileVersion": "Версия",
|
||||
"selectAll": "Выбрать все",
|
||||
"deselectAll": "Отменить выбор всех",
|
||||
"deleteSelected": "Удалить выбранные",
|
||||
"download": "Скачать",
|
||||
"delete": "Удалить"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Очистить PDF",
|
||||
"steps": {
|
||||
"settings": "Настройки"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Изменить разрешения",
|
||||
"submit": "Изменить разрешения",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Запретить компоновку документа"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Запретить извлечение содержимого"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Запретить извлечение для специальных возможностей"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Запретить заполнение форм"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Запретить модификацию"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Запретить модификацию аннотаций"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Запретить печать"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Запретить печать в высоком качестве"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Изменить разрешения"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@
|
||||
"search": "Search",
|
||||
"sections": {
|
||||
"organize": "Organize",
|
||||
"convertTo": "Convert to PDF",
|
||||
"convertTo": "Konvertovať do PDF",
|
||||
"convertFrom": "Convert from PDF",
|
||||
"security": "Sign & Security",
|
||||
"advance": "Advanced",
|
||||
@@ -332,7 +332,7 @@
|
||||
"globalPopularity": "Global Popularity",
|
||||
"sortBy": "Sort by:",
|
||||
"multiTool": {
|
||||
"title": "PDF Multi Tool",
|
||||
"title": "PDF Multi Nástroj",
|
||||
"desc": "Zlúčiť, otočiť, preusporiadať a odstrániť stránky"
|
||||
},
|
||||
"merge": {
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konvertovať"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extrahovať stránky"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Odstrániť",
|
||||
"desc": "Odstrániť nechcené stránky z vášho PDF dokumentu."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Remove image",
|
||||
"desc": "Remove image from PDF to reduce file size"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Automatické rozdelenie podľa veľkosti/počtu",
|
||||
"desc": "Rozdelí jeden PDF na viacero dokumentov na základe veľkosti, počtu stránok alebo počtu dokumentov"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Upraviť farby/kontrast",
|
||||
"desc": "Upravte kontrast, sýtosť a jas PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Zmeniť povolenia"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Rozdeliť",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Vymazať",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -634,7 +666,10 @@
|
||||
"8": "Dokument č. 6: Strana 10"
|
||||
},
|
||||
"splitPages": "Zadajte stránky na rozdelenie:",
|
||||
"submit": "Rozdeliť"
|
||||
"submit": "Rozdeliť",
|
||||
"steps": {
|
||||
"settings": "Nastavenia"
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "beží na serveri",
|
||||
@@ -734,7 +769,9 @@
|
||||
"submit": "Zmeniť"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Odstrániť stránky,vymazať stránky"
|
||||
"tags": "Odstrániť stránky,vymazať stránky",
|
||||
"title": "Odstrániť",
|
||||
"submit": "Odstrániť"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "zaistiť,bezpečnosť",
|
||||
@@ -758,7 +795,12 @@
|
||||
"15": "Obmedzuje, čo môže byť vykonané s dokumentom po jeho otvorení (Nepodporované všetkými čítačmi)",
|
||||
"16": "Obmedzuje samotné otvorenie dokumentu"
|
||||
},
|
||||
"submit": "Zašifrovať"
|
||||
"submit": "Zašifrovať",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Zmeniť povolenia"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "zaistiť,Dešifrovať,bezpečnosť,odheslovať,vymazať heslo",
|
||||
@@ -768,7 +810,12 @@
|
||||
"1": "Vyberte PDF na dešifrovanie",
|
||||
"2": "Heslo"
|
||||
},
|
||||
"submit": "Odstrániť"
|
||||
"submit": "Odstrániť",
|
||||
"desc": "Odstrániť ochranu heslom z vášho PDF dokumentu.",
|
||||
"password": {
|
||||
"stepTitle": "Odstrániť heslo",
|
||||
"label": "Aktuálne heslo"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "stlačiť,malé,drobné"
|
||||
@@ -829,7 +876,19 @@
|
||||
},
|
||||
"help": "Prosím, prečítajte si túto dokumentáciu o tom, ako používať OCR pre iné jazyky a/alebo použitie mimo docker",
|
||||
"credit": "Táto služba používa qpdf a Tesseract pre OCR.",
|
||||
"submit": "Spracovať PDF s OCR"
|
||||
"submit": "Spracovať PDF s OCR",
|
||||
"desc": "Čistenie skenov a rozpoznanie textu z obrázkov v PDF a opätovné pridanie ako text.",
|
||||
"settings": {
|
||||
"title": "Nastavenia",
|
||||
"ocrMode": {
|
||||
"label": "OCR režim"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR režim"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "obrázok,fotografia,uložiť,archív,zip,zachytiť,chytiť",
|
||||
@@ -924,7 +983,7 @@
|
||||
"sharedSigs": "Shared Signatures",
|
||||
"noSavedSigs": "No saved signatures found",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Vymazať",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +997,13 @@
|
||||
"title": "Zploštiť",
|
||||
"header": "Zploštiť PDF",
|
||||
"flattenOnlyForms": "Zploštiť iba formuláre",
|
||||
"submit": "Zploštiť"
|
||||
"submit": "Zploštiť",
|
||||
"steps": {
|
||||
"settings": "Nastavenia"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Zploštiť iba formuláre"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "opraviť,obnoviť,oprava,obnovenie",
|
||||
@@ -1106,10 +1171,10 @@
|
||||
"credit": "Používa WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup, webový obsah, transformácia, konvertovať",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Konvertovať"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "informácie, údaje, štatistiky",
|
||||
@@ -1148,7 +1213,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1239,17 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"wordsToRedact": {
|
||||
"add": "Pridať"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"placeholder": "(napr. 1,2,8 alebo 4,7,12-16 alebo 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, extrakcia tabuliek, extrahovať, konvertovať"
|
||||
@@ -1261,8 +1336,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Dôvod",
|
||||
"location": "Miesto",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1356,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Verzia",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1501,7 +1576,7 @@
|
||||
"3": "Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.",
|
||||
"4": "Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs."
|
||||
},
|
||||
"submit": "Split PDF"
|
||||
"submit": "Rozdeliť PDF"
|
||||
},
|
||||
"fileChooser": {
|
||||
"click": "Click",
|
||||
@@ -1557,5 +1632,70 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Stiahnuť",
|
||||
"convert": {
|
||||
"title": "Konvertovať",
|
||||
"settings": "Nastavenia",
|
||||
"color": "Farba",
|
||||
"greyscale": "Odtiene šedej",
|
||||
"fillPage": "Vyplniť stránku",
|
||||
"grayscale": "Odtiene šedej"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Podpísať"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Meno",
|
||||
"fileVersion": "Verzia",
|
||||
"download": "Stiahnuť",
|
||||
"delete": "Vymazať"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Vyčistiť PDF",
|
||||
"steps": {
|
||||
"settings": "Nastavenia"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Zmeniť povolenia",
|
||||
"submit": "Zmeniť povolenia",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Zakázať zostavovanie dokumentu"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Zakázať extrakciu obsahu"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Zakázať extrakciu pre prístupnosť"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Zakázať vypĺňanie formulárov"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Zakázať úpravy"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Zakázať úpravu anotácií"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Zakázať tlač"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Zakázať tlač rôznych formátov"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Zmeniť povolenia"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +332,7 @@
|
||||
"globalPopularity": "Globalna priljubljenost",
|
||||
"sortBy": "Razvrsti po:",
|
||||
"multiTool": {
|
||||
"title": "PDF Multi Tool",
|
||||
"title": "PDF večnamensko orodje",
|
||||
"desc": "Spoji, zavrti, prerazporedi, razdeli in odstrani strani"
|
||||
},
|
||||
"merge": {
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Napredne barvne možnosti",
|
||||
"desc": "Zamenjaj barvo besedila in ozadja v PDF-ju in obrni celotno barvo PDF-ja, da zmanjšaš velikost datoteke"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Pretvori"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Izvleči strani"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Odstrani",
|
||||
"desc": "Izbrišite neželene strani iz dokumenta PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Odstrani sliko",
|
||||
"desc": "Odstranite sliko iz PDF-ja, da zmanjšate velikost datoteke"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Samodejna razdelitev po velikosti/številu",
|
||||
"desc": "Razdeli en PDF na več dokumentov glede na velikost, število strani ali število dokumentov"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Prilagodi barve/kontrast",
|
||||
"desc": "Prilagodi kontrast, nasičenost in svetlost PDF-ja"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Napredne barvne možnosti",
|
||||
"desc": "Zamenjaj barvo besedila in ozadja v PDF-ju in obrni celotno barvo PDF-ja, da zmanjšaš velikost datoteke"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Spremeni dovoljenja"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -588,7 +620,7 @@
|
||||
"multiTool": {
|
||||
"tags": "Več orodij, več operacij, uporabniški vmesnik, klik povleci, sprednji del, odjemalska stran, interaktivno, nepremagljivo, premakni, izbriši, preseli, razdeli",
|
||||
"title": "PDF večnamensko orodje",
|
||||
"header": "PDF Multi Tool",
|
||||
"header": "PDF večnamensko orodje",
|
||||
"uploadPrompts": "Ime datoteke",
|
||||
"selectAll": "Izberi vse",
|
||||
"deselectAll": "Prekliči izbor vseh",
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Razvrsti po imenu",
|
||||
"sortByDate": "Razvrsti po datumu",
|
||||
"removeCertSign": "Odstraniti digitalni podpis v združeni datoteki?",
|
||||
"submit": "Združi"
|
||||
"submit": "Združi",
|
||||
"sortBy": {
|
||||
"filename": "Ime datoteke"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Operacije strani,deli,Multi Page,cut,strežniška stran",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: stran 10"
|
||||
},
|
||||
"splitPages": "Vnesite strani za razdelitev:",
|
||||
"submit": "Razdeli"
|
||||
"submit": "Razdeli",
|
||||
"steps": {
|
||||
"settings": "Nastavitve"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Velikost datoteke"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Velikost datoteke"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "strežniška stran",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Besedilo",
|
||||
"2": "Slika"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Besedilo"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Velikost pisave"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Spremeni"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Odstrani strani, izbriši strani"
|
||||
"tags": "Odstrani strani, izbriši strani",
|
||||
"title": "Odstrani",
|
||||
"submit": "Odstrani"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "varno,varnost",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Omejuje, kaj je mogoče storiti z dokumentom, ko je odprt (ni podprt za vse bralnike)",
|
||||
"16": "Omeji odpiranje samega dokumenta"
|
||||
},
|
||||
"submit": "Šifriraj"
|
||||
"submit": "Šifriraj",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Spremeni dovoljenja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "varno,dešifriranje,varnost,odstranitev gesla,brisanje gesla",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Izberite PDF za dešifriranje",
|
||||
"2": "Geslo"
|
||||
},
|
||||
"submit": "Odstrani"
|
||||
"submit": "Odstrani",
|
||||
"desc": "Odstranite zaščito z geslom iz vašega dokumenta PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Odstrani geslo",
|
||||
"label": "Trenutno geslo"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Prosimo, preberite to dokumentacijo o uporabi tega za druge jezike in/ali uporabi ne v dockerju",
|
||||
"credit": "Ta storitev uporablja qpdf in Tesseract za OCR.",
|
||||
"submit": "Obdelaj PDF z OCR"
|
||||
"submit": "Obdelaj PDF z OCR",
|
||||
"desc": "Cleanup skenira in zazna besedilo iz slik znotraj PDF-ja in ga ponovno doda kot besedilo.",
|
||||
"settings": {
|
||||
"title": "Nastavitve",
|
||||
"ocrMode": {
|
||||
"label": "Način OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Jeziki"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Način OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Jeziki"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "slika,fotografija,shrani,arhiv,zip,zajemi,zgrabi",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Zravnaj",
|
||||
"header": "Zravnaj PDF",
|
||||
"flattenOnlyForms": "Splošči samo obrazce",
|
||||
"submit": "Zravnaj"
|
||||
"submit": "Zravnaj",
|
||||
"steps": {
|
||||
"settings": "Nastavitve"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Splošči samo obrazce"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "popravi,obnovi,popravi,obnovi",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Prikaži plasti (dvokliknite za ponastavitev vseh plasti na privzeto stanje)",
|
||||
"colourPicker": "Izbirnik barv",
|
||||
"findCurrentOutlineItem": "Poišči trenutno postavko orisa",
|
||||
"applyChanges": "Uporabi spremembe"
|
||||
"applyChanges": "Uporabi spremembe",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Napredno"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Dodaj"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Strani",
|
||||
"placeholder": "(npr. 1,2,8 ali 4,7,12-16 ali 2n-1)"
|
||||
},
|
||||
"export": "Izvozi"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, ekstrakcija tabele, ekstrah, pretvorba"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Samodejni način - Samodejno prilagodi kakovost, da dobi PDF na natančno velikost",
|
||||
"5": "Pričakovana velikost PDF (npr. 25 MB, 10,8 MB, 25 KB)"
|
||||
},
|
||||
"submit": "Stisnite"
|
||||
"submit": "Stisnite",
|
||||
"method": {
|
||||
"filesize": "Velikost datoteke"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Ta datoteka je zaščitena z geslom. Prosim vnesite geslo:",
|
||||
@@ -1557,5 +1665,83 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Prenos",
|
||||
"undo": "Razveljavi",
|
||||
"convert": {
|
||||
"title": "Pretvori",
|
||||
"settings": "Nastavitve",
|
||||
"color": "Barva",
|
||||
"greyscale": "Sivine",
|
||||
"fillPage": "Izpolni stran",
|
||||
"pdfaDigitalSignatureWarning": "PDF vsebuje digitalni podpis. To bo odstranjeno v naslednjem koraku.",
|
||||
"grayscale": "Sivine"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Izberi vse",
|
||||
"deselectAll": "Prekliči izbor vseh"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Podpiši"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Nalaganje...",
|
||||
"or": "ali"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Ime",
|
||||
"fileVersion": "Različica",
|
||||
"selectAll": "Izberi vse",
|
||||
"deselectAll": "Prekliči izbor vseh",
|
||||
"deleteSelected": "Izbriši izbrano",
|
||||
"download": "Prenos",
|
||||
"delete": "Izbriši"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Prečisti PDF",
|
||||
"steps": {
|
||||
"settings": "Nastavitve"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Spremeni dovoljenja",
|
||||
"submit": "Spremeni dovoljenja",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Prepreči sestavljanje dokumenta"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Prepreči ekstrakcijo vsebine"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Prepreči ekstrakcijo za dostopnost"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Prepreči izpolnjevanje obrazca"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Prepreči spreminjanje"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Prepreči spreminjanje pripisov"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Prepreči tiskanje"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Prepreči tiskanje različnih formatov"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Spremeni dovoljenja"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -137,7 +137,7 @@
|
||||
"scanHelp": "Hjälp för mappskanning",
|
||||
"deletePrompt": "Är du säker på att du vill ta bort pipeline",
|
||||
"tags": "automatisera,sekvens,skriptad,batchprocess",
|
||||
"title": "Pipeline"
|
||||
"title": "Pipeline (Avancerat)"
|
||||
},
|
||||
"pipelineOptions": {
|
||||
"header": "Pipeline-konfiguration",
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "Laddar...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Ersätt och Invertera färg",
|
||||
"desc": "Ersätt färg för text och bakgrund i PDF och invertera hela färgen på PDF för att minska filstorlek"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Konvertera"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Extrahera sidor"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Ta bort",
|
||||
"desc": "Ta bort oönskade sidor från ditt PDF-dokument."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Ta bort bild",
|
||||
"desc": "Ta bort bild från PDF för att minska filstorlek"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Auto-dela efter storlek/antal",
|
||||
"desc": "Dela en enda PDF till flera dokument baserat på storlek, sidantal eller dokumentantal"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Justera färger/kontrast",
|
||||
"desc": "Justera kontrast, mättnad och ljusstyrka i en PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Ersätt och Invertera färg",
|
||||
"desc": "Ersätt färg för text och bakgrund i PDF och invertera hela färgen på PDF för att minska filstorlek"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Ändra behörigheter"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sortera efter namn",
|
||||
"sortByDate": "Sortera efter datum",
|
||||
"removeCertSign": "Ta bort digital signatur i den sammanslagna filen?",
|
||||
"submit": "Slå samman"
|
||||
"submit": "Slå samman",
|
||||
"sortBy": {
|
||||
"filename": "Filnamn"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Sidoperationer,dela,Multisida,klippa,serversida",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Dokument #6: Sida 10"
|
||||
},
|
||||
"splitPages": "Ange sidor att dela på:",
|
||||
"submit": "Dela"
|
||||
"submit": "Dela",
|
||||
"steps": {
|
||||
"settings": "Inställningar"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Filstorlek"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Filstorlek"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "serversida",
|
||||
@@ -712,6 +760,9 @@
|
||||
"type": {
|
||||
"1": "Text",
|
||||
"2": "Bild"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Teckenstorlek"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +785,9 @@
|
||||
"submit": "Ändra"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Ta bort sidor,radera sidor"
|
||||
"tags": "Ta bort sidor,radera sidor",
|
||||
"title": "Ta bort",
|
||||
"submit": "Ta bort"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "säkra,säkerhet",
|
||||
@@ -758,7 +811,12 @@
|
||||
"15": "Begränsar vad som kan göras med dokumentet när det väl är öppnat (Stöds inte av alla läsare)",
|
||||
"16": "Begränsar öppnandet av själva dokumentet"
|
||||
},
|
||||
"submit": "Kryptera"
|
||||
"submit": "Kryptera",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Ändra behörigheter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "säkra,Dekryptera,säkerhet,ta bort lösenord,radera lösenord",
|
||||
@@ -768,7 +826,12 @@
|
||||
"1": "Välj PDF att dekryptera",
|
||||
"2": "Lösenord"
|
||||
},
|
||||
"submit": "Ta bort"
|
||||
"submit": "Ta bort",
|
||||
"desc": "Ta bort lösenordsskydd från ditt PDF-dokument.",
|
||||
"password": {
|
||||
"stepTitle": "Ta bort lösenord",
|
||||
"label": "Nuvarande lösenord"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "pressa ihop,liten,minimal"
|
||||
@@ -829,7 +892,25 @@
|
||||
},
|
||||
"help": "Vänligen läs denna dokumentation om hur du använder detta för andra språk och/eller använder inte i docker",
|
||||
"credit": "Denna tjänst använder qpdf och Tesseract för OCR.",
|
||||
"submit": "Bearbeta PDF med OCR"
|
||||
"submit": "Bearbeta PDF med OCR",
|
||||
"desc": "Rengör skanningar och upptäcker text från bilder i en PDF och lägger till den igen som text.",
|
||||
"settings": {
|
||||
"title": "Inställningar",
|
||||
"ocrMode": {
|
||||
"label": "OCR-läge"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Språk"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR-läge"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Språk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "bild,foto,spara,arkiv,zip,fånga,ta",
|
||||
@@ -938,7 +1019,13 @@
|
||||
"title": "Platta till",
|
||||
"header": "Platta till PDF-filer",
|
||||
"flattenOnlyForms": "Platta till endast formulär",
|
||||
"submit": "Platta till"
|
||||
"submit": "Platta till",
|
||||
"steps": {
|
||||
"settings": "Inställningar"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Platta till endast formulär"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fixa,återställa,korrigering,återhämta",
|
||||
@@ -1106,7 +1193,7 @@
|
||||
"credit": "Använder WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "markup,webbinnehåll,transformation,konvertera",
|
||||
"title": "PDF Till Markdown",
|
||||
"header": "PDF Till Markdown",
|
||||
"submit": "Konvertera"
|
||||
@@ -1148,7 +1235,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1261,22 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Avancerat"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Lägg till"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Sidor",
|
||||
"placeholder": "(t.ex. 1,2,8 eller 4,7,12-16 eller 2n-1)"
|
||||
},
|
||||
"export": "Exportera"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Tabellextraktion,extrahera,konvertera"
|
||||
@@ -1386,7 +1488,10 @@
|
||||
"4": "Autoläge - Autojusterar kvaliteten för att få PDF till exakt storlek",
|
||||
"5": "Förväntad PDF-storlek (t.ex. 25MB, 10,8MB, 25KB)"
|
||||
},
|
||||
"submit": "Komprimera"
|
||||
"submit": "Komprimera",
|
||||
"method": {
|
||||
"filesize": "Filstorlek"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Denna fil är lösenordsskyddad. Fyll i lösenord:",
|
||||
@@ -1557,5 +1662,79 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Ladda ner",
|
||||
"convert": {
|
||||
"title": "Konvertera",
|
||||
"settings": "Inställningar",
|
||||
"color": "Färg",
|
||||
"greyscale": "Gråskala",
|
||||
"fillPage": "Fyll sida",
|
||||
"pdfaDigitalSignatureWarning": "PDF:en innehåller en digital signatur. Denna kommer att tas bort i nästa steg.",
|
||||
"grayscale": "Gråskala"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Välj allt"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Signera"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Laddar...",
|
||||
"or": "eller"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Namn",
|
||||
"selectAll": "Välj allt",
|
||||
"deleteSelected": "Ta bort valda",
|
||||
"download": "Ladda ner",
|
||||
"delete": "Radera"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Sanera PDF",
|
||||
"steps": {
|
||||
"settings": "Inställningar"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Ändra behörigheter",
|
||||
"submit": "Ändra behörigheter",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Förhindra sammansättning av dokument"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Förhindra innehållsextraktion"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Förhindra extraktion för tillgänglighet"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Förhindra att fylla i formulär"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Förhindra ändring"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Förhindra anteckningsändring"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Förhindra utskrift"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Förhindra utskrift av olika format"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Ändra behörigheter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"loading": "กำลังโหลด...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
@@ -568,7 +568,7 @@
|
||||
"desc": "ลบภาพออกจาก PDF เพื่อลดขนาดไฟล์"
|
||||
},
|
||||
"splitByChapters": {
|
||||
"title": "Split PDF by Chapters",
|
||||
"title": "แบ่งไฟล์ PDF ตามหมวดหมู่",
|
||||
"desc": "Split a PDF into multiple files based on its chapter structure."
|
||||
},
|
||||
"validateSignature": {
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"convert": {
|
||||
"title": "แปลง"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "แยกหน้า"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "ลบ",
|
||||
"desc": "ลบหน้าที่ไม่ต้องการจากเอกสาร PDF ของคุณ"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "ลบภาพออกจาก PDF",
|
||||
"desc": "ลบภาพออกจาก PDF เพื่อลดขนาดไฟล์"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "แยกตามขนาด/จำนวน",
|
||||
"desc": "แยก PDF เป็นเอกสารหลายฉบับตามขนาด จำนวนหน้า หรือจำนวนเอกสาร"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "ปรับสี/คอนทราสต์",
|
||||
"desc": "ปรับคอนทราสต์ ความอิ่มตัว และความสว่างของ PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "เปลี่ยนสิทธิ์"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -594,7 +626,7 @@
|
||||
"deselectAll": "Deselect All",
|
||||
"selectPages": "Page Select",
|
||||
"selectedPages": "Selected Pages",
|
||||
"page": "Page",
|
||||
"page": "หน้า",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"downloadAll": "Export",
|
||||
"downloadSelected": "Export Selected",
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "แยกไฟล์ PDF",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "ลบ",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "จัดเรียงตามชื่อ",
|
||||
"sortByDate": "จัดเรียงตามวันที่",
|
||||
"removeCertSign": "ลบลายเซ็นดิจิทัลในไฟล์ที่รวม?",
|
||||
"submit": "รวม"
|
||||
"submit": "รวม",
|
||||
"sortBy": {
|
||||
"filename": "ชื่อไฟล์"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "การดำเนินการหน้า, แบ่ง, หลายหน้า, ตัด, ฝั่งเซิร์ฟเวอร์",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "เอกสาร #6: หน้า 10"
|
||||
},
|
||||
"splitPages": "ป้อนหน้าที่ต้องการแยก:",
|
||||
"submit": "แยก"
|
||||
"submit": "แยก",
|
||||
"steps": {
|
||||
"settings": "การตั้งค่า"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "ขนาดไฟล์"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "ขนาดไฟล์"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "ฝั่งเซิร์ฟเวอร์",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "ข้อความ",
|
||||
"2": "ภาพ"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "ข้อความ"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "ขนาดตัวอักษร"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "เปลี่ยน"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "ลบหน้า, ลบหน้า"
|
||||
"tags": "ลบหน้า, ลบหน้า",
|
||||
"title": "ลบ",
|
||||
"submit": "ลบ"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "ปลอดภัย, ความปลอดภัย",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "จำกัดสิ่งที่สามารถทำได้กับเอกสารเมื่อเปิด (ไม่รองรับโดยผู้อ่านทั้งหมด)",
|
||||
"16": "จำกัดการเปิดเอกสาร"
|
||||
},
|
||||
"submit": "เข้ารหัส"
|
||||
"submit": "เข้ารหัส",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "เปลี่ยนสิทธิ์"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "ปลอดภัย, ถอดรหัส, ความปลอดภัย, ลบรหัสผ่าน",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "เลือก PDF ที่ต้องการถอดรหัส",
|
||||
"2": "รหัสผ่าน"
|
||||
},
|
||||
"submit": "ลบ"
|
||||
"submit": "ลบ",
|
||||
"desc": "ลบรหัสผ่านจากการป้องกันเอกสาร PDF ของคุณ",
|
||||
"password": {
|
||||
"stepTitle": "ลบรหัสผ่าน",
|
||||
"label": "รหัสผ่านปัจจุบัน"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "ย่อ, เล็ก, จิ๋ว"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "โปรดอ่านเอกสารนี้เพื่อใช้งานภาษาอื่นๆ และ/หรือใช้งานนอก docker",
|
||||
"credit": "บริการนี้ใช้ qpdf และ Tesseract สำหรับ OCR",
|
||||
"submit": "ประมวลผล PDF ด้วย OCR"
|
||||
"submit": "ประมวลผล PDF ด้วย OCR",
|
||||
"desc": "ทำความสะอาดการสแกนและตรวจจับข้อความจากภาพภายใน PDF และเพิ่มเป็นข้อความอีกครั้ง",
|
||||
"settings": {
|
||||
"title": "การตั้งค่า",
|
||||
"ocrMode": {
|
||||
"label": "โหมด OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "ภาษา"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "โหมด OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "ภาษา"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "รูปภาพ, ภาพ, บันทึก, เก็บถาวร, zip, จับ, รับ",
|
||||
@@ -924,7 +1008,7 @@
|
||||
"sharedSigs": "ลายเซ็นร่วม",
|
||||
"noSavedSigs": "ไม่พบลายเซ็นที่บันทึกไว้",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "ลบ",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "แบน",
|
||||
"header": "แบน PDF",
|
||||
"flattenOnlyForms": "แบนเฉพาะฟอร์ม",
|
||||
"submit": "แบน"
|
||||
"submit": "แบน",
|
||||
"steps": {
|
||||
"settings": "การตั้งค่า"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "แบนเฉพาะฟอร์ม"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "ซ่อมแซม, กู้คืน, กู้",
|
||||
@@ -1106,10 +1196,10 @@
|
||||
"credit": "ใช้ WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "มาร์กอัป, เนื้อหาเว็บ, การแปลง, การแปลง",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "แปลง"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "ข้อมูล, สถิติ, สถิติ",
|
||||
@@ -1148,7 +1238,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1264,21 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "ขั้นสูง"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "เพิ่ม"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "หน้า",
|
||||
"placeholder": "(เช่น 1,2,8 หรือ 4,7,12-16 หรือ 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, การแยกตาราง, แยก, การแปลง"
|
||||
@@ -1261,8 +1365,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "เหตุผล",
|
||||
"location": "ตำแหน่ง",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1385,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "เวอร์ชัน",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1490,10 @@
|
||||
"4": "โหมดอัตโนมัติ - ปรับคุณภาพอัตโนมัติเพื่อให้ PDF ตรงกับขนาดที่ต้องการ",
|
||||
"5": "ขนาด PDF ที่คาดหวัง (เช่น 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "บีบอัด"
|
||||
"submit": "บีบอัด",
|
||||
"method": {
|
||||
"filesize": "ขนาดไฟล์"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1557,5 +1664,74 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "ดาวน์โหลด",
|
||||
"convert": {
|
||||
"title": "แปลง",
|
||||
"settings": "การตั้งค่า",
|
||||
"color": "สี",
|
||||
"greyscale": "ระดับสีเทา",
|
||||
"fillPage": "เติมหน้า",
|
||||
"pdfaDigitalSignatureWarning": "PDF มีลายเซ็นดิจิทัล ซึ่งจะถูกลบในขั้นตอนถัดไป",
|
||||
"grayscale": "ระดับสีเทา"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "เซ็นชื่อ"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "กำลังโหลด..."
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "ชื่อ",
|
||||
"fileVersion": "เวอร์ชัน",
|
||||
"download": "ดาวน์โหลด",
|
||||
"delete": "ลบ"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "ทำความสะอาด PDF",
|
||||
"steps": {
|
||||
"settings": "การตั้งค่า"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "เปลี่ยนสิทธิ์",
|
||||
"submit": "เปลี่ยนสิทธิ์",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "ป้องกันการประกอบเอกสาร"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "ป้องกันการสกัดเนื้อหา"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "ป้องกันการสกัดเพื่อการเข้าถึง"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "ป้องกันการกรอกแบบฟอร์ม"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "ป้องกันการแก้ไข"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "ป้องกันการแก้ไขคำอธิบายประกอบ"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "ป้องกันการพิมพ์"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "ป้องกันการพิมพ์รูปแบบต่างๆ"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "เปลี่ยนสิทธิ์"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@
|
||||
"settings": "Ayarlar",
|
||||
"allTools": "Araçlar",
|
||||
"multiTool": "Çoklu Araçlar",
|
||||
"search": "Search",
|
||||
"search": "Ara",
|
||||
"sections": {
|
||||
"organize": "Düzenle",
|
||||
"convertTo": "PDF'ye dönüştür",
|
||||
@@ -320,7 +320,7 @@
|
||||
"desc": "Yerel olarak barındırılan tüm PDF ihtiyaçlarınız için tek durak noktanız.",
|
||||
"searchBar": "Özellikleri arayın...",
|
||||
"viewPdf": {
|
||||
"title": "View/Edit PDF",
|
||||
"title": "PDF Görüntüle/Düzenle",
|
||||
"desc": "Görüntüleyin, açıklama ekleyin, metin veya resim ekleyin"
|
||||
},
|
||||
"setFavorites": "Favorilere Ekle",
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "PDF'lerin dosya boyutunu azaltmak için sıkıştırın."
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "PDF Formlarının Kilidini Kaldır",
|
||||
"desc": "Form alanlarını düzenlenebilir hâle getir."
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "Metaveriyi Değiştir",
|
||||
@@ -578,11 +578,47 @@
|
||||
"replace-color": {
|
||||
"title": "Renkleri Değiştir ve Tersine Çevir",
|
||||
"desc": "PDF'deki metin ve arka plan renklerini değiştirin ve PDF'nin tüm renklerini tersine çevirerek dosya boyutunu azaltın"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Dönüştür"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "İçindekiler Tablosunu Düzenle",
|
||||
"desc": "PDF belgelerinde yer işaretleri ve içindekiler tablosu ekleyin veya düzenleyin"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Sayfaları Çıkar"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Kaldır",
|
||||
"desc": "PDF belgenizden istenmeyen sayfaları silin."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Resmi kaldır",
|
||||
"desc": "Dosya boyutunu küçültmek için PDF'den resmi kaldırın"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Boyut/Sayıya Göre Otomatik Bölme",
|
||||
"desc": "Tek bir PDF'yi boyut, sayfa sayısı veya belge sayısına göre birden fazla belgeye bölün"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Renkleri/Kontrastı Ayarla",
|
||||
"desc": "Bir PDF'in Kontrastını, Doygunluğunu ve Parlaklığını ayarlar"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Renkleri Değiştir ve Tersine Çevir",
|
||||
"desc": "PDF'deki metin ve arka plan renklerini değiştirin ve PDF'nin tüm renklerini tersine çevirerek dosya boyutunu azaltın"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "İzinleri Değiştir"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
"tags": "görüntüle,oku,açıklama ekle,metin,görüntü",
|
||||
"title": "View/Edit PDF",
|
||||
"title": "PDF Görüntüle/Düzenle",
|
||||
"header": "PDF Görüntüle"
|
||||
},
|
||||
"multiTool": {
|
||||
@@ -617,7 +653,10 @@
|
||||
"sortByName": "İsme göre sırala",
|
||||
"sortByDate": "Tarihe göre sırala",
|
||||
"removeCertSign": "Birleştirilen dosyadaki dijital imza kaldırılsın mı?",
|
||||
"submit": "Birleştir"
|
||||
"submit": "Birleştir",
|
||||
"sortBy": {
|
||||
"filename": "Dosya Adı"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Sayfa işlemleri,böl,Çoklu Sayfa,kes,sunucu tarafı",
|
||||
@@ -634,7 +673,20 @@
|
||||
"8": "Belge #6: Sayfa 10"
|
||||
},
|
||||
"splitPages": "Ayrılacak sayfaları girin:",
|
||||
"submit": "Ayır"
|
||||
"submit": "Ayır",
|
||||
"steps": {
|
||||
"settings": "Ayarlar"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Dosya Boyutu"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Dosya Boyutu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "sunucu tarafı",
|
||||
@@ -712,6 +764,12 @@
|
||||
"type": {
|
||||
"1": "Metin",
|
||||
"2": "Resim"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Metin"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Font Büyüklüğü"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +792,9 @@
|
||||
"submit": "Değiştir"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Sayfaları kaldır,sayfaları sil"
|
||||
"tags": "Sayfaları kaldır,sayfaları sil",
|
||||
"title": "Kaldır",
|
||||
"submit": "Kaldır"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "güvenli, güvenlik",
|
||||
@@ -758,7 +818,12 @@
|
||||
"15": "Açıldığında belgeyle ne yapılacağını kısıtlar (Tüm okuyucular tarafından desteklenmez)",
|
||||
"16": "Belgenin kendisinin açılmasını kısıtlar"
|
||||
},
|
||||
"submit": "Şifrele"
|
||||
"submit": "Şifrele",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "İzinleri Değiştir"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "güvenli,Şifreyi çöz,güvenlik,parolasız,parolayı sil",
|
||||
@@ -768,15 +833,20 @@
|
||||
"1": "Şifreyi Çözmek için PDF Seçin",
|
||||
"2": "Parola"
|
||||
},
|
||||
"submit": "Kaldır"
|
||||
"submit": "Kaldır",
|
||||
"desc": "PDF belgenizden parola korumasını kaldırın.",
|
||||
"password": {
|
||||
"stepTitle": "Parolayı Kaldır",
|
||||
"label": "Mevcut Şifre"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "sıkıştır,küçük,minik"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"title": "Form Alanlarının Salt Okunur Özelliğini Kaldır",
|
||||
"header": "PDF Formlarının Kilidini Aç",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -829,7 +899,25 @@
|
||||
},
|
||||
"help": "Lütfen bu belgede başka dillerde nasıl kullanılacağı ve/veya docker'da kullanılmaması hakkında bilgi edinin",
|
||||
"credit": "Bu hizmet OCR için qpdf ve Tesseract'ı kullanır.",
|
||||
"submit": "PDF'i OCR(Metin Tanıma) ile İşle"
|
||||
"submit": "PDF'i OCR(Metin Tanıma) ile İşle",
|
||||
"desc": "Taramaları temizler ve bir PDF içindeki resimlerden metni algılar ve tekrar metin olarak ekler.",
|
||||
"settings": {
|
||||
"title": "Ayarlar",
|
||||
"ocrMode": {
|
||||
"label": "OCR Modu"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Diller"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR Modu"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Diller"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "fotoğraf,resim,kaydet,arşiv,zip,yakala,al",
|
||||
@@ -938,7 +1026,13 @@
|
||||
"title": "Düzleştir",
|
||||
"header": "PDF'leri Düzleştir",
|
||||
"flattenOnlyForms": "Yalnızca formları düzleştir",
|
||||
"submit": "Düzleştir"
|
||||
"submit": "Düzleştir",
|
||||
"steps": {
|
||||
"settings": "Ayarlar"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Yalnızca formları düzleştir"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "onar,geri yükle,düzelt,geri getir",
|
||||
@@ -1109,7 +1203,7 @@
|
||||
"tags": "biçimlendirme,web-içeriği,dönüşüm,dönüştür,md",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Dönüştür"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "bilgi,veri,istatistikler,istatistik",
|
||||
@@ -1174,7 +1268,22 @@
|
||||
"showLayers": "Katmanları Göster (tüm katmanları varsayılana döndürmek için çift tıklayın)",
|
||||
"colourPicker": "Renk Seçici",
|
||||
"findCurrentOutlineItem": "Geçerli Anahat Öğesini Bul",
|
||||
"applyChanges": "Değişiklikleri Uygula"
|
||||
"applyChanges": "Değişiklikleri Uygula",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Gelişmiş"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Ekle"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Sayfalar",
|
||||
"placeholder": "(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1)"
|
||||
},
|
||||
"export": "Dışa Aktar"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV, Tablo Çıkarma, ayıklama, dönüştürme"
|
||||
@@ -1386,7 +1495,10 @@
|
||||
"4": "Otomatik mod - PDF'in tam boyutuna ulaşmak için kaliteyi otomatik ayarlar",
|
||||
"5": "Beklenen PDF Boyutu (örn. 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Sıkıştır"
|
||||
"submit": "Sıkıştır",
|
||||
"method": {
|
||||
"filesize": "Dosya Boyutu"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Bu dosya parola korumalı. Lütfen parolayı girin:",
|
||||
@@ -1557,5 +1669,83 @@
|
||||
"description": "Bu çerezler, araçlarımızın nasıl kullanıldığını anlamamıza yardımcı olur, böylece topluluğumuzun en çok değer verdiği özellikleri geliştirmeye odaklanabiliriz. İçiniz rahat olsun — Stirling PDF, belgelerinizin içeriğini asla takip etmez ve etmeyecektir."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "İndir",
|
||||
"undo": "Geri Al",
|
||||
"convert": {
|
||||
"title": "Dönüştür",
|
||||
"settings": "Ayarlar",
|
||||
"color": "Renk",
|
||||
"greyscale": "Gri tonlama",
|
||||
"fillPage": "Sayfayı Doldur",
|
||||
"pdfaDigitalSignatureWarning": "PDF dijital imza içeriyor. Bu bir sonraki adımda kaldırılacak.",
|
||||
"grayscale": "Gri tonlama"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Tümünü Seç",
|
||||
"deselectAll": "Seçimi Kaldır"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "İmzala"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Yükleniyor...",
|
||||
"or": "veya"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "İsim",
|
||||
"fileVersion": "Sürüm",
|
||||
"selectAll": "Tümünü Seç",
|
||||
"deselectAll": "Seçimi Kaldır",
|
||||
"deleteSelected": "Seçilenleri Sil",
|
||||
"download": "İndir",
|
||||
"delete": "Sil"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "PDF'i Temizle",
|
||||
"steps": {
|
||||
"settings": "Ayarlar"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "İzinleri Değiştir",
|
||||
"submit": "İzinleri Değiştir",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Belgenin birleştirilmesini önle"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "İçeriğin çıkarılmasını önle"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Erişilebilirlik için çıkarmanın önlenmesi"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Formun doldurulmasını önle"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Değişikliği önle"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Açıklama değişikliğini önle"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Yazdırmayı önle"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Farklı formatlarda yazdırmayı önle"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "İzinleri Değiştir"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Заміна та інверсія кольору",
|
||||
"desc": "Замінює колір тексту та фону у PDF та інвертує всі кольори PDF для зменшення розміру файлу"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Конвертувати"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Видобути сторінки"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Видалення",
|
||||
"desc": "Видаліть непотрібні сторінки з документа PDF."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Видалити зображення",
|
||||
"desc": "Видаляє зображення з PDF для зменшення розміру файлу"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Автоматичне розділення за розміром/кількістю",
|
||||
"desc": "Розділяє один PDF на кілька документів на основі розміру, кількості сторінок або кількості документів"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Налаштування кольорів/контрастності",
|
||||
"desc": "Налаштування контрастності, насиченості та яскравості файлу PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Заміна та інверсія кольору",
|
||||
"desc": "Замінює колір тексту та фону у PDF та інвертує всі кольори PDF для зменшення розміру файлу"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Змінити дозволи"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Сортування за ім'ям",
|
||||
"sortByDate": "Сортування за датою",
|
||||
"removeCertSign": "Видалити цифровий підпис у об’єднаному файлі?",
|
||||
"submit": "Об'єднати"
|
||||
"submit": "Об'єднати",
|
||||
"sortBy": {
|
||||
"filename": "Ім'я файлу"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "операції зі сторінками,розділення,багатосторінковий,вирізання,серверна частина",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Документ #6: Сторінки 9 і 10"
|
||||
},
|
||||
"splitPages": "Введіть сторінки для розділення:",
|
||||
"submit": "Розділити"
|
||||
"submit": "Розділити",
|
||||
"steps": {
|
||||
"settings": "Налаштування"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Розмір файлу"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Розмір файлу"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "серверна частина",
|
||||
@@ -712,6 +760,12 @@
|
||||
"type": {
|
||||
"1": "Текст",
|
||||
"2": "Зображення"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Текст"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "Розмір шрифту"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +788,9 @@
|
||||
"submit": "Змінити"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "видалити сторінки,видалення сторінок"
|
||||
"tags": "видалити сторінки,видалення сторінок",
|
||||
"title": "Видалення",
|
||||
"submit": "Видалення"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "безпека,захист",
|
||||
@@ -758,7 +814,12 @@
|
||||
"15": "Обмежує, що можна робити з документом після його відкриття (не підтримується всіма програмами читання)",
|
||||
"16": "Обмежує відкриття самого документа"
|
||||
},
|
||||
"submit": "Шифрувати"
|
||||
"submit": "Шифрувати",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Змінити дозволи"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "безпека,розшифровка,захист,видалення пароля",
|
||||
@@ -768,7 +829,12 @@
|
||||
"1": "Виберіть PDF для розшифрування",
|
||||
"2": "Пароль"
|
||||
},
|
||||
"submit": "Видалити"
|
||||
"submit": "Видалити",
|
||||
"desc": "Зніміть захист паролем з вашого документа PDF.",
|
||||
"password": {
|
||||
"stepTitle": "Видалити пароль",
|
||||
"label": "Поточний пароль"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "стиск,маленький,крихітний"
|
||||
@@ -829,7 +895,25 @@
|
||||
},
|
||||
"help": "Прочитайте цю документацію про те, як використовувати це для інших мов і/або використовувати не в докері.",
|
||||
"credit": "Цей сервіс використовує qpdf та Tesseract для OCR.",
|
||||
"submit": "Обробка PDF з OCR"
|
||||
"submit": "Обробка PDF з OCR",
|
||||
"desc": "Очищення сканування та виявлення тексту на зображеннях у файлі PDF та повторне додавання його як текст.",
|
||||
"settings": {
|
||||
"title": "Налаштування",
|
||||
"ocrMode": {
|
||||
"label": "Режим OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Мови"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Режим OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Мови"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "зображення,фото,збереження,архів,zip,захоплення,захоплення",
|
||||
@@ -938,7 +1022,13 @@
|
||||
"title": "Згладжування",
|
||||
"header": "Згладжування PDF",
|
||||
"flattenOnlyForms": "Згладити тільки форми",
|
||||
"submit": "Згладити"
|
||||
"submit": "Згладити",
|
||||
"steps": {
|
||||
"settings": "Налаштування"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Згладити тільки форми"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "виправити,відновити,виправити,відновити",
|
||||
@@ -1174,7 +1264,22 @@
|
||||
"showLayers": "Показати шари (подвійне клацання для скидання всіх шарів до стану за умовчанням)",
|
||||
"colourPicker": "Вибір кольору",
|
||||
"findCurrentOutlineItem": "Знайти поточний елемент структури",
|
||||
"applyChanges": "Застосувати зміни"
|
||||
"applyChanges": "Застосувати зміни",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Додаткове"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Додати"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "Сторінки",
|
||||
"placeholder": "(наприклад 1,2,8 або 4,7,12-16 або 2n-1)"
|
||||
},
|
||||
"export": "Експорт"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "csv,видобуток таблиці,вилучення,конвертація"
|
||||
@@ -1386,7 +1491,10 @@
|
||||
"4": "Автоматичний режим - автоматично налаштовує якість для отримання PDF точного розміру",
|
||||
"5": "Очікуваний розмір PDF (наприклад, 25 МБ, 10,8 МБ, 25 КБ)"
|
||||
},
|
||||
"submit": "Стиснути"
|
||||
"submit": "Стиснути",
|
||||
"method": {
|
||||
"filesize": "Розмір файлу"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "Цей файл захищений паролем. Будь ласка, введіть пароль:",
|
||||
@@ -1557,5 +1665,83 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Завантажити",
|
||||
"undo": "Скасувати",
|
||||
"convert": {
|
||||
"title": "Конвертувати",
|
||||
"settings": "Налаштування",
|
||||
"color": "Колір",
|
||||
"greyscale": "Відтінки сірого",
|
||||
"fillPage": "Заповнення сторінки",
|
||||
"pdfaDigitalSignatureWarning": "Цей PDF документ має цифровий підпис. Цей підпис буде видалений у наступному кроці.",
|
||||
"grayscale": "Відтінки сірого"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "Вибрати все",
|
||||
"deselectAll": "Скасувати вибір усіх"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Підпис"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "Завантаження...",
|
||||
"or": "або"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Ім'я",
|
||||
"fileVersion": "Версія",
|
||||
"selectAll": "Вибрати все",
|
||||
"deselectAll": "Скасувати вибір усіх",
|
||||
"deleteSelected": "Видалити вибрані",
|
||||
"download": "Завантажити",
|
||||
"delete": "Видалити"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Дезінфекція PDF",
|
||||
"steps": {
|
||||
"settings": "Налаштування"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Змінити дозволи",
|
||||
"submit": "Змінити дозволи",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Запобігти збірці документа"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Запобігти вилученню контенту"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Запобігти вилученню для доступності"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Заборонити заповнення форм"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Запобігти модифікації"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Заборонити модифікацію анотацій"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Заборонити друк"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Заборонити друк різних форматів"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Змінити дозволи"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@
|
||||
"scanHelp": "Trợ giúp quét thư mục",
|
||||
"deletePrompt": "Bạn có chắc chắn muốn xóa pipeline",
|
||||
"tags": "tự động hóa,chuỗi,kịch bản,xử lý hàng loạt",
|
||||
"title": "Pipeline"
|
||||
"title": "Pipeline (Nâng cao)"
|
||||
},
|
||||
"pipelineOptions": {
|
||||
"header": "Cấu hình Pipeline",
|
||||
@@ -578,6 +578,38 @@
|
||||
"replace-color": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"convert": {
|
||||
"title": "Chuyển đổi"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "Add attachments"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "Trích xuất trang"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Xóa",
|
||||
"desc": "Xóa các trang không mong muốn khỏi tài liệu PDF của bạn."
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "Remove image",
|
||||
"desc": "Remove image from PDF to reduce file size"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "Tự động chia theo kích thước/số lượng",
|
||||
"desc": "Chia một tệp PDF thành nhiều tài liệu dựa trên kích thước, số trang hoặc số lượng tài liệu"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Điều chỉnh màu sắc/tương phản",
|
||||
"desc": "Điều chỉnh độ tương phản, độ bão hòa và độ sáng của PDF"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "Replace and Invert Color",
|
||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Thay đổi quyền"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -602,10 +634,10 @@
|
||||
"addFile": "Add File",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"rotateRight": "Rotate Right",
|
||||
"split": "Split",
|
||||
"split": "Tách",
|
||||
"moveLeft": "Move Left",
|
||||
"moveRight": "Move Right",
|
||||
"delete": "Delete",
|
||||
"delete": "Xóa",
|
||||
"dragDropMessage": "Page(s) Selected",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
@@ -617,7 +649,10 @@
|
||||
"sortByName": "Sắp xếp theo tên",
|
||||
"sortByDate": "Sắp xếp theo ngày",
|
||||
"removeCertSign": "Xóa chữ ký số trong tệp đã trộn?",
|
||||
"submit": "Trộn"
|
||||
"submit": "Trộn",
|
||||
"sortBy": {
|
||||
"filename": "Tên tệp"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "Thao tác trang,chia,Nhiều trang,cắt,phía máy chủ",
|
||||
@@ -634,7 +669,20 @@
|
||||
"8": "Tài liệu #6: Trang 10"
|
||||
},
|
||||
"splitPages": "Nhập các trang cần chia:",
|
||||
"submit": "Chia"
|
||||
"submit": "Chia",
|
||||
"steps": {
|
||||
"settings": "Cài đặt"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "Kích thước tệp"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "Kích thước tệp"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "phía máy chủ",
|
||||
@@ -712,6 +760,9 @@
|
||||
"type": {
|
||||
"1": "Văn bản",
|
||||
"2": "Hình ảnh"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "Văn bản"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +785,9 @@
|
||||
"submit": "Thay đổi"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Xóa trang,xóa trang"
|
||||
"tags": "Xóa trang,xóa trang",
|
||||
"title": "Xóa",
|
||||
"submit": "Xóa"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "bảo mật,an toàn",
|
||||
@@ -758,7 +811,12 @@
|
||||
"15": "Hạn chế những gì có thể làm với tài liệu sau khi mở (Không được hỗ trợ bởi tất cả các trình đọc)",
|
||||
"16": "Hạn chế việc mở tài liệu"
|
||||
},
|
||||
"submit": "Mã hóa"
|
||||
"submit": "Mã hóa",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "Thay đổi quyền"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "bảo mật,Giải mã,an toàn,bỏ mật khẩu,xóa mật khẩu",
|
||||
@@ -768,7 +826,12 @@
|
||||
"1": "Chọn PDF để giải mã",
|
||||
"2": "Mật khẩu"
|
||||
},
|
||||
"submit": "Xóa"
|
||||
"submit": "Xóa",
|
||||
"desc": "Xóa bảo vệ mật khẩu khỏi tài liệu PDF của bạn.",
|
||||
"password": {
|
||||
"stepTitle": "Xóa mật khẩu",
|
||||
"label": "Mật khẩu hiện tại"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "ép,nhỏ,nhỏ gọn"
|
||||
@@ -829,7 +892,25 @@
|
||||
},
|
||||
"help": "Vui lòng đọc tài liệu này về cách sử dụng cho các ngôn ngữ khác và/hoặc sử dụng không trong docker",
|
||||
"credit": "Dịch vụ này sử dụng qpdf và Tesseract cho OCR.",
|
||||
"submit": "Xử lý PDF với OCR"
|
||||
"submit": "Xử lý PDF với OCR",
|
||||
"desc": "Dọn dẹp bản quét và phát hiện văn bản từ hình ảnh trong PDF và thêm lại dưới dạng văn bản.",
|
||||
"settings": {
|
||||
"title": "Cài đặt",
|
||||
"ocrMode": {
|
||||
"label": "Chế độ OCR"
|
||||
},
|
||||
"languages": {
|
||||
"label": "Ngôn ngữ"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "Chế độ OCR"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Ngôn ngữ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "hình ảnh,ảnh,lưu,lưu trữ,zip,chụp,lấy",
|
||||
@@ -924,7 +1005,7 @@
|
||||
"sharedSigs": "Shared Signatures",
|
||||
"noSavedSigs": "No saved signatures found",
|
||||
"addToAll": "Add to all pages",
|
||||
"delete": "Delete",
|
||||
"delete": "Xóa",
|
||||
"first": "First page",
|
||||
"last": "Last page",
|
||||
"next": "Next page",
|
||||
@@ -938,7 +1019,13 @@
|
||||
"title": "Làm phẳng",
|
||||
"header": "Làm phẳng PDF",
|
||||
"flattenOnlyForms": "Chỉ làm phẳng biểu mẫu",
|
||||
"submit": "Làm phẳng"
|
||||
"submit": "Làm phẳng",
|
||||
"steps": {
|
||||
"settings": "Cài đặt"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "Chỉ làm phẳng biểu mẫu"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "sửa,khôi phục,sửa chữa,phục hồi",
|
||||
@@ -1106,10 +1193,10 @@
|
||||
"credit": "Sử dụng WeasyPrint"
|
||||
},
|
||||
"PDFToMarkdown": {
|
||||
"tags": "markup,web-content,transformation,convert,md",
|
||||
"tags": "đánh dấu,nội dung web,chuyển đổi,chuyển",
|
||||
"title": "PDF To Markdown",
|
||||
"header": "PDF To Markdown",
|
||||
"submit": "Convert"
|
||||
"submit": "Chuyển đổi"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "thông tin,dữ liệu,số liệu thống kê,thống kê",
|
||||
@@ -1148,7 +1235,7 @@
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden,manual",
|
||||
"title": "Manual Redaction",
|
||||
"header": "Manual Redaction",
|
||||
"submit": "Redact",
|
||||
"submit": "Manual Redaction",
|
||||
"textBasedRedaction": "Text based Redaction",
|
||||
"pageBasedRedaction": "Page-based Redaction",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
@@ -1174,7 +1261,20 @@
|
||||
"showLayers": "Show Layers (double-click to reset all layers to the default state)",
|
||||
"colourPicker": "Colour Picker",
|
||||
"findCurrentOutlineItem": "Find current outline item",
|
||||
"applyChanges": "Apply Changes"
|
||||
"applyChanges": "Apply Changes",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "Nâng cao"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "Thêm"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"placeholder": "(ví dụ: 1,2,8 hoặc 4,7,12-16 hoặc 2n-1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,Trích xuất bảng,trích xuất,chuyển đổi"
|
||||
@@ -1261,8 +1361,8 @@
|
||||
},
|
||||
"signer": "Signer",
|
||||
"date": "Date",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"reason": "Lý do",
|
||||
"location": "Vị trí",
|
||||
"noSignatures": "No digital signatures found in this document",
|
||||
"chain": {
|
||||
"invalid": "Certificate chain validation failed - cannot verify signer's identity"
|
||||
@@ -1281,7 +1381,7 @@
|
||||
"validUntil": "Valid Until",
|
||||
"algorithm": "Algorithm",
|
||||
"keySize": "Key Size",
|
||||
"version": "Version",
|
||||
"version": "Phiên bản",
|
||||
"keyUsage": "Key Usage",
|
||||
"selfSigned": "Self-Signed",
|
||||
"bits": "bits"
|
||||
@@ -1386,7 +1486,10 @@
|
||||
"4": "Chế độ tự động - Tự động điều chỉnh chất lượng để đạt được kích thước PDF chính xác",
|
||||
"5": "Kích thước PDF mong muốn (ví dụ: 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "Nén"
|
||||
"submit": "Nén",
|
||||
"method": {
|
||||
"filesize": "Kích thước tệp"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "This file is password-protected. Please enter the password:",
|
||||
@@ -1501,7 +1604,7 @@
|
||||
"3": "Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.",
|
||||
"4": "Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs."
|
||||
},
|
||||
"submit": "Split PDF"
|
||||
"submit": "Chia PDF"
|
||||
},
|
||||
"fileChooser": {
|
||||
"click": "Click",
|
||||
@@ -1557,5 +1660,71 @@
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "Tải xuống",
|
||||
"convert": {
|
||||
"title": "Chuyển đổi",
|
||||
"settings": "Cài đặt",
|
||||
"color": "Màu sắc",
|
||||
"greyscale": "Thang độ xám",
|
||||
"fillPage": "Lấp đầy trang",
|
||||
"pdfaDigitalSignatureWarning": "PDF chứa chữ ký số. Điều này sẽ bị xóa trong bước tiếp theo.",
|
||||
"grayscale": "Thang độ xám"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "embed,attach,file,attachment,attachments",
|
||||
"title": "Add attachments",
|
||||
"header": "Add attachments",
|
||||
"submit": "Add attachments"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "Ký"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "Tên",
|
||||
"fileVersion": "Phiên bản",
|
||||
"download": "Tải xuống",
|
||||
"delete": "Xóa"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "Làm sạch PDF",
|
||||
"steps": {
|
||||
"settings": "Cài đặt"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "Thay đổi quyền",
|
||||
"submit": "Thay đổi quyền",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "Ngăn chặn lắp ráp tài liệu"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "Ngăn chặn trích xuất nội dung"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "Ngăn chặn trích xuất để truy cập"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "Ngăn chặn điền vào biểu mẫu"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "Ngăn chặn sửa đổi"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "Ngăn chặn sửa đổi chú thích"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "Ngăn chặn in"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "Ngăn chặn in các định dạng khác nhau"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "Thay đổi quyền"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@
|
||||
"multiPdfDropPrompt": "选择(或拖拽)所需的 PDF",
|
||||
"imgPrompt": "选择图像",
|
||||
"genericSubmit": "提交",
|
||||
"uploadLimit": "Maximum file size:",
|
||||
"uploadLimitExceededSingular": "is too large. Maximum allowed size is",
|
||||
"uploadLimitExceededPlural": "are too large. Maximum allowed size is",
|
||||
"uploadLimit": "最大文件大小:",
|
||||
"uploadLimitExceededSingular": "文件过大。最大允许大小为",
|
||||
"uploadLimitExceededPlural": "文件过大。最大允许大小为",
|
||||
"processTimeWarning": "警告:此过程可能需要多达一分钟,具体时间取决于文件大小",
|
||||
"pageOrderPrompt": "页面顺序(输入逗号分隔的页码列表或函数):",
|
||||
"pageSelectionPrompt": "自定义页面选择(输入以逗号分隔的页码列表或函数:1,5,6、2n+1):",
|
||||
@@ -118,14 +118,14 @@
|
||||
"addToDoc": "添加至文件",
|
||||
"reset": "重置",
|
||||
"apply": "应用",
|
||||
"noFileSelected": "No file selected. Please upload one.",
|
||||
"noFileSelected": "未选择文件,请上传一个文件。",
|
||||
"legal": {
|
||||
"privacy": "隐私政策",
|
||||
"terms": "服务条款",
|
||||
"accessibility": "无障碍",
|
||||
"cookie": "Cookie 政策",
|
||||
"impressum": "Impressum",
|
||||
"showCookieBanner": "Cookie Preferences"
|
||||
"impressum": "版权声明",
|
||||
"showCookieBanner": "Cookie 偏好设置"
|
||||
},
|
||||
"pipeline": {
|
||||
"header": "流水线菜单(Beta)",
|
||||
@@ -265,32 +265,32 @@
|
||||
"disabledUsers": "禁用用户:",
|
||||
"totalUsers": "总用户:",
|
||||
"lastRequest": "最后登录",
|
||||
"usage": "View Usage"
|
||||
"usage": "查看使用情况"
|
||||
},
|
||||
"endpointStatistics": {
|
||||
"title": "Endpoint Statistics",
|
||||
"header": "Endpoint Statistics",
|
||||
"top10": "Top 10",
|
||||
"top20": "Top 20",
|
||||
"all": "All",
|
||||
"refresh": "Refresh",
|
||||
"includeHomepage": "Include Homepage ('/')",
|
||||
"includeLoginPage": "Include Login Page ('/login')",
|
||||
"totalEndpoints": "Total Endpoints",
|
||||
"totalVisits": "Total Visits",
|
||||
"showing": "Showing",
|
||||
"selectedVisits": "Selected Visits",
|
||||
"endpoint": "Endpoint",
|
||||
"visits": "Visits",
|
||||
"percentage": "Percentage",
|
||||
"loading": "Loading...",
|
||||
"failedToLoad": "Failed to load endpoint data. Please try refreshing.",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
"top": "Top",
|
||||
"numberOfVisits": "Number of Visits",
|
||||
"visitsTooltip": "Visits: {0} ({1}% of total)",
|
||||
"retry": "Retry"
|
||||
"title": "端点统计",
|
||||
"header": "端点统计",
|
||||
"top10": "前10",
|
||||
"top20": "前20",
|
||||
"all": "全部",
|
||||
"refresh": "刷新",
|
||||
"includeHomepage": "包含主页('/')",
|
||||
"includeLoginPage": "包含登录页('/login')",
|
||||
"totalEndpoints": "端点总数",
|
||||
"totalVisits": "访问总数",
|
||||
"showing": "显示",
|
||||
"selectedVisits": "选中访问数",
|
||||
"endpoint": "端点",
|
||||
"visits": "访问次数",
|
||||
"percentage": "百分比",
|
||||
"loading": "加载中...",
|
||||
"failedToLoad": "加载端点数据失败。请尝试刷新。",
|
||||
"home": "主页",
|
||||
"login": "登录",
|
||||
"top": "顶部",
|
||||
"numberOfVisits": "访问次数",
|
||||
"visitsTooltip": "访问次数:{0}(占总数的{1}%)",
|
||||
"retry": "重试"
|
||||
},
|
||||
"database": {
|
||||
"title": "数据库 导入/导出",
|
||||
@@ -320,7 +320,7 @@
|
||||
"desc": "本地部署的一站式服务,满足您的所有 PDF 需求。",
|
||||
"searchBar": "搜索您需要的功能...",
|
||||
"viewPdf": {
|
||||
"title": "View/Edit PDF",
|
||||
"title": "浏览/编辑 PDF",
|
||||
"desc": "浏览、注释、添加文本或图像"
|
||||
},
|
||||
"setFavorites": "编辑收藏夹",
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "压缩 PDF 文件以减小文件大小。"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "解锁PDF表单",
|
||||
"desc": "移除表单字段只读属性"
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "更改元数据",
|
||||
@@ -578,11 +578,48 @@
|
||||
"replace-color": {
|
||||
"title": "替换和反转颜色",
|
||||
"desc": "替换 PDF 中文本和背景的颜色,并将PDF全色反转以减小文件大小"
|
||||
},
|
||||
"convert": {
|
||||
"title": "转换"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "添加附件",
|
||||
"desc": "在 PDF 中添加或删除嵌入文件(附件)"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "编辑目录",
|
||||
"desc": "为 PDF 文档添加或编辑目录和书签"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "提取页面"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "删除",
|
||||
"desc": "从 PDF 文档中删除不需要的页面。"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "删除图像",
|
||||
"desc": "删除图像减少 PDF 大小"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "自动根据大小/数目拆分 PDF",
|
||||
"desc": "将单个 PDF 拆分为多个文档,基于大小、页数或文档数"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "调整颜色/对比度",
|
||||
"desc": "调整 PDF 的对比度、饱和度和亮度"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "替换和反转颜色",
|
||||
"desc": "替换 PDF 中文本和背景的颜色,并将PDF反转颜色以减小文件大小"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "更改权限"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
"tags": "浏览、阅读、注释、文本、图像",
|
||||
"title": "View/Edit PDF",
|
||||
"title": "浏览/编辑 PDF",
|
||||
"header": "浏览 PDF"
|
||||
},
|
||||
"multiTool": {
|
||||
@@ -592,9 +629,9 @@
|
||||
"uploadPrompts": "文件名",
|
||||
"selectAll": "选择所有",
|
||||
"deselectAll": "取消选择所有",
|
||||
"selectPages": "Page Select",
|
||||
"selectPages": "页面选择",
|
||||
"selectedPages": "已选择的页面",
|
||||
"page": "Page",
|
||||
"page": "页面",
|
||||
"deleteSelected": "删除已选",
|
||||
"downloadAll": "导出全部",
|
||||
"downloadSelected": "导出已选",
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "按名称排序",
|
||||
"sortByDate": "按日期排序",
|
||||
"removeCertSign": "删除合并文件的数字签名吗?",
|
||||
"submit": "合并"
|
||||
"submit": "合并",
|
||||
"sortBy": {
|
||||
"filename": "文件名"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "页面操作,划分,多页面,剪切,服务器端",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "文档 #6:第 9 页和第 10 页"
|
||||
},
|
||||
"splitPages": "输入要分割的页面:",
|
||||
"submit": "拆分"
|
||||
"submit": "拆分",
|
||||
"steps": {
|
||||
"settings": "设置"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "文件大小"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "文件大小"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "服务器端",
|
||||
@@ -679,7 +732,7 @@
|
||||
"8": "删除最后一页",
|
||||
"9": "删除第一页和最后一页",
|
||||
"10": "奇偶合并",
|
||||
"11": "Duplicate all pages"
|
||||
"11": "复制所有页面"
|
||||
},
|
||||
"placeholder": "(例如:1,3,2 或 4-8,2,10-12 或 2n-1)"
|
||||
},
|
||||
@@ -712,6 +765,12 @@
|
||||
"type": {
|
||||
"1": "文字",
|
||||
"2": "图片"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "文本"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "字体大小"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +793,9 @@
|
||||
"submit": "改变"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "删除页面、删除"
|
||||
"tags": "删除页面、删除",
|
||||
"title": "删除",
|
||||
"submit": "删除"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "安全、密码、加密",
|
||||
@@ -758,7 +819,12 @@
|
||||
"15": "限制打开后对文档的操作(不被所有阅读器支持)",
|
||||
"16": "限制打开文档本身"
|
||||
},
|
||||
"submit": "加密"
|
||||
"submit": "加密",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "更改权限"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "安全、解密、密码、安全性、删除密码",
|
||||
@@ -768,15 +834,20 @@
|
||||
"1": "选择要解密的 PDF",
|
||||
"2": "密码"
|
||||
},
|
||||
"submit": "删除"
|
||||
"submit": "删除",
|
||||
"desc": "从 PDF 文档中移除密码保护。",
|
||||
"password": {
|
||||
"stepTitle": "删除密码",
|
||||
"label": "当前密码"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "压缩、小、微小"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"tags": "移除,删除,表单,字段,只读",
|
||||
"title": "移除表单字段只读属性",
|
||||
"header": "解锁 PDF 表单",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -829,7 +900,25 @@
|
||||
},
|
||||
"help": "请阅读此文档,了解如何将其用于其他语言和/或不在 docker 中使用。",
|
||||
"credit": "此服务使用 qpdf 和 Tesseract 进行 OCR。",
|
||||
"submit": "用 OCR 处理 PDF"
|
||||
"submit": "用 OCR 处理 PDF",
|
||||
"desc": "清理和识别 PDF 中的图像文本,并将其转换为可编辑文本。",
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"ocrMode": {
|
||||
"label": "OCR 模式"
|
||||
},
|
||||
"languages": {
|
||||
"label": "语言"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR 模式"
|
||||
},
|
||||
"languages": {
|
||||
"title": "语言"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "图片、照片、保存、归档、压缩包、截取、抓取",
|
||||
@@ -938,7 +1027,13 @@
|
||||
"title": "展平",
|
||||
"header": "展平 PDF",
|
||||
"flattenOnlyForms": "仅展平表格",
|
||||
"submit": "展平"
|
||||
"submit": "展平",
|
||||
"steps": {
|
||||
"settings": "设置"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "仅展平表格"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "修复、恢复、纠正、恢复",
|
||||
@@ -1174,7 +1269,22 @@
|
||||
"showLayers": "显示图层(双击将所有图层重置为默认状态)",
|
||||
"colourPicker": "颜色选择器",
|
||||
"findCurrentOutlineItem": "查找当前大纲项目",
|
||||
"applyChanges": "应用"
|
||||
"applyChanges": "应用",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "高级功能"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "添加"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "页码",
|
||||
"placeholder": "(例如:1,2,8 或 4,7,12-16 或 2n-1)"
|
||||
},
|
||||
"export": "导出全部"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV、表格提取、提取、转换"
|
||||
@@ -1331,12 +1441,12 @@
|
||||
"oauth2AccessDenied": "拒绝访问",
|
||||
"oauth2InvalidTokenResponse": "无效的 Token 响应",
|
||||
"oauth2InvalidIdToken": "无效的 Token",
|
||||
"relyingPartyRegistrationNotFound": "No relying party registration found",
|
||||
"relyingPartyRegistrationNotFound": "未找到依赖方注册信息",
|
||||
"userIsDisabled": "用户被禁用,登录已被阻止。请联系管理员。",
|
||||
"alreadyLoggedIn": "您已经登录到了",
|
||||
"alreadyLoggedIn2": "设备,请注销设备后重试。",
|
||||
"toManySessions": "你已经有太多的会话了。请注销一些设备后重试。",
|
||||
"logoutMessage": "You have been logged out."
|
||||
"logoutMessage": "您已退出登录。"
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF 转单页",
|
||||
@@ -1355,10 +1465,10 @@
|
||||
"selectText": {
|
||||
"1": "移除 JavaScript 操作",
|
||||
"2": "移除嵌入的文件",
|
||||
"3": "Remove XMP metadata",
|
||||
"3": "移除 XMP 元数据",
|
||||
"4": "移除链接",
|
||||
"5": "移除字体",
|
||||
"6": "Remove Document Info Metadata"
|
||||
"6": "移除文档信息元数据"
|
||||
},
|
||||
"submit": "清理PDF"
|
||||
},
|
||||
@@ -1380,13 +1490,16 @@
|
||||
"selectText": {
|
||||
"1": {
|
||||
"_value": "Compression Settings",
|
||||
"1": "1-3 PDF compression,</br> 4-6 lite image compression,</br> 7-9 intense image compression Will dramatically reduce image quality"
|
||||
"1": "1-3 PDF 压缩,</br> 4-6 轻度图像压缩,</br> 7-9 深度图像压缩(将显著降低图像质量)"
|
||||
},
|
||||
"2": "优化级别:",
|
||||
"4": "自动模式 - 自动调整质量以获得精确大小的PDF",
|
||||
"5": "预期PDF大小(例如:25MB、10.8MB、25KB)"
|
||||
},
|
||||
"submit": "压缩"
|
||||
"submit": "压缩",
|
||||
"method": {
|
||||
"filesize": "文件大小"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "此文件受密码保护。请输入密码:",
|
||||
@@ -1472,15 +1585,15 @@
|
||||
"button": "参与调查",
|
||||
"dontShowAgain": "不再显示",
|
||||
"meeting": {
|
||||
"1": "If you're using Stirling PDF at work, we'd love to speak to you. We're offering technical support sessions in exchange for a 15 minute user discovery session.",
|
||||
"2": "This is a chance to:",
|
||||
"3": "Get help with deployment, integrations, or troubleshooting",
|
||||
"4": "Provide direct feedback on performance, edge cases, and feature gaps",
|
||||
"5": "Help us refine Stirling PDF for real-world enterprise use",
|
||||
"6": "If you're interested, you can book time with our team directly. (English speaking only)",
|
||||
"7": "Looking forward to digging into your use cases and making Stirling PDF even better!",
|
||||
"notInterested": "Not a business and/or interested in a meeting?",
|
||||
"button": "Book meeting"
|
||||
"1": "如果您在工作中使用 Stirling PDF,我们非常希望与您交流。我们正在提供技术支持服务,以换取一次 15 分钟的用户访谈。",
|
||||
"2": "这是一个机会:",
|
||||
"3": "获取部署、集成或故障排除方面的帮助",
|
||||
"4": "提供直接反馈,包括性能、边缘案例和功能差距",
|
||||
"5": "帮助我们改进 Stirling PDF 以满足实际的企业使用需求",
|
||||
"6": "如果您有兴趣,可以直接与我们团队预约时间。(仅限英语)",
|
||||
"7": "期待深入了解您的使用案例,并使 Stirling PDF 变得更好!",
|
||||
"notInterested": "不是企业或对会议不感兴趣?",
|
||||
"button": "预约会议"
|
||||
}
|
||||
},
|
||||
"removeImage": {
|
||||
@@ -1523,39 +1636,117 @@
|
||||
},
|
||||
"cookieBanner": {
|
||||
"popUp": {
|
||||
"title": "How we use Cookies",
|
||||
"title": "我们如何使用 Cookie",
|
||||
"description": {
|
||||
"1": "We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.",
|
||||
"1": "我们使用 Cookie 和其他技术来使 Stirling PDF 更好地为您服务——帮助我们改进工具并构建您喜爱的功能。",
|
||||
"2": "If you’d rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
|
||||
},
|
||||
"acceptAllBtn": "Okay",
|
||||
"acceptNecessaryBtn": "No Thanks",
|
||||
"showPreferencesBtn": "Manage preferences"
|
||||
"acceptAllBtn": "全部接受",
|
||||
"acceptNecessaryBtn": "拒绝",
|
||||
"showPreferencesBtn": "管理偏好设置"
|
||||
},
|
||||
"preferencesModal": {
|
||||
"title": "Consent Preferences Center",
|
||||
"acceptAllBtn": "Accept all",
|
||||
"acceptNecessaryBtn": "Reject all",
|
||||
"savePreferencesBtn": "Save preferences",
|
||||
"closeIconLabel": "Close modal",
|
||||
"serviceCounterLabel": "Service|Services",
|
||||
"subtitle": "Cookie Usage",
|
||||
"title": "隐私偏好设置中心",
|
||||
"acceptAllBtn": "全部接受",
|
||||
"acceptNecessaryBtn": "拒绝所有",
|
||||
"savePreferencesBtn": "保存设置",
|
||||
"closeIconLabel": "关闭弹窗",
|
||||
"serviceCounterLabel": "服务",
|
||||
"subtitle": "Cookie使用说明",
|
||||
"description": {
|
||||
"1": "Stirling PDF uses cookies and similar technologies to enhance your experience and understand how our tools are used. This helps us improve performance, develop the features you care about, and provide ongoing support to our users.",
|
||||
"2": "Stirling PDF cannot—and will never—track or access the content of the documents you use.",
|
||||
"3": "Your privacy and trust are at the core of what we do."
|
||||
"1": "Stirling PDF通过Cookie及类似技术优化用户体验并分析工具使用情况,帮助我们提升性能、开发实用功能并提供持续支持。",
|
||||
"2": "我们承诺:Stirling PDF永远不会追踪或访问您使用的文档内容。",
|
||||
"3": "用户隐私与信任是我们一切工作的核心。"
|
||||
},
|
||||
"necessary": {
|
||||
"title": {
|
||||
"1": "Strictly Necessary Cookies",
|
||||
"2": "Always Enabled"
|
||||
"1": "必要Cookie",
|
||||
"2": "始终启用"
|
||||
},
|
||||
"description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can’t be turned off."
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytics",
|
||||
"description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with."
|
||||
"title": "分析统计",
|
||||
"description": "这些Cookie帮助我们分析工具使用情况,以便聚焦开发用户最需要的功能。再次强调:Stirling PDF绝不会追踪您处理的文档内容。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "下载",
|
||||
"undo": "撤销",
|
||||
"convert": {
|
||||
"title": "转换",
|
||||
"settings": "设置",
|
||||
"color": "颜色",
|
||||
"greyscale": "灰度",
|
||||
"fillPage": "填充页面",
|
||||
"pdfaDigitalSignatureWarning": "该PDF包含数字签名,下一步将移除该签名。",
|
||||
"grayscale": "灰度"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "嵌入、附件、文件、附加",
|
||||
"title": "添加附件",
|
||||
"header": "添加附件",
|
||||
"submit": "添加附件"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "选择所有",
|
||||
"deselectAll": "取消选择所有"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "签名"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "加载中...",
|
||||
"or": "或"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "名称",
|
||||
"fileVersion": "版本",
|
||||
"selectAll": "选择所有",
|
||||
"deselectAll": "取消选择所有",
|
||||
"deleteSelected": "删除已选",
|
||||
"download": "下载",
|
||||
"delete": "删除"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "清理 PDF",
|
||||
"steps": {
|
||||
"settings": "设置"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "更改权限",
|
||||
"submit": "更改权限",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "防止文件的拼接"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "防止内容提取"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "防止提取内容的可访问性"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "防止填写表格"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "防止修改"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "防止修改注释"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "防止打印"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "防止打印不同的格式"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "更改权限"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,8 +388,8 @@
|
||||
"desc": "壓縮 PDF 以減少其檔案大小。"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"title": "Unlock PDF Forms",
|
||||
"desc": "Remove read-only property of form fields in a PDF document."
|
||||
"title": "解鎖 PDF 表單",
|
||||
"desc": "移除 PDF 文件中表單欄位的唯讀屬性"
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "變更中繼資料",
|
||||
@@ -578,6 +578,43 @@
|
||||
"replace-color": {
|
||||
"title": "取代與反轉顏色",
|
||||
"desc": "取代 PDF 中文字和背景的顏色,並反轉整個 PDF 的顏色以減少檔案大小"
|
||||
},
|
||||
"convert": {
|
||||
"title": "轉換"
|
||||
},
|
||||
"attachments": {
|
||||
"title": "新增附件",
|
||||
"desc": "將檔案(附件)新增或移除至/從 PDF"
|
||||
},
|
||||
"editTableOfContents": {
|
||||
"title": "編輯目錄",
|
||||
"desc": "在 PDF 文件中新增或編輯書籤和目錄"
|
||||
},
|
||||
"extractPages": {
|
||||
"title": "提取頁面"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "移除",
|
||||
"desc": "從您的 PDF 檔案中刪除不需要的頁面。"
|
||||
},
|
||||
"removeImagePdf": {
|
||||
"title": "移除圖片",
|
||||
"desc": "從 PDF 中移除圖片以減少檔案大小"
|
||||
},
|
||||
"autoSizeSplitPDF": {
|
||||
"title": "根據大小/數量自動分割",
|
||||
"desc": "根據大小、頁數或文件數將單一 PDF 分割為多個文件"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "調整顏色/對比",
|
||||
"desc": "調整 PDF 的對比、飽和度和亮度"
|
||||
},
|
||||
"replaceColorPdf": {
|
||||
"title": "取代與反轉顏色",
|
||||
"desc": "取代 PDF 中文字和背景的顏色,並反轉整個 PDF 的顏色以減少檔案大小"
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "變更權限"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
@@ -617,7 +654,10 @@
|
||||
"sortByName": "依名稱排序",
|
||||
"sortByDate": "依日期排序",
|
||||
"removeCertSign": "是否移除合併後檔案的憑證簽章?",
|
||||
"submit": "合併"
|
||||
"submit": "合併",
|
||||
"sortBy": {
|
||||
"filename": "檔案名稱"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"tags": "頁面操作,劃分,多頁,剪下,伺服器端",
|
||||
@@ -634,7 +674,20 @@
|
||||
"8": "文件 #6:頁面 10"
|
||||
},
|
||||
"splitPages": "輸入要分割的頁面:",
|
||||
"submit": "分割"
|
||||
"submit": "分割",
|
||||
"steps": {
|
||||
"settings": "設定"
|
||||
},
|
||||
"methods": {
|
||||
"bySize": {
|
||||
"name": "檔案大小"
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"fileSize": {
|
||||
"label": "檔案大小"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "伺服器端",
|
||||
@@ -712,6 +765,12 @@
|
||||
"type": {
|
||||
"1": "文字",
|
||||
"2": "圖片"
|
||||
},
|
||||
"watermarkType": {
|
||||
"text": "文字"
|
||||
},
|
||||
"settings": {
|
||||
"fontSize": "字型大小"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
@@ -734,7 +793,9 @@
|
||||
"submit": "變更"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "移除頁面,刪除頁面"
|
||||
"tags": "移除頁面,刪除頁面",
|
||||
"title": "移除",
|
||||
"submit": "移除"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "安全,安全性",
|
||||
@@ -758,7 +819,12 @@
|
||||
"15": "限制一旦開啟文件可以做什麼(並非所有軟體都支援)",
|
||||
"16": "限制開啟文件本身"
|
||||
},
|
||||
"submit": "加密"
|
||||
"submit": "加密",
|
||||
"tooltip": {
|
||||
"permissions": {
|
||||
"title": "變更權限"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "安全,解密,安全性,取消密碼,刪除密碼",
|
||||
@@ -768,15 +834,20 @@
|
||||
"1": "選擇要解密的 PDF",
|
||||
"2": "密碼"
|
||||
},
|
||||
"submit": "移除"
|
||||
"submit": "移除",
|
||||
"desc": "從您的 PDF 檔案中移除密碼保護。",
|
||||
"password": {
|
||||
"stepTitle": "移除密碼",
|
||||
"label": "目前密碼"
|
||||
}
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "壓縮,小,微小"
|
||||
},
|
||||
"unlockPDFForms": {
|
||||
"tags": "remove,delete,form,field,readonly",
|
||||
"title": "Remove Read-Only from Form Fields",
|
||||
"header": "Unlock PDF Forms",
|
||||
"tags": "移除,刪除,表格,欄位,唯讀",
|
||||
"title": "移除表單欄位的唯讀限制",
|
||||
"header": "解鎖 PDF 表單",
|
||||
"submit": "Remove"
|
||||
},
|
||||
"changeMetadata": {
|
||||
@@ -829,7 +900,25 @@
|
||||
},
|
||||
"help": "請閱讀此文件,了解如何使用其他語言和/或在 Docker 中使用",
|
||||
"credit": "此服務使用 qpdf 和 Tesseract 進行 OCR。",
|
||||
"submit": "使用 OCR 處理 PDF"
|
||||
"submit": "使用 OCR 處理 PDF",
|
||||
"desc": "清理掃描並從 PDF 中的影像中偵測文字並重新新增為文字。",
|
||||
"settings": {
|
||||
"title": "設定",
|
||||
"ocrMode": {
|
||||
"label": "OCR 模式"
|
||||
},
|
||||
"languages": {
|
||||
"label": "語言"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": {
|
||||
"title": "OCR 模式"
|
||||
},
|
||||
"languages": {
|
||||
"title": "語言"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "圖片,照片,儲存,存檔,壓縮檔,捕獲,抓取",
|
||||
@@ -938,7 +1027,13 @@
|
||||
"title": "平坦化",
|
||||
"header": "PDF 平坦化",
|
||||
"flattenOnlyForms": "僅將表單平坦化",
|
||||
"submit": "平坦化"
|
||||
"submit": "平坦化",
|
||||
"steps": {
|
||||
"settings": "設定"
|
||||
},
|
||||
"options": {
|
||||
"flattenOnlyForms": "僅將表單平坦化"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"tags": "修復,恢復,修正,復原",
|
||||
@@ -1174,7 +1269,22 @@
|
||||
"showLayers": "顯示圖層(按兩下可將所有圖層重設為預設狀態)",
|
||||
"colourPicker": "顏色選擇器",
|
||||
"findCurrentOutlineItem": "尋找目前的大綱項目",
|
||||
"applyChanges": "套用變更"
|
||||
"applyChanges": "套用變更",
|
||||
"auto": {
|
||||
"settings": {
|
||||
"advancedTitle": "進階"
|
||||
},
|
||||
"wordsToRedact": {
|
||||
"add": "新增"
|
||||
}
|
||||
},
|
||||
"manual": {
|
||||
"pageRedactionNumbers": {
|
||||
"title": "頁面",
|
||||
"placeholder": "(例如 1,2,8 或 4,7,12-16 或 2n-1)"
|
||||
},
|
||||
"export": "匯出"
|
||||
}
|
||||
},
|
||||
"tableExtraxt": {
|
||||
"tags": "CSV,表格提取,提取,轉換"
|
||||
@@ -1336,7 +1446,7 @@
|
||||
"alreadyLoggedIn": "您已經登入了",
|
||||
"alreadyLoggedIn2": "部裝置。請先從這些裝置登出後再試一次。",
|
||||
"toManySessions": "您有太多使用中的工作階段",
|
||||
"logoutMessage": "You have been logged out."
|
||||
"logoutMessage": "您已登出。"
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF 轉為單一頁面",
|
||||
@@ -1386,7 +1496,10 @@
|
||||
"4": "自動模式 - 自動調整品質使 PDF 達到指定的檔案大小",
|
||||
"5": "指定的 PDF 檔案大小(例如 25MB, 10.8MB, 25KB)"
|
||||
},
|
||||
"submit": "壓縮"
|
||||
"submit": "壓縮",
|
||||
"method": {
|
||||
"filesize": "檔案大小"
|
||||
}
|
||||
},
|
||||
"decrypt": {
|
||||
"passwordPrompt": "此檔案已受密碼保護。請輸入密碼:",
|
||||
@@ -1557,5 +1670,83 @@
|
||||
"description": "這些 Cookies 幫助我們分析您如何使用我們的工具,好讓我們能專注在構建社群最重視的功能。儘管放心—— Stirling PDF 不會且永不追蹤您的文件"
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": "下載",
|
||||
"undo": "撤銷",
|
||||
"convert": {
|
||||
"title": "轉換",
|
||||
"settings": "設定",
|
||||
"color": "顏色",
|
||||
"greyscale": "灰度",
|
||||
"fillPage": "填充頁面",
|
||||
"pdfaDigitalSignatureWarning": "該 PDF 的憑證簽章將會在下一步被移除",
|
||||
"grayscale": "灰度"
|
||||
},
|
||||
"attachments": {
|
||||
"tags": "嵌入,附件,檔案,附加,附件管理",
|
||||
"title": "新增附件",
|
||||
"header": "新增附件",
|
||||
"submit": "新增附件"
|
||||
},
|
||||
"rightRail": {
|
||||
"selectAll": "全選",
|
||||
"deselectAll": "取消全選"
|
||||
},
|
||||
"quickAccess": {
|
||||
"sign": "簽章"
|
||||
},
|
||||
"fileUpload": {
|
||||
"loading": "載入中...",
|
||||
"or": "或"
|
||||
},
|
||||
"fileManager": {
|
||||
"fileName": "名稱",
|
||||
"fileVersion": "版本",
|
||||
"selectAll": "全選",
|
||||
"deselectAll": "取消全選",
|
||||
"deleteSelected": "刪除已選取的項目",
|
||||
"download": "下載",
|
||||
"delete": "刪除"
|
||||
},
|
||||
"sanitize": {
|
||||
"submit": "清理 PDF",
|
||||
"steps": {
|
||||
"settings": "設定"
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"title": "變更權限",
|
||||
"submit": "變更權限",
|
||||
"permissions": {
|
||||
"preventAssembly": {
|
||||
"label": "防止文件組裝"
|
||||
},
|
||||
"preventExtractContent": {
|
||||
"label": "防止內容提取"
|
||||
},
|
||||
"preventExtractForAccessibility": {
|
||||
"label": "防止為了無障礙使用而提取資料"
|
||||
},
|
||||
"preventFillInForm": {
|
||||
"label": "防止填寫表單"
|
||||
},
|
||||
"preventModify": {
|
||||
"label": "防止修改"
|
||||
},
|
||||
"preventModifyAnnotations": {
|
||||
"label": "防止註釋修改"
|
||||
},
|
||||
"preventPrinting": {
|
||||
"label": "防止列印"
|
||||
},
|
||||
"preventPrintingFaithful": {
|
||||
"label": "防止列印不同格式"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"header": {
|
||||
"title": "變更權限"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
// Web Worker for parallel thumbnail generation
|
||||
console.log('🔧 Thumbnail worker starting up...');
|
||||
|
||||
let pdfJsLoaded = false;
|
||||
|
||||
// Import PDF.js properly for worker context
|
||||
try {
|
||||
console.log('📦 Loading PDF.js locally...');
|
||||
importScripts('/pdf.js');
|
||||
|
||||
// PDF.js exports to globalThis, check both self and globalThis
|
||||
const pdfjsLib = self.pdfjsLib || globalThis.pdfjsLib;
|
||||
|
||||
if (pdfjsLib) {
|
||||
// Make it available on self for consistency
|
||||
self.pdfjsLib = pdfjsLib;
|
||||
|
||||
// Set up PDF.js worker
|
||||
self.pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||||
pdfJsLoaded = true;
|
||||
console.log('✓ PDF.js loaded successfully from local files');
|
||||
console.log('✓ PDF.js version:', self.pdfjsLib.version || 'unknown');
|
||||
} else {
|
||||
throw new Error('pdfjsLib not available after import - neither self.pdfjsLib nor globalThis.pdfjsLib found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('✗ Failed to load local PDF.js:', error.message || error);
|
||||
console.error('✗ Available globals:', Object.keys(self).filter(key => key.includes('pdf')));
|
||||
pdfJsLoaded = false;
|
||||
}
|
||||
|
||||
// Log the final status
|
||||
if (pdfJsLoaded) {
|
||||
console.log('✅ Thumbnail worker ready for PDF processing');
|
||||
} else {
|
||||
console.log('❌ Thumbnail worker failed to initialize - PDF.js not available');
|
||||
}
|
||||
|
||||
self.onmessage = async function(e) {
|
||||
const { type, data, jobId } = e.data;
|
||||
|
||||
try {
|
||||
// Handle PING for worker health check
|
||||
if (type === 'PING') {
|
||||
console.log('🏓 Worker PING received, checking PDF.js status...');
|
||||
|
||||
// Check if PDF.js is loaded before responding
|
||||
if (pdfJsLoaded && self.pdfjsLib) {
|
||||
console.log('✓ Worker PONG - PDF.js ready');
|
||||
self.postMessage({ type: 'PONG', jobId });
|
||||
} else {
|
||||
console.error('✗ PDF.js not loaded - worker not ready');
|
||||
console.error('✗ pdfJsLoaded:', pdfJsLoaded);
|
||||
console.error('✗ self.pdfjsLib:', !!self.pdfjsLib);
|
||||
self.postMessage({
|
||||
type: 'ERROR',
|
||||
jobId,
|
||||
data: { error: 'PDF.js not loaded in worker' }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'GENERATE_THUMBNAILS') {
|
||||
console.log('🖼️ Starting thumbnail generation for', data.pageNumbers.length, 'pages');
|
||||
|
||||
if (!pdfJsLoaded || !self.pdfjsLib) {
|
||||
const error = 'PDF.js not available in worker';
|
||||
console.error('✗', error);
|
||||
throw new Error(error);
|
||||
}
|
||||
const { pdfArrayBuffer, pageNumbers, scale = 0.2, quality = 0.8 } = data;
|
||||
|
||||
console.log('📄 Loading PDF document, size:', pdfArrayBuffer.byteLength, 'bytes');
|
||||
// Load PDF in worker using imported PDF.js
|
||||
const pdf = await self.pdfjsLib.getDocument({ data: pdfArrayBuffer }).promise;
|
||||
console.log('✓ PDF loaded, total pages:', pdf.numPages);
|
||||
|
||||
const thumbnails = [];
|
||||
|
||||
// Process pages in smaller batches for smoother UI
|
||||
const batchSize = 3; // Process 3 pages at once for smoother UI
|
||||
for (let i = 0; i < pageNumbers.length; i += batchSize) {
|
||||
const batch = pageNumbers.slice(i, i + batchSize);
|
||||
|
||||
const batchPromises = batch.map(async (pageNumber) => {
|
||||
try {
|
||||
console.log(`🎯 Processing page ${pageNumber}...`);
|
||||
const page = await pdf.getPage(pageNumber);
|
||||
const viewport = page.getViewport({ scale });
|
||||
console.log(`📐 Page ${pageNumber} viewport:`, viewport.width, 'x', viewport.height);
|
||||
|
||||
// Create OffscreenCanvas for better performance
|
||||
const canvas = new OffscreenCanvas(viewport.width, viewport.height);
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Failed to get 2D context from OffscreenCanvas');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
console.log(`✓ Page ${pageNumber} rendered`);
|
||||
|
||||
// Convert to blob then to base64 (more efficient than toDataURL)
|
||||
const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality });
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
|
||||
const thumbnail = `data:image/jpeg;base64,${base64}`;
|
||||
console.log(`✓ Page ${pageNumber} thumbnail generated (${base64.length} chars)`);
|
||||
|
||||
return { pageNumber, thumbnail, success: true };
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed to generate thumbnail for page ${pageNumber}:`, error.message || error);
|
||||
return { pageNumber, error: error.message || String(error), success: false };
|
||||
}
|
||||
});
|
||||
|
||||
const batchResults = await Promise.all(batchPromises);
|
||||
thumbnails.push(...batchResults);
|
||||
|
||||
// Send progress update
|
||||
console.log(`📊 Worker: Sending progress update - ${thumbnails.length}/${pageNumbers.length} completed, ${batchResults.filter(r => r.success).length} new thumbnails`);
|
||||
self.postMessage({
|
||||
type: 'PROGRESS',
|
||||
jobId,
|
||||
data: {
|
||||
completed: thumbnails.length,
|
||||
total: pageNumbers.length,
|
||||
thumbnails: batchResults.filter(r => r.success)
|
||||
}
|
||||
});
|
||||
|
||||
// Small delay between batches to keep UI smooth
|
||||
if (i + batchSize < pageNumbers.length) {
|
||||
console.log(`⏸️ Worker: Pausing 100ms before next batch (${i + batchSize}/${pageNumbers.length})`);
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // Increased to 100ms pause between batches for smoother scrolling
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
pdf.destroy();
|
||||
|
||||
self.postMessage({
|
||||
type: 'COMPLETE',
|
||||
jobId,
|
||||
data: { thumbnails: thumbnails.filter(r => r.success) }
|
||||
});
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: 'ERROR',
|
||||
jobId,
|
||||
data: { error: error.message }
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { icons } = require('@iconify-json/material-symbols');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Check for verbose flag
|
||||
const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
||||
|
||||
// Logging functions
|
||||
const info = (message) => console.log(message);
|
||||
const debug = (message) => {
|
||||
if (isVerbose) {
|
||||
console.log(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to scan codebase for LocalIcon usage
|
||||
function scanForUsedIcons() {
|
||||
const usedIcons = new Set();
|
||||
const srcDir = path.join(__dirname, '..', 'src');
|
||||
|
||||
info('🔍 Scanning codebase for LocalIcon usage...');
|
||||
|
||||
if (!fs.existsSync(srcDir)) {
|
||||
console.error('❌ Source directory not found:', srcDir);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Recursively scan all .tsx and .ts files
|
||||
function scanDirectory(dir) {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
scanDirectory(filePath);
|
||||
} else if (file.endsWith('.tsx') || file.endsWith('.ts')) {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Match LocalIcon usage: <LocalIcon icon="icon-name" ...>
|
||||
const localIconMatches = content.match(/<LocalIcon\s+[^>]*icon="([^"]+)"/g);
|
||||
if (localIconMatches) {
|
||||
localIconMatches.forEach(match => {
|
||||
const iconMatch = match.match(/icon="([^"]+)"/);
|
||||
if (iconMatch) {
|
||||
usedIcons.add(iconMatch[1]);
|
||||
debug(` Found: ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Match old material-symbols-rounded spans: <span className="material-symbols-rounded">icon-name</span>
|
||||
const spanMatches = content.match(/<span[^>]*className="[^"]*material-symbols-rounded[^"]*"[^>]*>([^<]+)<\/span>/g);
|
||||
if (spanMatches) {
|
||||
spanMatches.forEach(match => {
|
||||
const iconMatch = match.match(/>([^<]+)<\/span>/);
|
||||
if (iconMatch && iconMatch[1].trim()) {
|
||||
const iconName = iconMatch[1].trim();
|
||||
usedIcons.add(iconName);
|
||||
debug(` Found (legacy): ${iconName} in ${path.relative(srcDir, filePath)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Match Icon component usage: <Icon icon="material-symbols:icon-name" ...>
|
||||
const iconMatches = content.match(/<Icon\s+[^>]*icon="material-symbols:([^"]+)"/g);
|
||||
if (iconMatches) {
|
||||
iconMatches.forEach(match => {
|
||||
const iconMatch = match.match(/icon="material-symbols:([^"]+)"/);
|
||||
if (iconMatch) {
|
||||
usedIcons.add(iconMatch[1]);
|
||||
debug(` Found (Icon): ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scanDirectory(srcDir);
|
||||
|
||||
const iconArray = Array.from(usedIcons).sort();
|
||||
info(`📋 Found ${iconArray.length} unique icons across codebase`);
|
||||
|
||||
return iconArray;
|
||||
}
|
||||
|
||||
// Main async function
|
||||
async function main() {
|
||||
// Auto-detect used icons
|
||||
const usedIcons = scanForUsedIcons();
|
||||
|
||||
// Check if we need to regenerate (compare with existing)
|
||||
const outputPath = path.join(__dirname, '..', 'src', 'assets', 'material-symbols-icons.json');
|
||||
let needsRegeneration = true;
|
||||
|
||||
if (fs.existsSync(outputPath)) {
|
||||
try {
|
||||
const existingSet = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
||||
const existingIcons = Object.keys(existingSet.icons || {}).sort();
|
||||
const currentIcons = [...usedIcons].sort();
|
||||
|
||||
if (JSON.stringify(existingIcons) === JSON.stringify(currentIcons)) {
|
||||
needsRegeneration = false;
|
||||
info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`);
|
||||
}
|
||||
} catch {
|
||||
// If we can't parse existing file, regenerate
|
||||
needsRegeneration = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsRegeneration) {
|
||||
info('🎉 No regeneration needed!');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
info(`🔍 Extracting ${usedIcons.length} icons from Material Symbols...`);
|
||||
|
||||
// Dynamic import of ES module
|
||||
const { getIcons } = await import('@iconify/utils');
|
||||
|
||||
// Extract only our used icons from the full set
|
||||
const extractedIcons = getIcons(icons, usedIcons);
|
||||
|
||||
if (!extractedIcons) {
|
||||
console.error('❌ Failed to extract icons');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for missing icons
|
||||
const extractedIconNames = Object.keys(extractedIcons.icons || {});
|
||||
const missingIcons = usedIcons.filter(icon => !extractedIconNames.includes(icon));
|
||||
|
||||
if (missingIcons.length > 0) {
|
||||
info(`⚠️ Missing icons (${missingIcons.length}): ${missingIcons.join(', ')}`);
|
||||
info('💡 These icons don\'t exist in Material Symbols. Please use available alternatives.');
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
const outputDir = path.join(__dirname, '..', 'src', 'assets');
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the extracted icon set to a file (outputPath already defined above)
|
||||
fs.writeFileSync(outputPath, JSON.stringify(extractedIcons, null, 2));
|
||||
|
||||
info(`✅ Successfully extracted ${Object.keys(extractedIcons.icons || {}).length} icons`);
|
||||
info(`📦 Bundle size: ${Math.round(JSON.stringify(extractedIcons).length / 1024)}KB`);
|
||||
info(`💾 Saved to: ${outputPath}`);
|
||||
|
||||
// Generate TypeScript types
|
||||
const typesContent = `// Auto-generated icon types
|
||||
// This file is automatically generated by scripts/generate-icons.js
|
||||
// Do not edit manually - changes will be overwritten
|
||||
|
||||
export type MaterialSymbolIcon = ${usedIcons.map(icon => `'${icon}'`).join(' | ')};
|
||||
|
||||
export interface IconSet {
|
||||
prefix: string;
|
||||
icons: Record<string, any>;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
// Re-export the icon set as the default export with proper typing
|
||||
declare const iconSet: IconSet;
|
||||
export default iconSet;
|
||||
`;
|
||||
|
||||
const typesPath = path.join(outputDir, 'material-symbols-icons.d.ts');
|
||||
fs.writeFileSync(typesPath, typesContent);
|
||||
|
||||
info(`📝 Generated types: ${typesPath}`);
|
||||
info(`🎉 Icon extraction complete!`);
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main().catch(error => {
|
||||
console.error('❌ Script failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -24,17 +24,17 @@ try {
|
||||
// Install license-checker if not present
|
||||
try {
|
||||
require.resolve('license-checker');
|
||||
} catch (e) {
|
||||
} catch {
|
||||
console.log('📦 Installing license-checker...');
|
||||
execSync('npm install --save-dev license-checker', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// Generate license report using license-checker (more reliable)
|
||||
const licenseReport = execSync('npx license-checker --production --json', {
|
||||
const licenseReport = execSync('npx license-checker --production --json', {
|
||||
encoding: 'utf8',
|
||||
cwd: path.dirname(PACKAGE_JSON)
|
||||
});
|
||||
|
||||
|
||||
let licenseData;
|
||||
try {
|
||||
licenseData = JSON.parse(licenseReport);
|
||||
@@ -43,16 +43,16 @@ try {
|
||||
console.error('Raw output:', licenseReport.substring(0, 500) + '...');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
if (!licenseData || typeof licenseData !== 'object') {
|
||||
console.error('❌ Invalid license data structure');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
// Convert license-checker format to array
|
||||
const licenseArray = Object.entries(licenseData).map(([key, value]) => {
|
||||
let name, version;
|
||||
|
||||
|
||||
// Handle scoped packages like @mantine/core@1.0.0
|
||||
if (key.startsWith('@')) {
|
||||
const parts = key.split('@');
|
||||
@@ -64,30 +64,30 @@ try {
|
||||
name = key.substring(0, lastAtIndex);
|
||||
version = key.substring(lastAtIndex + 1);
|
||||
}
|
||||
|
||||
|
||||
// Normalize license types for edge cases
|
||||
let licenseType = value.licenses;
|
||||
|
||||
|
||||
// Handle missing or null licenses
|
||||
if (!licenseType || licenseType === null || licenseType === undefined) {
|
||||
licenseType = 'Unknown';
|
||||
}
|
||||
|
||||
|
||||
// Handle empty string licenses
|
||||
if (licenseType === '') {
|
||||
licenseType = 'Unknown';
|
||||
}
|
||||
|
||||
|
||||
// Handle array licenses (rare but possible)
|
||||
if (Array.isArray(licenseType)) {
|
||||
licenseType = licenseType.join(' AND ');
|
||||
}
|
||||
|
||||
|
||||
// Handle object licenses (fallback)
|
||||
if (typeof licenseType === 'object' && licenseType !== null) {
|
||||
licenseType = 'Unknown';
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
name: name,
|
||||
version: version || value.version || 'unknown',
|
||||
@@ -97,13 +97,13 @@ try {
|
||||
link: value.licenseUrl
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Transform to match Java backend format
|
||||
const transformedData = {
|
||||
dependencies: licenseArray.map(dep => {
|
||||
const licenseType = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : (dep.licenseType || 'Unknown');
|
||||
const licenseUrl = dep.link || getLicenseUrl(licenseType);
|
||||
|
||||
|
||||
return {
|
||||
moduleName: dep.name,
|
||||
moduleUrl: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`,
|
||||
@@ -113,29 +113,29 @@ try {
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
// Log summary of license types found
|
||||
const licenseSummary = licenseArray.reduce((acc, dep) => {
|
||||
const license = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : (dep.licenseType || 'Unknown');
|
||||
acc[license] = (acc[license] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
console.log('📊 License types found:');
|
||||
Object.entries(licenseSummary).forEach(([license, count]) => {
|
||||
console.log(` ${license}: ${count} packages`);
|
||||
});
|
||||
|
||||
|
||||
// Log any complex or unusual license formats for debugging
|
||||
const complexLicenses = licenseArray.filter(dep =>
|
||||
const complexLicenses = licenseArray.filter(dep =>
|
||||
dep.licenseType && (
|
||||
dep.licenseType.includes('AND') ||
|
||||
dep.licenseType.includes('OR') ||
|
||||
dep.licenseType.includes('AND') ||
|
||||
dep.licenseType.includes('OR') ||
|
||||
dep.licenseType === 'Unknown' ||
|
||||
dep.licenseType.includes('SEE LICENSE')
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
if (complexLicenses.length > 0) {
|
||||
console.log('\n🔍 Complex/Edge case licenses detected:');
|
||||
complexLicenses.forEach(dep => {
|
||||
@@ -150,7 +150,7 @@ try {
|
||||
problematicLicenses.forEach(warning => {
|
||||
console.log(` ${warning.message}`);
|
||||
});
|
||||
|
||||
|
||||
// Write license warnings to a separate file for CI/CD
|
||||
const warningsFile = path.join(__dirname, '..', 'src', 'assets', 'license-warnings.json');
|
||||
fs.writeFileSync(warningsFile, JSON.stringify({
|
||||
@@ -164,11 +164,11 @@ try {
|
||||
|
||||
// Write to file
|
||||
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(transformedData, null, 4));
|
||||
|
||||
|
||||
console.log(`✅ License report generated successfully!`);
|
||||
console.log(`📄 Found ${transformedData.dependencies.length} dependencies`);
|
||||
console.log(`💾 Saved to: ${OUTPUT_FILE}`);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error generating license report:', error.message);
|
||||
process.exit(1);
|
||||
@@ -179,9 +179,10 @@ try {
|
||||
*/
|
||||
function getLicenseUrl(licenseType) {
|
||||
if (!licenseType || licenseType === 'Unknown') return '';
|
||||
|
||||
|
||||
const licenseUrls = {
|
||||
'MIT': 'https://opensource.org/licenses/MIT',
|
||||
'MIT*': 'https://opensource.org/licenses/MIT',
|
||||
'Apache-2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
|
||||
'Apache License 2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
|
||||
'BSD-3-Clause': 'https://opensource.org/licenses/BSD-3-Clause',
|
||||
@@ -206,12 +207,12 @@ function getLicenseUrl(licenseType) {
|
||||
'Public Domain': 'https://creativecommons.org/publicdomain/zero/1.0/',
|
||||
'UNLICENSED': ''
|
||||
};
|
||||
|
||||
|
||||
// Try exact match first
|
||||
if (licenseUrls[licenseType]) {
|
||||
return licenseUrls[licenseType];
|
||||
}
|
||||
|
||||
|
||||
// Try case-insensitive match
|
||||
const lowerType = licenseType.toLowerCase();
|
||||
for (const [key, url] of Object.entries(licenseUrls)) {
|
||||
@@ -219,16 +220,16 @@ function getLicenseUrl(licenseType) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle complex SPDX expressions like "(MIT AND Zlib)" or "(MIT OR CC0-1.0)"
|
||||
if (licenseType.includes('AND') || licenseType.includes('OR')) {
|
||||
// Extract the first license from compound expressions for URL
|
||||
const match = licenseType.match(/\(?\s*([A-Za-z0-9\-\.]+)/);
|
||||
const match = licenseType.match(/\(?\s*([A-Za-z0-9\-.]+)/);
|
||||
if (match && licenseUrls[match[1]]) {
|
||||
return licenseUrls[match[1]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// For non-standard licenses, return empty string (will use package link if available)
|
||||
return '';
|
||||
}
|
||||
@@ -238,7 +239,7 @@ function getLicenseUrl(licenseType) {
|
||||
*/
|
||||
function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
const warnings = [];
|
||||
|
||||
|
||||
// Define problematic license patterns
|
||||
const problematicLicenses = {
|
||||
// Copyleft licenses
|
||||
@@ -248,7 +249,7 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
'LGPL-3.0': 'Weak copyleft license - may require source disclosure for modifications',
|
||||
'AGPL-3.0': 'Network copyleft license - requires source disclosure for network use',
|
||||
'AGPL-1.0': 'Network copyleft license - requires source disclosure for network use',
|
||||
|
||||
|
||||
// Other potentially problematic licenses
|
||||
'WTFPL': 'Potentially problematic license - legal uncertainty',
|
||||
'CC-BY-SA-4.0': 'ShareAlike license - requires derivative works to use same license',
|
||||
@@ -267,47 +268,47 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
'UNLICENSED': 'No license specified - usage rights unclear',
|
||||
'Unknown': 'License not detected - manual review required'
|
||||
};
|
||||
|
||||
|
||||
// Known good licenses (no warnings needed)
|
||||
const goodLicenses = new Set([
|
||||
'MIT', 'Apache-2.0', 'Apache License 2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD',
|
||||
'MIT', 'MIT*', 'Apache-2.0', 'Apache License 2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD',
|
||||
'ISC', 'CC0-1.0', 'Public Domain', 'Unlicense', '0BSD', 'BlueOak-1.0.0',
|
||||
'Zlib', 'Artistic-2.0', 'Python-2.0', 'Ruby', 'MPL-2.0', 'CC-BY-4.0',
|
||||
'SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE'
|
||||
]);
|
||||
|
||||
|
||||
// Helper function to normalize license names for comparison
|
||||
function normalizeLicense(license) {
|
||||
return license
|
||||
.replace(/-or-later$/, '') // Remove -or-later suffix
|
||||
.replace(/\+$/, '') // Remove + suffix
|
||||
.replace(/\+$/, '') // Remove + suffix
|
||||
.trim();
|
||||
}
|
||||
|
||||
|
||||
// Check each license type
|
||||
Object.entries(licenseSummary).forEach(([license, count]) => {
|
||||
// Skip known good licenses
|
||||
if (goodLicenses.has(license)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if this license only affects our own packages
|
||||
const affectedPackages = licenseArray.filter(dep => {
|
||||
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType;
|
||||
return depLicense === license;
|
||||
});
|
||||
|
||||
const isOnlyOurPackages = affectedPackages.every(dep =>
|
||||
dep.name === 'frontend' ||
|
||||
|
||||
const isOnlyOurPackages = affectedPackages.every(dep =>
|
||||
dep.name === 'frontend' ||
|
||||
dep.name.toLowerCase().includes('stirling-pdf') ||
|
||||
dep.name.toLowerCase().includes('stirling_pdf') ||
|
||||
dep.name.toLowerCase().includes('stirlingpdf')
|
||||
);
|
||||
|
||||
|
||||
if (isOnlyOurPackages && (license === 'UNLICENSED' || license.startsWith('SEE LICENSE IN'))) {
|
||||
return; // Skip warnings for our own Stirling-PDF packages
|
||||
}
|
||||
|
||||
|
||||
// Check for compound licenses like "(MIT AND Zlib)" or "(MIT OR CC0-1.0)"
|
||||
if (license.includes('AND') || license.includes('OR')) {
|
||||
// For OR licenses, check if there's at least one acceptable license option
|
||||
@@ -317,23 +318,23 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
.replace(/[()]/g, '') // Remove parentheses
|
||||
.split(' OR ')
|
||||
.map(component => component.trim());
|
||||
|
||||
|
||||
// Check if any component is in the goodLicenses set (with normalization)
|
||||
const hasGoodLicense = orComponents.some(component => {
|
||||
const normalized = normalizeLicense(component);
|
||||
return goodLicenses.has(component) || goodLicenses.has(normalized);
|
||||
});
|
||||
|
||||
|
||||
if (hasGoodLicense) {
|
||||
return; // Skip warning - can use the good license option
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// For AND licenses or OR licenses with no good options, check for problematic components
|
||||
const hasProblematicComponent = Object.keys(problematicLicenses).some(problematic =>
|
||||
const hasProblematicComponent = Object.keys(problematicLicenses).some(problematic =>
|
||||
license.includes(problematic)
|
||||
);
|
||||
|
||||
|
||||
if (hasProblematicComponent) {
|
||||
const affectedPackages = licenseArray
|
||||
.filter(dep => {
|
||||
@@ -345,12 +346,12 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
version: dep.version,
|
||||
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`
|
||||
}));
|
||||
|
||||
|
||||
const licenseType = license.includes('AND') ? 'AND' : 'OR';
|
||||
const reason = licenseType === 'AND'
|
||||
const reason = licenseType === 'AND'
|
||||
? 'Compound license with AND requirement - all components must be compatible'
|
||||
: 'Compound license with potentially problematic components and no good fallback options';
|
||||
|
||||
|
||||
warnings.push({
|
||||
message: `📋 This PR contains ${count} package${count > 1 ? 's' : ''} with compound license "${license}" - manual review recommended`,
|
||||
licenseType: license,
|
||||
@@ -362,7 +363,7 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check for exact matches with problematic licenses
|
||||
if (problematicLicenses[license]) {
|
||||
const affectedPackages = licenseArray
|
||||
@@ -375,10 +376,10 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
version: dep.version,
|
||||
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`
|
||||
}));
|
||||
|
||||
|
||||
const packageList = affectedPackages.map(pkg => pkg.name).slice(0, 5).join(', ') + (affectedPackages.length > 5 ? `, and ${affectedPackages.length - 5} more` : '');
|
||||
const licenseUrl = getLicenseUrl(license) || 'https://opensource.org/licenses';
|
||||
|
||||
|
||||
warnings.push({
|
||||
message: `⚠️ This PR contains ${count} package${count > 1 ? 's' : ''} with license type [${license}](${licenseUrl}) - ${problematicLicenses[license]}. Affected packages: ${packageList}`,
|
||||
licenseType: license,
|
||||
@@ -399,7 +400,7 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
version: dep.version,
|
||||
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`
|
||||
}));
|
||||
|
||||
|
||||
warnings.push({
|
||||
message: `❓ This PR contains ${count} package${count > 1 ? 's' : ''} with unknown license type "${license}" - manual review required`,
|
||||
licenseType: license,
|
||||
@@ -410,6 +411,6 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return warnings;
|
||||
}
|
||||
}
|
||||
|
||||
+41
-20
@@ -1,23 +1,34 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider';
|
||||
import { FileContextProvider } from './contexts/FileContext';
|
||||
import { FilesModalProvider } from './contexts/FilesModalContext';
|
||||
import HomePage from './pages/HomePage';
|
||||
import React, { Suspense } from "react";
|
||||
import { RainbowThemeProvider } from "./components/shared/RainbowThemeProvider";
|
||||
import { FileContextProvider } from "./contexts/FileContext";
|
||||
import { NavigationProvider } from "./contexts/NavigationContext";
|
||||
import { FilesModalProvider } from "./contexts/FilesModalContext";
|
||||
import { ToolWorkflowProvider } from "./contexts/ToolWorkflowContext";
|
||||
import { SidebarProvider } from "./contexts/SidebarContext";
|
||||
import ErrorBoundary from "./components/shared/ErrorBoundary";
|
||||
import HomePage from "./pages/HomePage";
|
||||
|
||||
// Import global styles
|
||||
import './styles/tailwind.css';
|
||||
import './index.css';
|
||||
import "./styles/tailwind.css";
|
||||
import "./styles/cookieconsent.css";
|
||||
import "./index.css";
|
||||
import { RightRailProvider } from "./contexts/RightRailContext";
|
||||
|
||||
// Import file ID debugging helpers (development only)
|
||||
import "./utils/fileIdSafety";
|
||||
|
||||
// Loading component for i18next suspense
|
||||
const LoadingFallback = () => (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
fontSize: '18px',
|
||||
color: '#666'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
fontSize: "18px",
|
||||
color: "#666",
|
||||
}}
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
@@ -26,11 +37,21 @@ export default function App() {
|
||||
return (
|
||||
<Suspense fallback={<LoadingFallback />}>
|
||||
<RainbowThemeProvider>
|
||||
<FileContextProvider enableUrlSync={true} enablePersistence={true}>
|
||||
<FilesModalProvider>
|
||||
<HomePage />
|
||||
</FilesModalProvider>
|
||||
</FileContextProvider>
|
||||
<ErrorBoundary>
|
||||
<FileContextProvider enableUrlSync={true} enablePersistence={true}>
|
||||
<NavigationProvider>
|
||||
<FilesModalProvider>
|
||||
<ToolWorkflowProvider>
|
||||
<SidebarProvider>
|
||||
<RightRailProvider>
|
||||
<HomePage />
|
||||
</RightRailProvider>
|
||||
</SidebarProvider>
|
||||
</ToolWorkflowProvider>
|
||||
</FilesModalProvider>
|
||||
</NavigationProvider>
|
||||
</FileContextProvider>
|
||||
</ErrorBoundary>
|
||||
</RainbowThemeProvider>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "@atlaskit/pragmatic-drag-and-drop",
|
||||
"moduleUrl": "https://github.com/atlassian/pragmatic-drag-and-drop",
|
||||
"moduleVersion": "1.7.4",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "@babel/code-frame",
|
||||
"moduleUrl": "https://github.com/babel/babel",
|
||||
@@ -59,7 +66,7 @@
|
||||
{
|
||||
"moduleName": "@babel/parser",
|
||||
"moduleUrl": "https://github.com/babel/babel",
|
||||
"moduleVersion": "7.27.3",
|
||||
"moduleVersion": "7.28.3",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
@@ -87,7 +94,7 @@
|
||||
{
|
||||
"moduleName": "@babel/types",
|
||||
"moduleUrl": "https://github.com/babel/babel",
|
||||
"moduleVersion": "7.27.3",
|
||||
"moduleVersion": "7.28.2",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
@@ -217,6 +224,20 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@iconify/react",
|
||||
"moduleUrl": "https://github.com/iconify/iconify",
|
||||
"moduleVersion": "6.0.0",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@iconify/types",
|
||||
"moduleUrl": "https://github.com/iconify/iconify",
|
||||
"moduleVersion": "2.0.0",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@isaacs/fs-minipass",
|
||||
"moduleUrl": "https://github.com/npm/fs-minipass",
|
||||
@@ -364,6 +385,13 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@posthog/core",
|
||||
"moduleUrl": "https://github.com/PostHog/posthog-js",
|
||||
"moduleVersion": "1.0.2",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@tailwindcss/node",
|
||||
"moduleUrl": "https://github.com/tailwindlabs/tailwindcss",
|
||||
@@ -399,6 +427,20 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@tanstack/react-virtual",
|
||||
"moduleUrl": "https://github.com/TanStack/virtual",
|
||||
"moduleVersion": "3.13.12",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@tanstack/virtual-core",
|
||||
"moduleUrl": "https://github.com/TanStack/virtual",
|
||||
"moduleVersion": "3.13.12",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "@testing-library/dom",
|
||||
"moduleUrl": "https://github.com/testing-library/dom-testing-library",
|
||||
@@ -567,6 +609,13 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "bind-event-listener",
|
||||
"moduleUrl": "https://github.com/alexreardon/bind-event-listener",
|
||||
"moduleVersion": "3.0.0",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "brace-expansion",
|
||||
"moduleUrl": "https://github.com/juliangruber/brace-expansion",
|
||||
@@ -700,6 +749,13 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "core-js",
|
||||
"moduleUrl": "https://github.com/zloirock/core-js",
|
||||
"moduleVersion": "3.45.1",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "core-util-is",
|
||||
"moduleUrl": "https://github.com/isaacs/core-util-is",
|
||||
@@ -882,6 +938,13 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "fflate",
|
||||
"moduleUrl": "https://github.com/101arrowz/fflate",
|
||||
"moduleVersion": "0.4.8",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "file-selector",
|
||||
"moduleUrl": "https://github.com/react-dropzone/file-selector",
|
||||
@@ -1246,13 +1309,6 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "material-symbols",
|
||||
"moduleUrl": "https://github.com/marella/material-symbols",
|
||||
"moduleVersion": "0.33.0",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "math-intrinsics",
|
||||
"moduleUrl": "https://github.com/es-shims/math-intrinsics",
|
||||
@@ -1494,7 +1550,21 @@
|
||||
{
|
||||
"moduleName": "postcss",
|
||||
"moduleUrl": "https://github.com/postcss/postcss",
|
||||
"moduleVersion": "8.5.3",
|
||||
"moduleVersion": "8.5.6",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "posthog-js",
|
||||
"moduleUrl": "https://github.com/PostHog/posthog-js",
|
||||
"moduleVersion": "1.261.0",
|
||||
"moduleLicense": "MIT*",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "preact",
|
||||
"moduleUrl": "https://github.com/preactjs/preact",
|
||||
"moduleVersion": "10.27.1",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
@@ -1526,6 +1596,13 @@
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "raf-schd",
|
||||
"moduleUrl": "https://github.com/alexreardon/raf-schd",
|
||||
"moduleVersion": "4.0.3",
|
||||
"moduleLicense": "MIT",
|
||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "react-dom",
|
||||
"moduleUrl": "https://github.com/facebook/react",
|
||||
@@ -1886,7 +1963,7 @@
|
||||
{
|
||||
"moduleName": "typescript",
|
||||
"moduleUrl": "https://github.com/microsoft/TypeScript",
|
||||
"moduleVersion": "5.8.3",
|
||||
"moduleVersion": "5.9.2",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
@@ -1953,6 +2030,13 @@
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "web-vitals",
|
||||
"moduleUrl": "https://github.com/GoogleChrome/web-vitals",
|
||||
"moduleVersion": "4.2.4",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "webidl-conversions",
|
||||
"moduleUrl": "https://github.com/jsdom/webidl-conversions",
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
import { Command, CommandSequence } from '../hooks/useUndoRedo';
|
||||
import { PDFDocument, PDFPage } from '../types/pageEditor';
|
||||
|
||||
// Base class for page operations
|
||||
abstract class PageCommand implements Command {
|
||||
protected pdfDocument: PDFDocument;
|
||||
protected setPdfDocument: (doc: PDFDocument) => void;
|
||||
protected previousState: PDFDocument;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void
|
||||
) {
|
||||
this.pdfDocument = pdfDocument;
|
||||
this.setPdfDocument = setPdfDocument;
|
||||
this.previousState = JSON.parse(JSON.stringify(pdfDocument)); // Deep clone
|
||||
}
|
||||
|
||||
abstract execute(): void;
|
||||
abstract description: string;
|
||||
|
||||
undo(): void {
|
||||
this.setPdfDocument(this.previousState);
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate pages command
|
||||
export class RotatePagesCommand extends PageCommand {
|
||||
private pageIds: string[];
|
||||
private rotation: number;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void,
|
||||
pageIds: string[],
|
||||
rotation: number
|
||||
) {
|
||||
super(pdfDocument, setPdfDocument);
|
||||
this.pageIds = pageIds;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const updatedPages = this.pdfDocument.pages.map(page => {
|
||||
if (this.pageIds.includes(page.id)) {
|
||||
return { ...page, rotation: page.rotation + this.rotation };
|
||||
}
|
||||
return page;
|
||||
});
|
||||
|
||||
this.setPdfDocument({ ...this.pdfDocument, pages: updatedPages });
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const direction = this.rotation > 0 ? 'right' : 'left';
|
||||
return `Rotate ${this.pageIds.length} page(s) ${direction}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete pages command
|
||||
export class DeletePagesCommand extends PageCommand {
|
||||
private pageIds: string[];
|
||||
private deletedPages: PDFPage[];
|
||||
private deletedPositions: Map<string, number>;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void,
|
||||
pageIds: string[]
|
||||
) {
|
||||
super(pdfDocument, setPdfDocument);
|
||||
this.pageIds = pageIds;
|
||||
this.deletedPages = [];
|
||||
this.deletedPositions = new Map();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Store deleted pages and their positions for undo
|
||||
this.deletedPages = this.pdfDocument.pages.filter(page =>
|
||||
this.pageIds.includes(page.id)
|
||||
);
|
||||
|
||||
this.deletedPages.forEach(page => {
|
||||
const index = this.pdfDocument.pages.findIndex(p => p.id === page.id);
|
||||
this.deletedPositions.set(page.id, index);
|
||||
});
|
||||
|
||||
const updatedPages = this.pdfDocument.pages
|
||||
.filter(page => !this.pageIds.includes(page.id))
|
||||
.map((page, index) => ({ ...page, pageNumber: index + 1 }));
|
||||
|
||||
this.setPdfDocument({
|
||||
...this.pdfDocument,
|
||||
pages: updatedPages,
|
||||
totalPages: updatedPages.length
|
||||
});
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Simply restore to the previous state (before deletion)
|
||||
this.setPdfDocument(this.previousState);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Delete ${this.pageIds.length} page(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Move pages command
|
||||
export class MovePagesCommand extends PageCommand {
|
||||
private pageIds: string[];
|
||||
private targetIndex: number;
|
||||
private originalIndices: Map<string, number>;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void,
|
||||
pageIds: string[],
|
||||
targetIndex: number
|
||||
) {
|
||||
super(pdfDocument, setPdfDocument);
|
||||
this.pageIds = pageIds;
|
||||
this.targetIndex = targetIndex;
|
||||
this.originalIndices = new Map();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Store original positions
|
||||
this.pageIds.forEach(pageId => {
|
||||
const index = this.pdfDocument.pages.findIndex(p => p.id === pageId);
|
||||
this.originalIndices.set(pageId, index);
|
||||
});
|
||||
|
||||
let newPages = [...this.pdfDocument.pages];
|
||||
const pagesToMove = this.pageIds
|
||||
.map(id => this.pdfDocument.pages.find(p => p.id === id))
|
||||
.filter((page): page is PDFPage => page !== undefined);
|
||||
|
||||
// Remove pages to move
|
||||
newPages = newPages.filter(page => !this.pageIds.includes(page.id));
|
||||
|
||||
// Insert pages at target position
|
||||
newPages.splice(this.targetIndex, 0, ...pagesToMove);
|
||||
|
||||
// Update page numbers
|
||||
newPages = newPages.map((page, index) => ({
|
||||
...page,
|
||||
pageNumber: index + 1
|
||||
}));
|
||||
|
||||
this.setPdfDocument({ ...this.pdfDocument, pages: newPages });
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Move ${this.pageIds.length} page(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Reorder single page command (for drag-and-drop)
|
||||
export class ReorderPageCommand extends PageCommand {
|
||||
private pageId: string;
|
||||
private targetIndex: number;
|
||||
private originalIndex: number;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void,
|
||||
pageId: string,
|
||||
targetIndex: number
|
||||
) {
|
||||
super(pdfDocument, setPdfDocument);
|
||||
this.pageId = pageId;
|
||||
this.targetIndex = targetIndex;
|
||||
this.originalIndex = pdfDocument.pages.findIndex(p => p.id === pageId);
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const newPages = [...this.pdfDocument.pages];
|
||||
const [movedPage] = newPages.splice(this.originalIndex, 1);
|
||||
newPages.splice(this.targetIndex, 0, movedPage);
|
||||
|
||||
// Update page numbers
|
||||
const updatedPages = newPages.map((page, index) => ({
|
||||
...page,
|
||||
pageNumber: index + 1
|
||||
}));
|
||||
|
||||
this.setPdfDocument({ ...this.pdfDocument, pages: updatedPages });
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Reorder page ${this.originalIndex + 1} to position ${this.targetIndex + 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle split markers command
|
||||
export class ToggleSplitCommand extends PageCommand {
|
||||
private pageIds: string[];
|
||||
private previousSplitStates: Map<string, boolean>;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void,
|
||||
pageIds: string[]
|
||||
) {
|
||||
super(pdfDocument, setPdfDocument);
|
||||
this.pageIds = pageIds;
|
||||
this.previousSplitStates = new Map();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Store previous split states
|
||||
this.pageIds.forEach(pageId => {
|
||||
const page = this.pdfDocument.pages.find(p => p.id === pageId);
|
||||
if (page) {
|
||||
this.previousSplitStates.set(pageId, !!page.splitBefore);
|
||||
}
|
||||
});
|
||||
|
||||
const updatedPages = this.pdfDocument.pages.map(page => {
|
||||
if (this.pageIds.includes(page.id)) {
|
||||
return { ...page, splitBefore: !page.splitBefore };
|
||||
}
|
||||
return page;
|
||||
});
|
||||
|
||||
this.setPdfDocument({ ...this.pdfDocument, pages: updatedPages });
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
const updatedPages = this.pdfDocument.pages.map(page => {
|
||||
if (this.pageIds.includes(page.id)) {
|
||||
const previousState = this.previousSplitStates.get(page.id);
|
||||
return { ...page, splitBefore: previousState };
|
||||
}
|
||||
return page;
|
||||
});
|
||||
|
||||
this.setPdfDocument({ ...this.pdfDocument, pages: updatedPages });
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Toggle split markers for ${this.pageIds.length} page(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add pages command (for inserting new files)
|
||||
export class AddPagesCommand extends PageCommand {
|
||||
private newPages: PDFPage[];
|
||||
private insertIndex: number;
|
||||
|
||||
constructor(
|
||||
pdfDocument: PDFDocument,
|
||||
setPdfDocument: (doc: PDFDocument) => void,
|
||||
newPages: PDFPage[],
|
||||
insertIndex: number = -1 // -1 means append to end
|
||||
) {
|
||||
super(pdfDocument, setPdfDocument);
|
||||
this.newPages = newPages;
|
||||
this.insertIndex = insertIndex === -1 ? pdfDocument.pages.length : insertIndex;
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const newPagesArray = [...this.pdfDocument.pages];
|
||||
newPagesArray.splice(this.insertIndex, 0, ...this.newPages);
|
||||
|
||||
// Update page numbers for all pages
|
||||
const updatedPages = newPagesArray.map((page, index) => ({
|
||||
...page,
|
||||
pageNumber: index + 1
|
||||
}));
|
||||
|
||||
this.setPdfDocument({
|
||||
...this.pdfDocument,
|
||||
pages: updatedPages,
|
||||
totalPages: updatedPages.length
|
||||
});
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
const updatedPages = this.pdfDocument.pages
|
||||
.filter(page => !this.newPages.some(newPage => newPage.id === page.id))
|
||||
.map((page, index) => ({ ...page, pageNumber: index + 1 }));
|
||||
|
||||
this.setPdfDocument({
|
||||
...this.pdfDocument,
|
||||
pages: updatedPages,
|
||||
totalPages: updatedPages.length
|
||||
});
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Add ${this.newPages.length} page(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Command sequence for bulk operations
|
||||
export class PageCommandSequence implements CommandSequence {
|
||||
commands: Command[];
|
||||
description: string;
|
||||
|
||||
constructor(commands: Command[], description?: string) {
|
||||
this.commands = commands;
|
||||
this.description = description || `Execute ${commands.length} operations`;
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
this.commands.forEach(command => command.execute());
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Undo in reverse order
|
||||
[...this.commands].reverse().forEach(command => command.undo());
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import React from "react";
|
||||
import { Card, Stack, Text, Group, Badge, Button, Box, Image, ThemeIcon } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
||||
import StorageIcon from "@mui/icons-material/Storage";
|
||||
|
||||
import { FileWithUrl } from "../types/file";
|
||||
import { getFileSize, getFileDate } from "../utils/fileUtils";
|
||||
import { useIndexedDBThumbnail } from "../hooks/useIndexedDBThumbnail";
|
||||
|
||||
interface FileCardProps {
|
||||
file: FileWithUrl;
|
||||
onRemove: () => void;
|
||||
onDoubleClick?: () => void;
|
||||
}
|
||||
|
||||
const FileCard: React.FC<FileCardProps> = ({ file, onRemove, onDoubleClick }) => {
|
||||
const { t } = useTranslation();
|
||||
const { thumbnail: thumb, isGenerating } = useIndexedDBThumbnail(file);
|
||||
|
||||
return (
|
||||
<Card
|
||||
shadow="xs"
|
||||
radius="md"
|
||||
withBorder
|
||||
p="xs"
|
||||
style={{
|
||||
width: 225,
|
||||
minWidth: 180,
|
||||
maxWidth: 260,
|
||||
cursor: onDoubleClick ? "pointer" : undefined
|
||||
}}
|
||||
onDoubleClick={onDoubleClick}
|
||||
>
|
||||
<Stack gap={6} align="center">
|
||||
<Box
|
||||
style={{
|
||||
border: "2px solid #e0e0e0",
|
||||
borderRadius: 8,
|
||||
width: 90,
|
||||
height: 120,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
margin: "0 auto",
|
||||
background: "#fafbfc",
|
||||
}}
|
||||
>
|
||||
{thumb ? (
|
||||
<Image
|
||||
src={thumb}
|
||||
alt="PDF thumbnail"
|
||||
height={110}
|
||||
width={80}
|
||||
fit="contain"
|
||||
radius="sm"
|
||||
/>
|
||||
) : isGenerating ? (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<div style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
border: '2px solid #ddd',
|
||||
borderTop: '2px solid #666',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 1s linear infinite',
|
||||
marginBottom: 8
|
||||
}} />
|
||||
<Text size="xs" c="dimmed">Generating...</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<ThemeIcon
|
||||
variant="light"
|
||||
color={file.size > 100 * 1024 * 1024 ? "orange" : "red"}
|
||||
size={60}
|
||||
radius="sm"
|
||||
style={{ display: "flex", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<PictureAsPdfIcon style={{ fontSize: 40 }} />
|
||||
</ThemeIcon>
|
||||
{file.size > 100 * 1024 * 1024 && (
|
||||
<Text size="xs" c="dimmed" mt={4}>Large File</Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Text fw={500} size="sm" lineClamp={1} ta="center">
|
||||
{file.name}
|
||||
</Text>
|
||||
|
||||
<Group gap="xs" justify="center">
|
||||
<Badge color="gray" variant="light" size="sm">
|
||||
{getFileSize(file)}
|
||||
</Badge>
|
||||
<Badge color="blue" variant="light" size="sm">
|
||||
{getFileDate(file)}
|
||||
</Badge>
|
||||
{file.storedInIndexedDB && (
|
||||
<Badge
|
||||
color="green"
|
||||
variant="light"
|
||||
size="sm"
|
||||
leftSection={<StorageIcon style={{ fontSize: 12 }} />}
|
||||
>
|
||||
DB
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
color="red"
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={onRemove}
|
||||
mt={4}
|
||||
>
|
||||
{t("delete", "Remove")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileCard;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { Modal } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { FileWithUrl } from '../types/file';
|
||||
import { StirlingFileStub } from '../types/fileContext';
|
||||
import { useFileManager } from '../hooks/useFileManager';
|
||||
import { useFilesModalContext } from '../contexts/FilesModalContext';
|
||||
import { Tool } from '../types/tool';
|
||||
@@ -15,12 +15,12 @@ interface FileManagerProps {
|
||||
}
|
||||
|
||||
const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
||||
const { isFilesModalOpen, closeFilesModal, onFilesSelect } = useFilesModalContext();
|
||||
const [recentFiles, setRecentFiles] = useState<FileWithUrl[]>([]);
|
||||
const { isFilesModalOpen, closeFilesModal, onFileUpload, onRecentFileSelect } = useFilesModalContext();
|
||||
const [recentFiles, setRecentFiles] = useState<StirlingFileStub[]>([]);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
const { loadRecentFiles, handleRemoveFile, storeFile, convertToFile } = useFileManager();
|
||||
const { loadRecentFiles, handleRemoveFile } = useFileManager();
|
||||
|
||||
// File management handlers
|
||||
const isFileSupported = useCallback((fileName: string) => {
|
||||
@@ -34,30 +34,26 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
||||
setRecentFiles(files);
|
||||
}, [loadRecentFiles]);
|
||||
|
||||
const handleFilesSelected = useCallback(async (files: FileWithUrl[]) => {
|
||||
const handleRecentFilesSelected = useCallback(async (files: StirlingFileStub[]) => {
|
||||
try {
|
||||
const fileObjects = await Promise.all(
|
||||
files.map(async (fileWithUrl) => {
|
||||
return await convertToFile(fileWithUrl);
|
||||
})
|
||||
);
|
||||
onFilesSelect(fileObjects);
|
||||
// Use StirlingFileStubs directly - preserves all metadata!
|
||||
onRecentFileSelect(files);
|
||||
} catch (error) {
|
||||
console.error('Failed to process selected files:', error);
|
||||
}
|
||||
}, [convertToFile, onFilesSelect]);
|
||||
}, [onRecentFileSelect]);
|
||||
|
||||
const handleNewFileUpload = useCallback(async (files: File[]) => {
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
// Files will get IDs assigned through onFilesSelect -> FileContext addFiles
|
||||
onFilesSelect(files);
|
||||
onFileUpload(files);
|
||||
await refreshRecentFiles();
|
||||
} catch (error) {
|
||||
console.error('Failed to process dropped files:', error);
|
||||
}
|
||||
}
|
||||
}, [onFilesSelect, refreshRecentFiles]);
|
||||
}, [onFileUpload, refreshRecentFiles]);
|
||||
|
||||
const handleRemoveFileByIndex = useCallback(async (index: number) => {
|
||||
await handleRemoveFile(index, recentFiles, setRecentFiles);
|
||||
@@ -82,14 +78,11 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
||||
// Cleanup any blob URLs when component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Clean up blob URLs from recent files
|
||||
recentFiles.forEach(file => {
|
||||
if (file.url && file.url.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(file.url);
|
||||
}
|
||||
});
|
||||
// StoredFileMetadata doesn't have blob URLs, so no cleanup needed
|
||||
// Blob URLs are managed by FileContext and tool operations
|
||||
console.log('FileManager unmounting - FileContext handles blob URL cleanup');
|
||||
};
|
||||
}, [recentFiles]);
|
||||
}, []);
|
||||
|
||||
// Modal size constants for consistent scaling
|
||||
const modalHeight = '80vh';
|
||||
@@ -104,7 +97,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
||||
onClose={closeFilesModal}
|
||||
size={isMobile ? "100%" : "auto"}
|
||||
centered
|
||||
radius={30}
|
||||
radius="md"
|
||||
className="overflow-hidden p-0"
|
||||
withCloseButton={false}
|
||||
styles={{
|
||||
@@ -130,14 +123,14 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
||||
onDrop={handleNewFileUpload}
|
||||
onDragEnter={() => setIsDragging(true)}
|
||||
onDragLeave={() => setIsDragging(false)}
|
||||
accept={["*/*"] as any}
|
||||
accept={{}}
|
||||
multiple={true}
|
||||
activateOnClick={false}
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
borderRadius: '30px',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
backgroundColor: 'var(--bg-file-manager)'
|
||||
}}
|
||||
styles={{
|
||||
@@ -146,13 +139,13 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
||||
>
|
||||
<FileManagerProvider
|
||||
recentFiles={recentFiles}
|
||||
onFilesSelected={handleFilesSelected}
|
||||
onRecentFilesSelected={handleRecentFilesSelected}
|
||||
onNewFilesSelect={handleNewFileUpload}
|
||||
onClose={closeFilesModal}
|
||||
isFileSupported={isFileSupported}
|
||||
isOpen={isFilesModalOpen}
|
||||
onFileRemove={handleRemoveFileByIndex}
|
||||
modalHeight={modalHeight}
|
||||
storeFile={storeFile}
|
||||
refreshRecentFiles={refreshRecentFiles}
|
||||
>
|
||||
{isMobile ? <MobileLayout /> : <DesktopLayout />}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
/* =========================
|
||||
FileEditor Card UI Styles
|
||||
========================= */
|
||||
|
||||
.card {
|
||||
background: var(--file-card-bg);
|
||||
border-radius: 0.0625rem;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.18s ease, outline-color 0.18s ease, transform 0.18s ease;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.card[data-selected="true"] {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* While dragging */
|
||||
.card.dragging,
|
||||
.card:global(.dragging) {
|
||||
outline: 1px solid var(--border-strong);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* -------- Header -------- */
|
||||
.header {
|
||||
height: 2.25rem;
|
||||
border-radius: 0.0625rem 0.0625rem 0 0;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 44px;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
user-select: none;
|
||||
background: var(--bg-toolbar);
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
}
|
||||
|
||||
.headerResting {
|
||||
background: #3B4B6E; /* dark blue for unselected in light mode */
|
||||
color: #FFFFFF;
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
}
|
||||
|
||||
.headerSelected {
|
||||
background: var(--header-selected-bg);
|
||||
color: var(--header-selected-fg);
|
||||
border-bottom: 1px solid var(--header-selected-bg);
|
||||
}
|
||||
|
||||
/* Selected border color in light mode */
|
||||
:global([data-mantine-color-scheme="light"]) .card[data-selected="true"] {
|
||||
outline-color: var(--card-selected-border);
|
||||
}
|
||||
|
||||
/* Reserve space for checkbox instead of logo */
|
||||
.logoMark {
|
||||
margin-left: 8px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.headerIndex {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.kebab {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
/* Menu dropdown */
|
||||
.menuDropdown {
|
||||
min-width: 210px;
|
||||
}
|
||||
|
||||
/* -------- Title / Meta -------- */
|
||||
.title {
|
||||
line-height: 1.2;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.meta {
|
||||
margin-top: 2px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* -------- Preview area -------- */
|
||||
.previewBox {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--file-card-bg);
|
||||
}
|
||||
|
||||
.previewPaper {
|
||||
width: 100%;
|
||||
height: calc(100% - 6px);
|
||||
min-height: 9rem;
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--file-card-bg);
|
||||
}
|
||||
|
||||
/* Thumbnail fallback */
|
||||
.previewPaper[data-thumb-missing="true"]::after {
|
||||
content: "No preview";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Drag handle grip */
|
||||
.dragHandle {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
color: var(--text-secondary);
|
||||
z-index: 1;
|
||||
cursor: grab;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* Actions Overlay */
|
||||
.actionsOverlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 44px; /* just below header */
|
||||
background: var(--bg-toolbar);
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
z-index: 20;
|
||||
overflow: hidden;
|
||||
animation: slideDown 140ms ease-out;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(-8px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.actionRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.actionRow:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.actionDanger {
|
||||
color: var(--text-brand-accent);
|
||||
}
|
||||
|
||||
.actionsDivider {
|
||||
height: 1px;
|
||||
background: var(--border-default);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Pin indicator */
|
||||
.pinIndicator {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
z-index: 1;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
/* Unsupported file indicator */
|
||||
.unsupportedPill {
|
||||
margin-left: 1.75rem;
|
||||
background: #6B7280;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 80px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.card,
|
||||
.menuDropdown {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
DARK MODE OVERRIDES
|
||||
========================= */
|
||||
:global([data-mantine-color-scheme="dark"]) .card {
|
||||
outline-color: #3A4047; /* deselected stroke */
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .card[data-selected="true"] {
|
||||
outline-color: #4B525A; /* selected stroke (subtle grey) */
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .headerResting {
|
||||
background: #1F2329; /* requested default unselected color */
|
||||
color: var(--tool-header-text); /* #D0D6DC */
|
||||
border-bottom-color: var(--tool-header-border); /* #3A4047 */
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .headerSelected {
|
||||
background: var(--tool-header-border); /* #3A4047 */
|
||||
color: var(--tool-header-text); /* #D0D6DC */
|
||||
border-bottom-color: var(--tool-header-border);
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .title {
|
||||
color: #D0D6DC; /* title text */
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .meta {
|
||||
color: #6B7280; /* subtitle text */
|
||||
}
|
||||
|
||||
/* Light mode selected header stroke override */
|
||||
:global([data-mantine-color-scheme="light"]) .card[data-selected="true"] {
|
||||
outline-color: #3B4B6E;
|
||||
}
|
||||
@@ -1,52 +1,28 @@
|
||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import React, { useState, useCallback, useRef, useMemo } from 'react';
|
||||
import {
|
||||
Button, Text, Center, Box, Notification, TextInput, LoadingOverlay, Modal, Alert, Container,
|
||||
Stack, Group
|
||||
Text, Center, Box, Notification, LoadingOverlay, Stack, Group, Portal
|
||||
} from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import { useFileContext } from '../../contexts/FileContext';
|
||||
import { useFileSelection } from '../../contexts/FileSelectionContext';
|
||||
import { FileOperation } from '../../types/fileContext';
|
||||
import { fileStorage } from '../../services/fileStorage';
|
||||
import { generateThumbnailForFile } from '../../utils/thumbnailUtils';
|
||||
import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext';
|
||||
import { useNavigationActions } from '../../contexts/NavigationContext';
|
||||
import { zipFileService } from '../../services/zipFileService';
|
||||
import { detectFileExtension } from '../../utils/fileUtils';
|
||||
import styles from '../pageEditor/PageEditor.module.css';
|
||||
import FileThumbnail from '../pageEditor/FileThumbnail';
|
||||
import DragDropGrid from '../pageEditor/DragDropGrid';
|
||||
import FileEditorThumbnail from './FileEditorThumbnail';
|
||||
import FilePickerModal from '../shared/FilePickerModal';
|
||||
import SkeletonLoader from '../shared/SkeletonLoader';
|
||||
|
||||
interface FileItem {
|
||||
id: string;
|
||||
name: string;
|
||||
pageCount: number;
|
||||
thumbnail: string;
|
||||
size: number;
|
||||
file: File;
|
||||
splitBefore?: boolean;
|
||||
}
|
||||
import { FileId, StirlingFile } from '../../types/fileContext';
|
||||
|
||||
interface FileEditorProps {
|
||||
onOpenPageEditor?: (file: File) => void;
|
||||
onMergeFiles?: (files: File[]) => void;
|
||||
onOpenPageEditor?: () => void;
|
||||
onMergeFiles?: (files: StirlingFile[]) => void;
|
||||
toolMode?: boolean;
|
||||
showUpload?: boolean;
|
||||
showBulkActions?: boolean;
|
||||
supportedExtensions?: string[];
|
||||
}
|
||||
|
||||
const FileEditor = ({
|
||||
onOpenPageEditor,
|
||||
onMergeFiles,
|
||||
toolMode = false,
|
||||
showUpload = true,
|
||||
showBulkActions = true,
|
||||
supportedExtensions = ["pdf"]
|
||||
}: FileEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Utility function to check if a file extension is supported
|
||||
const isFileSupported = useCallback((fileName: string): boolean => {
|
||||
@@ -54,33 +30,22 @@ const FileEditor = ({
|
||||
return extension ? supportedExtensions.includes(extension) : false;
|
||||
}, [supportedExtensions]);
|
||||
|
||||
// Get file context
|
||||
const fileContext = useFileContext();
|
||||
const {
|
||||
activeFiles,
|
||||
processedFiles,
|
||||
selectedFileIds,
|
||||
setSelectedFiles: setContextSelectedFiles,
|
||||
isProcessing,
|
||||
addFiles,
|
||||
removeFiles,
|
||||
setCurrentView,
|
||||
recordOperation,
|
||||
markOperationApplied
|
||||
} = fileContext;
|
||||
// Use optimized FileContext hooks
|
||||
const { state, selectors } = useFileState();
|
||||
const { addFiles, removeFiles, reorderFiles } = useFileManagement();
|
||||
|
||||
// Extract needed values from state (memoized to prevent infinite loops)
|
||||
const activeStirlingFileStubs = useMemo(() => selectors.getStirlingFileStubs(), [selectors.getFilesSignature()]);
|
||||
const selectedFileIds = state.ui.selectedFileIds;
|
||||
|
||||
// Get navigation actions
|
||||
const { actions: navActions } = useNavigationActions();
|
||||
|
||||
// Get file selection context
|
||||
const {
|
||||
selectedFiles: toolSelectedFiles,
|
||||
setSelectedFiles: setToolSelectedFiles,
|
||||
maxFiles,
|
||||
isToolMode
|
||||
} = useFileSelection();
|
||||
const { setSelectedFiles } = useFileSelection();
|
||||
|
||||
const [files, setFiles] = useState<FileItem[]>([]);
|
||||
const [status, setStatus] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [localLoading, setLocalLoading] = useState(false);
|
||||
const [selectionMode, setSelectionMode] = useState(toolMode);
|
||||
|
||||
// Enable selection mode automatically in tool mode
|
||||
@@ -89,13 +54,7 @@ const FileEditor = ({
|
||||
setSelectionMode(true);
|
||||
}
|
||||
}, [toolMode]);
|
||||
const [draggedFile, setDraggedFile] = useState<string | null>(null);
|
||||
const [dropTarget, setDropTarget] = useState<string | null>(null);
|
||||
const [multiFileDrag, setMultiFileDrag] = useState<{fileIds: string[], count: number} | null>(null);
|
||||
const [dragPosition, setDragPosition] = useState<{x: number, y: number} | null>(null);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
|
||||
const [conversionProgress, setConversionProgress] = useState(0);
|
||||
const [zipExtractionProgress, setZipExtractionProgress] = useState<{
|
||||
isExtracting: boolean;
|
||||
currentFile: string;
|
||||
@@ -109,116 +68,15 @@ const FileEditor = ({
|
||||
extractedCount: 0,
|
||||
totalFiles: 0
|
||||
});
|
||||
const fileRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||
const lastActiveFilesRef = useRef<string[]>([]);
|
||||
const lastProcessedFilesRef = useRef<number>(0);
|
||||
|
||||
// Get selected file IDs from context (defensive programming)
|
||||
const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : [];
|
||||
|
||||
// Map context selections to local file IDs for UI display
|
||||
const localSelectedIds = files
|
||||
.filter(file => {
|
||||
const fileId = (file.file as any).id || file.name;
|
||||
return contextSelectedIds.includes(fileId);
|
||||
})
|
||||
.map(file => file.id);
|
||||
|
||||
// Convert shared files to FileEditor format
|
||||
const convertToFileItem = useCallback(async (sharedFile: any): Promise<FileItem> => {
|
||||
// Generate thumbnail if not already available
|
||||
const thumbnail = sharedFile.thumbnail || await generateThumbnailForFile(sharedFile.file || sharedFile);
|
||||
|
||||
return {
|
||||
id: sharedFile.id || `file-${Date.now()}-${Math.random()}`,
|
||||
name: (sharedFile.file?.name || sharedFile.name || 'unknown'),
|
||||
pageCount: sharedFile.pageCount || Math.floor(Math.random() * 20) + 1, // Mock for now
|
||||
thumbnail,
|
||||
size: sharedFile.file?.size || sharedFile.size || 0,
|
||||
file: sharedFile.file || sharedFile,
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Convert activeFiles to FileItem format using context (async to avoid blocking)
|
||||
useEffect(() => {
|
||||
// Check if the actual content has changed, not just references
|
||||
const currentActiveFileNames = activeFiles.map(f => f.name);
|
||||
const currentProcessedFilesSize = processedFiles.size;
|
||||
|
||||
const activeFilesChanged = JSON.stringify(currentActiveFileNames) !== JSON.stringify(lastActiveFilesRef.current);
|
||||
const processedFilesChanged = currentProcessedFilesSize !== lastProcessedFilesRef.current;
|
||||
|
||||
if (!activeFilesChanged && !processedFilesChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update refs
|
||||
lastActiveFilesRef.current = currentActiveFileNames;
|
||||
lastProcessedFilesRef.current = currentProcessedFilesSize;
|
||||
|
||||
const convertActiveFiles = async () => {
|
||||
|
||||
if (activeFiles.length > 0) {
|
||||
setLocalLoading(true);
|
||||
try {
|
||||
// Process files in chunks to avoid blocking UI
|
||||
const convertedFiles: FileItem[] = [];
|
||||
|
||||
for (let i = 0; i < activeFiles.length; i++) {
|
||||
const file = activeFiles[i];
|
||||
|
||||
// Try to get thumbnail from processed file first
|
||||
const processedFile = processedFiles.get(file);
|
||||
let thumbnail = processedFile?.pages?.[0]?.thumbnail;
|
||||
|
||||
// If no thumbnail from processed file, try to generate one
|
||||
if (!thumbnail) {
|
||||
try {
|
||||
thumbnail = await generateThumbnailForFile(file);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to generate thumbnail for ${file.name}:`, error);
|
||||
thumbnail = undefined; // Use placeholder
|
||||
}
|
||||
}
|
||||
|
||||
const convertedFile = {
|
||||
id: `file-${Date.now()}-${Math.random()}`,
|
||||
name: file.name,
|
||||
pageCount: processedFile?.totalPages || Math.floor(Math.random() * 20) + 1,
|
||||
thumbnail: thumbnail || '',
|
||||
size: file.size,
|
||||
file,
|
||||
};
|
||||
|
||||
convertedFiles.push(convertedFile);
|
||||
|
||||
// Update progress
|
||||
setConversionProgress(((i + 1) / activeFiles.length) * 100);
|
||||
|
||||
// Yield to main thread between files
|
||||
if (i < activeFiles.length - 1) {
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setFiles(convertedFiles);
|
||||
} catch (err) {
|
||||
console.error('Error converting active files:', err);
|
||||
} finally {
|
||||
setLocalLoading(false);
|
||||
setConversionProgress(0);
|
||||
}
|
||||
} else {
|
||||
setFiles([]);
|
||||
setLocalLoading(false);
|
||||
setConversionProgress(0);
|
||||
}
|
||||
};
|
||||
|
||||
convertActiveFiles();
|
||||
}, [activeFiles, processedFiles]);
|
||||
// Create refs for frequently changing values to stabilize callbacks
|
||||
const contextSelectedIdsRef = useRef<FileId[]>([]);
|
||||
contextSelectedIdsRef.current = contextSelectedIds;
|
||||
|
||||
// Use activeStirlingFileStubs directly - no conversion needed
|
||||
const localSelectedIds = contextSelectedIds;
|
||||
|
||||
// Process uploaded files using context
|
||||
const handleFileUpload = useCallback(async (uploadedFiles: File[]) => {
|
||||
@@ -270,38 +128,14 @@ const FileEditor = ({
|
||||
if (extractionResult.success) {
|
||||
allExtractedFiles.push(...extractionResult.extractedFiles);
|
||||
|
||||
// Record ZIP extraction operation
|
||||
const operationId = `zip-extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const operation: FileOperation = {
|
||||
id: operationId,
|
||||
type: 'convert',
|
||||
timestamp: Date.now(),
|
||||
fileIds: extractionResult.extractedFiles.map(f => f.name),
|
||||
status: 'pending',
|
||||
metadata: {
|
||||
originalFileName: file.name,
|
||||
outputFileNames: extractionResult.extractedFiles.map(f => f.name),
|
||||
fileSize: file.size,
|
||||
parameters: {
|
||||
extractionType: 'zip',
|
||||
extractedCount: extractionResult.extractedCount,
|
||||
totalFiles: extractionResult.totalFiles
|
||||
}
|
||||
if (extractionResult.errors.length > 0) {
|
||||
errors.push(...extractionResult.errors);
|
||||
}
|
||||
};
|
||||
|
||||
recordOperation(file.name, operation);
|
||||
markOperationApplied(file.name, operationId);
|
||||
|
||||
if (extractionResult.errors.length > 0) {
|
||||
errors.push(...extractionResult.errors);
|
||||
}
|
||||
} else {
|
||||
errors.push(`Failed to extract ZIP file "${file.name}": ${extractionResult.errors.join(', ')}`);
|
||||
}
|
||||
} else {
|
||||
// ZIP doesn't contain PDFs or is invalid - treat as regular file
|
||||
console.log(`Adding ZIP file as regular file: ${file.name} (no PDFs found)`);
|
||||
allExtractedFiles.push(file);
|
||||
}
|
||||
} catch (zipError) {
|
||||
@@ -315,7 +149,6 @@ const FileEditor = ({
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`Adding none PDF file: ${file.name} (${file.type})`);
|
||||
allExtractedFiles.push(file);
|
||||
}
|
||||
}
|
||||
@@ -327,28 +160,6 @@ const FileEditor = ({
|
||||
|
||||
// Process all extracted files
|
||||
if (allExtractedFiles.length > 0) {
|
||||
// Record upload operations for PDF files
|
||||
for (const file of allExtractedFiles) {
|
||||
const operationId = `upload-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const operation: FileOperation = {
|
||||
id: operationId,
|
||||
type: 'upload',
|
||||
timestamp: Date.now(),
|
||||
fileIds: [file.name],
|
||||
status: 'pending',
|
||||
metadata: {
|
||||
originalFileName: file.name,
|
||||
fileSize: file.size,
|
||||
parameters: {
|
||||
uploadMethod: 'drag-drop'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
recordOperation(file.name, operation);
|
||||
markOperationApplied(file.name, operationId);
|
||||
}
|
||||
|
||||
// Add files to context (they will be processed automatically)
|
||||
await addFiles(allExtractedFiles);
|
||||
setStatus(`Added ${allExtractedFiles.length} files`);
|
||||
@@ -367,298 +178,139 @@ const FileEditor = ({
|
||||
totalFiles: 0
|
||||
});
|
||||
}
|
||||
}, [addFiles, recordOperation, markOperationApplied]);
|
||||
}, [addFiles]);
|
||||
|
||||
const selectAll = useCallback(() => {
|
||||
setContextSelectedFiles(files.map(f => (f.file as any).id || f.name));
|
||||
}, [files, setContextSelectedFiles]);
|
||||
const toggleFile = useCallback((fileId: FileId) => {
|
||||
const currentSelectedIds = contextSelectedIdsRef.current;
|
||||
|
||||
const deselectAll = useCallback(() => setContextSelectedFiles([]), [setContextSelectedFiles]);
|
||||
const targetRecord = activeStirlingFileStubs.find(r => r.id === fileId);
|
||||
if (!targetRecord) return;
|
||||
|
||||
const closeAllFiles = useCallback(() => {
|
||||
if (activeFiles.length === 0) return;
|
||||
const contextFileId = fileId; // No need to create a new ID
|
||||
const isSelected = currentSelectedIds.includes(contextFileId);
|
||||
|
||||
// Record close all operation for each file
|
||||
activeFiles.forEach(file => {
|
||||
const operationId = `close-all-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const operation: FileOperation = {
|
||||
id: operationId,
|
||||
type: 'remove',
|
||||
timestamp: Date.now(),
|
||||
fileIds: [file.name],
|
||||
status: 'pending',
|
||||
metadata: {
|
||||
originalFileName: file.name,
|
||||
fileSize: file.size,
|
||||
parameters: {
|
||||
action: 'close_all',
|
||||
reason: 'user_request'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
recordOperation(file.name, operation);
|
||||
markOperationApplied(file.name, operationId);
|
||||
});
|
||||
|
||||
// Remove all files from context but keep in storage
|
||||
removeFiles(activeFiles.map(f => (f as any).id || f.name), false);
|
||||
|
||||
// Clear selections
|
||||
setContextSelectedFiles([]);
|
||||
}, [activeFiles, removeFiles, setContextSelectedFiles, recordOperation, markOperationApplied]);
|
||||
|
||||
const toggleFile = useCallback((fileId: string) => {
|
||||
const targetFile = files.find(f => f.id === fileId);
|
||||
if (!targetFile) return;
|
||||
|
||||
const contextFileId = (targetFile.file as any).id || targetFile.name;
|
||||
const isSelected = contextSelectedIds.includes(contextFileId);
|
||||
|
||||
let newSelection: string[];
|
||||
let newSelection: FileId[];
|
||||
|
||||
if (isSelected) {
|
||||
// Remove file from selection
|
||||
newSelection = contextSelectedIds.filter(id => id !== contextFileId);
|
||||
newSelection = currentSelectedIds.filter(id => id !== contextFileId);
|
||||
} else {
|
||||
// Add file to selection
|
||||
if (maxFiles === 1) {
|
||||
// In tool mode, typically allow multiple files unless specified otherwise
|
||||
const maxAllowed = toolMode ? 10 : Infinity; // Default max for tools
|
||||
|
||||
if (maxAllowed === 1) {
|
||||
newSelection = [contextFileId];
|
||||
} else {
|
||||
// Check if we've hit the selection limit
|
||||
if (maxFiles > 1 && contextSelectedIds.length >= maxFiles) {
|
||||
setStatus(`Maximum ${maxFiles} files can be selected`);
|
||||
if (maxAllowed > 1 && currentSelectedIds.length >= maxAllowed) {
|
||||
setStatus(`Maximum ${maxAllowed} files can be selected`);
|
||||
return;
|
||||
}
|
||||
newSelection = [...contextSelectedIds, contextFileId];
|
||||
newSelection = [...currentSelectedIds, contextFileId];
|
||||
}
|
||||
}
|
||||
|
||||
// Update context
|
||||
setContextSelectedFiles(newSelection);
|
||||
|
||||
// Update tool selection context if in tool mode
|
||||
if (isToolMode || toolMode) {
|
||||
const selectedFiles = files
|
||||
.filter(f => {
|
||||
const fId = (f.file as any).id || f.name;
|
||||
return newSelection.includes(fId);
|
||||
})
|
||||
.map(f => f.file);
|
||||
setToolSelectedFiles(selectedFiles);
|
||||
}
|
||||
}, [files, setContextSelectedFiles, maxFiles, contextSelectedIds, setStatus, isToolMode, toolMode, setToolSelectedFiles]);
|
||||
|
||||
const toggleSelectionMode = useCallback(() => {
|
||||
setSelectionMode(prev => {
|
||||
const newMode = !prev;
|
||||
if (!newMode) {
|
||||
setContextSelectedFiles([]);
|
||||
}
|
||||
return newMode;
|
||||
});
|
||||
}, [setContextSelectedFiles]);
|
||||
// Update context (this automatically updates tool selection since they use the same action)
|
||||
setSelectedFiles(newSelection);
|
||||
}, [setSelectedFiles, toolMode, setStatus, activeStirlingFileStubs]);
|
||||
|
||||
|
||||
// Drag and drop handlers
|
||||
const handleDragStart = useCallback((fileId: string) => {
|
||||
setDraggedFile(fileId);
|
||||
// File reordering handler for drag and drop
|
||||
const handleReorderFiles = useCallback((sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => {
|
||||
const currentIds = activeStirlingFileStubs.map(r => r.id);
|
||||
|
||||
if (selectionMode && localSelectedIds.includes(fileId) && localSelectedIds.length > 1) {
|
||||
setMultiFileDrag({
|
||||
fileIds: localSelectedIds,
|
||||
count: localSelectedIds.length
|
||||
});
|
||||
} else {
|
||||
setMultiFileDrag(null);
|
||||
}
|
||||
}, [selectionMode, localSelectedIds]);
|
||||
// Find indices
|
||||
const sourceIndex = currentIds.findIndex(id => id === sourceFileId);
|
||||
const targetIndex = currentIds.findIndex(id => id === targetFileId);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
setDraggedFile(null);
|
||||
setDropTarget(null);
|
||||
setMultiFileDrag(null);
|
||||
setDragPosition(null);
|
||||
}, []);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!draggedFile) return;
|
||||
|
||||
if (multiFileDrag) {
|
||||
setDragPosition({ x: e.clientX, y: e.clientY });
|
||||
}
|
||||
|
||||
const elementUnderCursor = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (!elementUnderCursor) return;
|
||||
|
||||
const fileContainer = elementUnderCursor.closest('[data-file-id]');
|
||||
if (fileContainer) {
|
||||
const fileId = fileContainer.getAttribute('data-file-id');
|
||||
if (fileId && fileId !== draggedFile) {
|
||||
setDropTarget(fileId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const endZone = elementUnderCursor.closest('[data-drop-zone="end"]');
|
||||
if (endZone) {
|
||||
setDropTarget('end');
|
||||
if (sourceIndex === -1 || targetIndex === -1) {
|
||||
console.warn('Could not find source or target file for reordering');
|
||||
return;
|
||||
}
|
||||
|
||||
setDropTarget(null);
|
||||
}, [draggedFile, multiFileDrag]);
|
||||
// Handle multi-file selection reordering
|
||||
const filesToMove = selectedFileIds.length > 1
|
||||
? selectedFileIds.filter(id => currentIds.includes(id))
|
||||
: [sourceFileId];
|
||||
|
||||
const handleDragEnter = useCallback((fileId: string) => {
|
||||
if (draggedFile && fileId !== draggedFile) {
|
||||
setDropTarget(fileId);
|
||||
}
|
||||
}, [draggedFile]);
|
||||
// Create new order
|
||||
const newOrder = [...currentIds];
|
||||
|
||||
const handleDragLeave = useCallback(() => {
|
||||
// Let dragover handle this
|
||||
}, []);
|
||||
// Remove files to move from their current positions (in reverse order to maintain indices)
|
||||
const sourceIndices = filesToMove.map(id => newOrder.findIndex(nId => nId === id))
|
||||
.sort((a, b) => b - a); // Sort descending
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent, targetFileId: string | 'end') => {
|
||||
e.preventDefault();
|
||||
if (!draggedFile || draggedFile === targetFileId) return;
|
||||
|
||||
let targetIndex: number;
|
||||
if (targetFileId === 'end') {
|
||||
targetIndex = files.length;
|
||||
} else {
|
||||
targetIndex = files.findIndex(f => f.id === targetFileId);
|
||||
if (targetIndex === -1) return;
|
||||
}
|
||||
|
||||
const filesToMove = selectionMode && localSelectedIds.includes(draggedFile)
|
||||
? localSelectedIds
|
||||
: [draggedFile];
|
||||
|
||||
// Update the local files state and sync with activeFiles
|
||||
setFiles(prev => {
|
||||
const newFiles = [...prev];
|
||||
const movedFiles = filesToMove.map(id => newFiles.find(f => f.id === id)!).filter(Boolean);
|
||||
|
||||
// Remove moved files
|
||||
filesToMove.forEach(id => {
|
||||
const index = newFiles.findIndex(f => f.id === id);
|
||||
if (index !== -1) newFiles.splice(index, 1);
|
||||
});
|
||||
|
||||
// Insert at target position
|
||||
newFiles.splice(targetIndex, 0, ...movedFiles);
|
||||
|
||||
// TODO: Update context with reordered files (need to implement file reordering in context)
|
||||
// For now, just return the reordered local state
|
||||
return newFiles;
|
||||
sourceIndices.forEach(index => {
|
||||
newOrder.splice(index, 1);
|
||||
});
|
||||
|
||||
const moveCount = multiFileDrag ? multiFileDrag.count : 1;
|
||||
setStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
|
||||
|
||||
}, [draggedFile, files, selectionMode, localSelectedIds, multiFileDrag]);
|
||||
|
||||
const handleEndZoneDragEnter = useCallback(() => {
|
||||
if (draggedFile) {
|
||||
setDropTarget('end');
|
||||
// Calculate insertion index after removals
|
||||
let insertIndex = newOrder.findIndex(id => id === targetFileId);
|
||||
if (insertIndex !== -1) {
|
||||
// Determine if moving forward or backward
|
||||
const isMovingForward = sourceIndex < targetIndex;
|
||||
if (isMovingForward) {
|
||||
// Moving forward: insert after target
|
||||
insertIndex += 1;
|
||||
} else {
|
||||
// Moving backward: insert before target (insertIndex already correct)
|
||||
}
|
||||
} else {
|
||||
// Target was moved, insert at end
|
||||
insertIndex = newOrder.length;
|
||||
}
|
||||
}, [draggedFile]);
|
||||
|
||||
// Insert files at the calculated position
|
||||
newOrder.splice(insertIndex, 0, ...filesToMove);
|
||||
|
||||
// Update file order
|
||||
reorderFiles(newOrder);
|
||||
|
||||
// Update status
|
||||
const moveCount = filesToMove.length;
|
||||
setStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
|
||||
}, [activeStirlingFileStubs, reorderFiles, setStatus]);
|
||||
|
||||
|
||||
|
||||
// File operations using context
|
||||
const handleDeleteFile = useCallback((fileId: string) => {
|
||||
console.log('handleDeleteFile called with fileId:', fileId);
|
||||
const file = files.find(f => f.id === fileId);
|
||||
console.log('Found file:', file);
|
||||
|
||||
if (file) {
|
||||
console.log('Attempting to remove file:', file.name);
|
||||
console.log('Actual file object:', file.file);
|
||||
console.log('Actual file.file.name:', file.file.name);
|
||||
|
||||
// Record close operation
|
||||
const fileName = file.file.name;
|
||||
const fileId = (file.file as any).id || fileName;
|
||||
const operationId = `close-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const operation: FileOperation = {
|
||||
id: operationId,
|
||||
type: 'remove',
|
||||
timestamp: Date.now(),
|
||||
fileIds: [fileName],
|
||||
status: 'pending',
|
||||
metadata: {
|
||||
originalFileName: fileName,
|
||||
fileSize: file.size,
|
||||
parameters: {
|
||||
action: 'close',
|
||||
reason: 'user_request'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
recordOperation(fileName, operation);
|
||||
const handleDeleteFile = useCallback((fileId: FileId) => {
|
||||
const record = activeStirlingFileStubs.find(r => r.id === fileId);
|
||||
const file = record ? selectors.getFile(record.id) : null;
|
||||
|
||||
if (record && file) {
|
||||
// Remove file from context but keep in storage (close, don't delete)
|
||||
console.log('Calling removeFiles with:', [fileId]);
|
||||
removeFiles([fileId], false);
|
||||
const contextFileId = record.id;
|
||||
removeFiles([contextFileId], false);
|
||||
|
||||
// Remove from context selections
|
||||
const newSelection = contextSelectedIds.filter(id => id !== fileId);
|
||||
setContextSelectedFiles(newSelection);
|
||||
// Mark operation as applied
|
||||
markOperationApplied(fileName, operationId);
|
||||
} else {
|
||||
console.log('File not found for fileId:', fileId);
|
||||
const currentSelected = selectedFileIds.filter(id => id !== contextFileId);
|
||||
setSelectedFiles(currentSelected);
|
||||
}
|
||||
}, [files, removeFiles, setContextSelectedFiles, recordOperation, markOperationApplied]);
|
||||
}, [activeStirlingFileStubs, selectors, removeFiles, setSelectedFiles, selectedFileIds]);
|
||||
|
||||
const handleViewFile = useCallback((fileId: string) => {
|
||||
const file = files.find(f => f.id === fileId);
|
||||
if (file) {
|
||||
// Set the file as selected in context and switch to page editor view
|
||||
const contextFileId = (file.file as any).id || file.name;
|
||||
setContextSelectedFiles([contextFileId]);
|
||||
setCurrentView('pageEditor');
|
||||
onOpenPageEditor?.(file.file);
|
||||
const handleViewFile = useCallback((fileId: FileId) => {
|
||||
const record = activeStirlingFileStubs.find(r => r.id === fileId);
|
||||
if (record) {
|
||||
// Set the file as selected in context and switch to viewer for preview
|
||||
setSelectedFiles([fileId]);
|
||||
navActions.setWorkbench('viewer');
|
||||
}
|
||||
}, [files, setContextSelectedFiles, setCurrentView, onOpenPageEditor]);
|
||||
}, [activeStirlingFileStubs, setSelectedFiles, navActions.setWorkbench]);
|
||||
|
||||
const handleMergeFromHere = useCallback((fileId: string) => {
|
||||
const startIndex = files.findIndex(f => f.id === fileId);
|
||||
if (startIndex === -1) return;
|
||||
|
||||
const filesToMerge = files.slice(startIndex).map(f => f.file);
|
||||
if (onMergeFiles) {
|
||||
onMergeFiles(filesToMerge);
|
||||
}
|
||||
}, [files, onMergeFiles]);
|
||||
|
||||
const handleSplitFile = useCallback((fileId: string) => {
|
||||
const file = files.find(f => f.id === fileId);
|
||||
if (file && onOpenPageEditor) {
|
||||
onOpenPageEditor(file.file);
|
||||
}
|
||||
}, [files, onOpenPageEditor]);
|
||||
|
||||
const handleLoadFromStorage = useCallback(async (selectedFiles: any[]) => {
|
||||
const handleLoadFromStorage = useCallback(async (selectedFiles: File[]) => {
|
||||
if (selectedFiles.length === 0) return;
|
||||
|
||||
setLocalLoading(true);
|
||||
try {
|
||||
const convertedFiles = await Promise.all(
|
||||
selectedFiles.map(convertToFileItem)
|
||||
);
|
||||
setFiles(prev => [...prev, ...convertedFiles]);
|
||||
// Use FileContext to handle loading stored files
|
||||
// The files are already in FileContext, just need to add them to active files
|
||||
setStatus(`Loaded ${selectedFiles.length} files from storage`);
|
||||
} catch (err) {
|
||||
console.error('Error loading files from storage:', err);
|
||||
setError('Failed to load some files from storage');
|
||||
} finally {
|
||||
setLocalLoading(false);
|
||||
}
|
||||
}, [convertToFileItem]);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
@@ -667,7 +319,6 @@ const FileEditor = ({
|
||||
multiple={true}
|
||||
maxSize={2 * 1024 * 1024 * 1024}
|
||||
style={{
|
||||
height: '100vh',
|
||||
border: 'none',
|
||||
borderRadius: 0,
|
||||
backgroundColor: 'transparent'
|
||||
@@ -675,24 +326,13 @@ const FileEditor = ({
|
||||
activateOnClick={false}
|
||||
activateOnDrag={true}
|
||||
>
|
||||
<Box pos="relative" h="100vh" style={{ overflow: 'auto' }}>
|
||||
<Box pos="relative" style={{ overflow: 'auto' }}>
|
||||
<LoadingOverlay visible={false} />
|
||||
|
||||
<Box p="md" pt="xl">
|
||||
<Group mb="md">
|
||||
{showBulkActions && !toolMode && (
|
||||
<>
|
||||
<Button onClick={selectAll} variant="light">Select All</Button>
|
||||
<Button onClick={deselectAll} variant="light">Deselect All</Button>
|
||||
<Button onClick={closeAllFiles} variant="light" color="orange">
|
||||
Close All
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
|
||||
{files.length === 0 && !localLoading && !zipExtractionProgress.isExtracting ? (
|
||||
{activeStirlingFileStubs.length === 0 && !zipExtractionProgress.isExtracting ? (
|
||||
<Center h="60vh">
|
||||
<Stack align="center" gap="md">
|
||||
<Text size="lg" c="dimmed">📁</Text>
|
||||
@@ -700,7 +340,7 @@ const FileEditor = ({
|
||||
<Text size="sm" c="dimmed">Upload PDF files, ZIP archives, or load from storage to get started</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : files.length === 0 && (localLoading || zipExtractionProgress.isExtracting) ? (
|
||||
) : activeStirlingFileStubs.length === 0 && zipExtractionProgress.isExtracting ? (
|
||||
<Box>
|
||||
<SkeletonLoader type="controls" />
|
||||
|
||||
@@ -734,88 +374,39 @@ const FileEditor = ({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Processing indicator */}
|
||||
{localLoading && (
|
||||
<Box mb="md" p="sm" style={{ backgroundColor: 'var(--mantine-color-blue-0)', borderRadius: 8 }}>
|
||||
<Group justify="space-between" mb="xs">
|
||||
<Text size="sm" fw={500}>Loading files...</Text>
|
||||
<Text size="sm" c="dimmed">{Math.round(conversionProgress)}%</Text>
|
||||
</Group>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '4px',
|
||||
backgroundColor: 'var(--mantine-color-gray-2)',
|
||||
borderRadius: '2px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{
|
||||
width: `${Math.round(conversionProgress)}%`,
|
||||
height: '100%',
|
||||
backgroundColor: 'var(--mantine-color-blue-6)',
|
||||
transition: 'width 0.3s ease'
|
||||
}} />
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<SkeletonLoader type="fileGrid" count={6} />
|
||||
</Box>
|
||||
) : (
|
||||
<DragDropGrid
|
||||
items={files}
|
||||
selectedItems={localSelectedIds as any /* FIX ME */}
|
||||
selectionMode={selectionMode}
|
||||
isAnimating={isAnimating}
|
||||
onDragStart={handleDragStart as any /* FIX ME */}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter as any /* FIX ME */}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop as any /* FIX ME */}
|
||||
onEndZoneDragEnter={handleEndZoneDragEnter}
|
||||
draggedItem={draggedFile as any /* FIX ME */}
|
||||
dropTarget={dropTarget as any /* FIX ME */}
|
||||
multiItemDrag={multiFileDrag as any /* FIX ME */}
|
||||
dragPosition={dragPosition}
|
||||
renderItem={(file, index, refs) => (
|
||||
<FileThumbnail
|
||||
file={file}
|
||||
index={index}
|
||||
totalFiles={files.length}
|
||||
selectedFiles={localSelectedIds}
|
||||
selectionMode={selectionMode}
|
||||
draggedFile={draggedFile}
|
||||
dropTarget={dropTarget}
|
||||
isAnimating={isAnimating}
|
||||
fileRefs={refs}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onToggleFile={toggleFile}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
onViewFile={handleViewFile}
|
||||
onSetStatus={setStatus}
|
||||
toolMode={toolMode}
|
||||
isSupported={isFileSupported(file.name)}
|
||||
/>
|
||||
)}
|
||||
renderSplitMarker={(file, index) => (
|
||||
<div
|
||||
style={{
|
||||
width: '2px',
|
||||
height: '24rem',
|
||||
borderLeft: '2px dashed #3b82f6',
|
||||
backgroundColor: 'transparent',
|
||||
marginLeft: '-0.75rem',
|
||||
marginRight: '-0.75rem',
|
||||
flexShrink: 0
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
|
||||
rowGap: '1.5rem',
|
||||
padding: '1rem',
|
||||
pointerEvents: 'auto'
|
||||
}}
|
||||
>
|
||||
{activeStirlingFileStubs.map((record, index) => {
|
||||
return (
|
||||
<FileEditorThumbnail
|
||||
key={record.id}
|
||||
file={record}
|
||||
index={index}
|
||||
totalFiles={activeStirlingFileStubs.length}
|
||||
selectedFiles={localSelectedIds}
|
||||
selectionMode={selectionMode}
|
||||
onToggleFile={toggleFile}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
onViewFile={handleViewFile}
|
||||
onSetStatus={setStatus}
|
||||
onReorderFiles={handleReorderFiles}
|
||||
toolMode={toolMode}
|
||||
isSupported={isFileSupported(record.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -828,25 +419,29 @@ const FileEditor = ({
|
||||
/>
|
||||
|
||||
{status && (
|
||||
<Notification
|
||||
color="blue"
|
||||
mt="md"
|
||||
onClose={() => setStatus(null)}
|
||||
style={{ position: 'fixed', bottom: 20, right: 20, zIndex: 1000 }}
|
||||
>
|
||||
{status}
|
||||
</Notification>
|
||||
<Portal>
|
||||
<Notification
|
||||
color="blue"
|
||||
mt="md"
|
||||
onClose={() => setStatus(null)}
|
||||
style={{ position: 'fixed', bottom: 40, right: 80, zIndex: 10001 }}
|
||||
>
|
||||
{status}
|
||||
</Notification>
|
||||
</Portal>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Notification
|
||||
color="red"
|
||||
mt="md"
|
||||
onClose={() => setError(null)}
|
||||
style={{ position: 'fixed', bottom: 80, right: 20, zIndex: 1000 }}
|
||||
>
|
||||
{error}
|
||||
</Notification>
|
||||
<Portal>
|
||||
<Notification
|
||||
color="red"
|
||||
mt="md"
|
||||
onClose={() => setError(null)}
|
||||
style={{ position: 'fixed', bottom: 80, right: 20, zIndex: 10001 }}
|
||||
>
|
||||
{error}
|
||||
</Notification>
|
||||
</Portal>
|
||||
)}
|
||||
</Box>
|
||||
</Dropzone>
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
||||
import { Text, ActionIcon, CheckboxIndicator } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import { StirlingFileStub } from '../../types/fileContext';
|
||||
|
||||
import styles from './FileEditor.module.css';
|
||||
import { useFileContext } from '../../contexts/FileContext';
|
||||
import { FileId } from '../../types/file';
|
||||
import ToolChain from '../shared/ToolChain';
|
||||
|
||||
|
||||
|
||||
interface FileEditorThumbnailProps {
|
||||
file: StirlingFileStub;
|
||||
index: number;
|
||||
totalFiles: number;
|
||||
selectedFiles: FileId[];
|
||||
selectionMode: boolean;
|
||||
onToggleFile: (fileId: FileId) => void;
|
||||
onDeleteFile: (fileId: FileId) => void;
|
||||
onViewFile: (fileId: FileId) => void;
|
||||
onSetStatus: (status: string) => void;
|
||||
onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void;
|
||||
onDownloadFile?: (fileId: FileId) => void;
|
||||
toolMode?: boolean;
|
||||
isSupported?: boolean;
|
||||
}
|
||||
|
||||
const FileEditorThumbnail = ({
|
||||
file,
|
||||
index,
|
||||
selectedFiles,
|
||||
onToggleFile,
|
||||
onDeleteFile,
|
||||
onSetStatus,
|
||||
onReorderFiles,
|
||||
onDownloadFile,
|
||||
isSupported = true,
|
||||
}: FileEditorThumbnailProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { pinFile, unpinFile, isFilePinned, activeFiles } = useFileContext();
|
||||
|
||||
// ---- Drag state ----
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragElementRef = useRef<HTMLDivElement | null>(null);
|
||||
const [actionsWidth, setActionsWidth] = useState<number | undefined>(undefined);
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
|
||||
// Resolve the actual File object for pin/unpin operations
|
||||
const actualFile = useMemo(() => {
|
||||
return activeFiles.find(f => f.fileId === file.id);
|
||||
}, [activeFiles, file.id]);
|
||||
const isPinned = actualFile ? isFilePinned(actualFile) : false;
|
||||
|
||||
const pageCount = file.processedFile?.totalPages || 0;
|
||||
|
||||
const downloadSelectedFile = useCallback(() => {
|
||||
// Prefer parent-provided handler if available
|
||||
if (typeof onDownloadFile === 'function') {
|
||||
onDownloadFile(file.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: attempt to download using the File object if provided
|
||||
const maybeFile = (file as unknown as { file?: File }).file;
|
||||
if (maybeFile instanceof File) {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(maybeFile);
|
||||
link.download = maybeFile.name || file.name || 'download';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(link.href);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we can't find a way to download, surface a status message
|
||||
onSetStatus?.(typeof t === 'function' ? t('downloadUnavailable', 'Download unavailable for this item') : 'Download unavailable for this item');
|
||||
}, [file, onDownloadFile, onSetStatus, t]);
|
||||
const handleRef = useRef<HTMLSpanElement | null>(null);
|
||||
|
||||
// ---- Selection ----
|
||||
const isSelected = selectedFiles.includes(file.id);
|
||||
|
||||
// ---- Meta formatting ----
|
||||
const prettySize = useMemo(() => {
|
||||
const bytes = file.size ?? 0;
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
||||
}, [file.size]);
|
||||
|
||||
const extUpper = useMemo(() => {
|
||||
const m = /\.([a-z0-9]+)$/i.exec(file.name ?? '');
|
||||
return (m?.[1] || '').toUpperCase();
|
||||
}, [file.name]);
|
||||
|
||||
const pageLabel = useMemo(
|
||||
() =>
|
||||
pageCount > 0
|
||||
? `${pageCount} ${pageCount === 1 ? 'Page' : 'Pages'}`
|
||||
: '',
|
||||
[pageCount]
|
||||
);
|
||||
|
||||
const dateLabel = useMemo(() => {
|
||||
const d = new Date(file.lastModified);
|
||||
if (Number.isNaN(d.getTime())) return '';
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
year: 'numeric',
|
||||
}).format(d);
|
||||
}, [file.lastModified]);
|
||||
|
||||
// ---- Drag & drop wiring ----
|
||||
const fileElementRef = useCallback((element: HTMLDivElement | null) => {
|
||||
if (!element) return;
|
||||
|
||||
dragElementRef.current = element;
|
||||
|
||||
const dragCleanup = draggable({
|
||||
element,
|
||||
getInitialData: () => ({
|
||||
type: 'file',
|
||||
fileId: file.id,
|
||||
fileName: file.name,
|
||||
selectedFiles: [file.id] // Always drag only this file, ignore selection state
|
||||
}),
|
||||
onDragStart: () => {
|
||||
setIsDragging(true);
|
||||
},
|
||||
onDrop: () => {
|
||||
setIsDragging(false);
|
||||
}
|
||||
});
|
||||
|
||||
const dropCleanup = dropTargetForElements({
|
||||
element,
|
||||
getData: () => ({
|
||||
type: 'file',
|
||||
fileId: file.id
|
||||
}),
|
||||
canDrop: ({ source }) => {
|
||||
const sourceData = source.data;
|
||||
return sourceData.type === 'file' && sourceData.fileId !== file.id;
|
||||
},
|
||||
onDrop: ({ source }) => {
|
||||
const sourceData = source.data;
|
||||
if (sourceData.type === 'file' && onReorderFiles) {
|
||||
const sourceFileId = sourceData.fileId as FileId;
|
||||
const selectedFileIds = sourceData.selectedFiles as FileId[];
|
||||
onReorderFiles(sourceFileId, file.id, selectedFileIds);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
dragCleanup();
|
||||
dropCleanup();
|
||||
};
|
||||
}, [file.id, file.name, selectedFiles, onReorderFiles]);
|
||||
|
||||
// Update dropdown width on resize
|
||||
useEffect(() => {
|
||||
const update = () => {
|
||||
if (dragElementRef.current) setActionsWidth(dragElementRef.current.offsetWidth);
|
||||
};
|
||||
update();
|
||||
window.addEventListener('resize', update);
|
||||
return () => window.removeEventListener('resize', update);
|
||||
}, []);
|
||||
|
||||
// Close the actions dropdown when hovering outside this file card (and its dropdown)
|
||||
useEffect(() => {
|
||||
if (!showActions) return;
|
||||
|
||||
const isInsideCard = (target: EventTarget | null) => {
|
||||
const container = dragElementRef.current;
|
||||
if (!container) return false;
|
||||
return target instanceof Node && container.contains(target);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isInsideCard(e.target)) {
|
||||
setShowActions(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
// On touch devices, close if the touch target is outside the card
|
||||
if (!isInsideCard(e.target)) {
|
||||
setShowActions(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('touchstart', handleTouchStart, { passive: true });
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('touchstart', handleTouchStart);
|
||||
};
|
||||
}, [showActions]);
|
||||
|
||||
// ---- Card interactions ----
|
||||
const handleCardClick = () => {
|
||||
if (!isSupported) return;
|
||||
onToggleFile(file.id);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={fileElementRef}
|
||||
data-file-id={file.id}
|
||||
data-testid="file-thumbnail"
|
||||
data-selected={isSelected}
|
||||
data-supported={isSupported}
|
||||
className={`${styles.card} w-[18rem] h-[22rem] select-none flex flex-col shadow-sm transition-all relative`}
|
||||
style={{
|
||||
opacity: isSupported ? (isDragging ? 0.9 : 1) : 0.5,
|
||||
filter: isSupported ? 'none' : 'grayscale(50%)',
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="listitem"
|
||||
aria-selected={isSelected}
|
||||
onClick={handleCardClick}
|
||||
>
|
||||
{/* Header bar */}
|
||||
<div
|
||||
className={`${styles.header} ${
|
||||
isSelected ? styles.headerSelected : styles.headerResting
|
||||
}`}
|
||||
>
|
||||
{/* Logo/checkbox area */}
|
||||
<div className={styles.logoMark}>
|
||||
{isSupported ? (
|
||||
<CheckboxIndicator
|
||||
checked={isSelected}
|
||||
onChange={() => onToggleFile(file.id)}
|
||||
color="var(--checkbox-checked-bg)"
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.unsupportedPill}>
|
||||
<span>
|
||||
{t('unsupported', 'Unsupported')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Centered index */}
|
||||
<div className={styles.headerIndex} aria-label={`Position ${index + 1}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
|
||||
{/* Kebab menu */}
|
||||
<ActionIcon
|
||||
aria-label={t('moreOptions', 'More options')}
|
||||
variant="subtle"
|
||||
className={styles.kebab}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowActions((v) => !v);
|
||||
}}
|
||||
>
|
||||
<MoreVertIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
|
||||
{/* Actions overlay */}
|
||||
{showActions && (
|
||||
<div
|
||||
className={styles.actionsOverlay}
|
||||
style={{ width: actionsWidth }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className={styles.actionRow}
|
||||
onClick={() => {
|
||||
if (actualFile) {
|
||||
if (isPinned) {
|
||||
unpinFile(actualFile);
|
||||
onSetStatus?.(`Unpinned ${file.name}`);
|
||||
} else {
|
||||
pinFile(actualFile);
|
||||
onSetStatus?.(`Pinned ${file.name}`);
|
||||
}
|
||||
}
|
||||
setShowActions(false);
|
||||
}}
|
||||
>
|
||||
{isPinned ? <PushPinIcon fontSize="small" /> : <PushPinOutlinedIcon fontSize="small" />}
|
||||
<span>{isPinned ? t('unpin', 'Unpin') : t('pin', 'Pin')}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.actionRow}
|
||||
onClick={() => { downloadSelectedFile(); setShowActions(false); }}
|
||||
>
|
||||
<DownloadOutlinedIcon fontSize="small" />
|
||||
<span>{t('download', 'Download')}</span>
|
||||
</button>
|
||||
|
||||
<div className={styles.actionsDivider} />
|
||||
|
||||
<button
|
||||
className={`${styles.actionRow} ${styles.actionDanger}`}
|
||||
onClick={() => {
|
||||
onDeleteFile(file.id);
|
||||
onSetStatus(`Deleted ${file.name}`);
|
||||
setShowActions(false);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlineIcon fontSize="small" />
|
||||
<span>{t('delete', 'Delete')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title + meta line */}
|
||||
<div
|
||||
style={{
|
||||
padding: '0.5rem',
|
||||
textAlign: 'center',
|
||||
background: 'var(--file-card-bg)',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}>
|
||||
<Text size="lg" fw={700} className={styles.title} lineClamp={2}>
|
||||
{file.name}
|
||||
</Text>
|
||||
<Text
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
className={styles.meta}
|
||||
lineClamp={3}
|
||||
title={`${extUpper || 'FILE'} • ${prettySize}`}
|
||||
>
|
||||
{/* e.g., v2 - Jan 29, 2025 - PDF file - 3 Pages */}
|
||||
{`v${file.versionNumber} - `}
|
||||
{dateLabel}
|
||||
{extUpper ? ` - ${extUpper} file` : ''}
|
||||
{pageLabel ? ` - ${pageLabel}` : ''}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Preview area */}
|
||||
<div className={`${styles.previewBox} mx-6 mb-4 relative flex-1`}>
|
||||
<div className={styles.previewPaper}>
|
||||
{file.thumbnailUrl && (
|
||||
<img
|
||||
src={file.thumbnailUrl}
|
||||
alt={file.name}
|
||||
draggable={false}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
onError={(e) => {
|
||||
const img = e.currentTarget;
|
||||
img.style.display = 'none';
|
||||
img.parentElement?.setAttribute('data-thumb-missing', 'true');
|
||||
}}
|
||||
style={{
|
||||
maxWidth: '80%',
|
||||
maxHeight: '80%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: 0,
|
||||
background: '#ffffff',
|
||||
border: '1px solid var(--border-default)',
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
alignSelf: 'start'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pin indicator (bottom-left) */}
|
||||
{isPinned && (
|
||||
<span className={styles.pinIndicator} aria-hidden>
|
||||
<PushPinIcon fontSize="small" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Drag handle (span wrapper so we can attach a ref reliably) */}
|
||||
<span ref={handleRef} className={styles.dragHandle} aria-hidden>
|
||||
<DragIndicatorIcon fontSize="small" />
|
||||
</span>
|
||||
|
||||
{/* Tool chain display at bottom */}
|
||||
{file.toolHistory && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '4px',
|
||||
left: '4px',
|
||||
right: '4px',
|
||||
padding: '4px 6px',
|
||||
textAlign: 'center',
|
||||
fontWeight: 600,
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
<ToolChain
|
||||
toolChain={file.toolHistory}
|
||||
displayStyle="text"
|
||||
size="xs"
|
||||
maxWidth={'100%'}
|
||||
color='var(--mantine-color-gray-7)'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FileEditorThumbnail);
|
||||
@@ -5,12 +5,12 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getFileSize } from '../../utils/fileUtils';
|
||||
import { FileWithUrl } from '../../types/file';
|
||||
import { StirlingFileStub } from '../../types/fileContext';
|
||||
|
||||
interface CompactFileDetailsProps {
|
||||
currentFile: FileWithUrl | null;
|
||||
currentFile: StirlingFileStub | null;
|
||||
thumbnail: string | null;
|
||||
selectedFiles: FileWithUrl[];
|
||||
selectedFiles: StirlingFileStub[];
|
||||
currentFileIndex: number;
|
||||
numberOfFiles: number;
|
||||
isAnimating: boolean;
|
||||
@@ -72,12 +72,19 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
<Text size="xs" c="dimmed">
|
||||
{currentFile ? getFileSize(currentFile) : ''}
|
||||
{selectedFiles.length > 1 && ` • ${selectedFiles.length} files`}
|
||||
{currentFile && ` • v${currentFile.versionNumber || 1}`}
|
||||
</Text>
|
||||
{hasMultipleFiles && (
|
||||
<Text size="xs" c="blue">
|
||||
{currentFileIndex + 1} of {selectedFiles.length}
|
||||
</Text>
|
||||
)}
|
||||
{/* Compact tool chain for mobile */}
|
||||
{currentFile?.toolHistory && currentFile.toolHistory.length > 0 && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{currentFile.toolHistory.map((tool: any) => tool.toolName).join(' → ')}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Navigation arrows for multiple files */}
|
||||
|
||||
@@ -4,6 +4,7 @@ import FileSourceButtons from './FileSourceButtons';
|
||||
import FileDetails from './FileDetails';
|
||||
import SearchInput from './SearchInput';
|
||||
import FileListArea from './FileListArea';
|
||||
import FileActions from './FileActions';
|
||||
import HiddenFileInput from './HiddenFileInput';
|
||||
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||
|
||||
@@ -17,27 +18,27 @@ const DesktopLayout: React.FC = () => {
|
||||
return (
|
||||
<Grid gutter="xs" h="100%" grow={false} style={{ flexWrap: 'nowrap', minWidth: 0 }}>
|
||||
{/* Column 1: File Sources */}
|
||||
<Grid.Col span="content" p="lg" style={{
|
||||
minWidth: '13.625rem',
|
||||
width: '13.625rem',
|
||||
flexShrink: 0,
|
||||
<Grid.Col span="content" p="lg" style={{
|
||||
minWidth: '13.625rem',
|
||||
width: '13.625rem',
|
||||
flexShrink: 0,
|
||||
height: '100%',
|
||||
}}>
|
||||
<FileSourceButtons />
|
||||
</Grid.Col>
|
||||
|
||||
|
||||
{/* Column 2: File List */}
|
||||
<Grid.Col span="auto" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
<Grid.Col span="auto" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
flex: '1 1 0px'
|
||||
}}>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: 'var(--bg-file-list)',
|
||||
border: '1px solid var(--mantine-color-gray-2)',
|
||||
@@ -45,18 +46,26 @@ const DesktopLayout: React.FC = () => {
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{activeSource === 'recent' && (
|
||||
<div style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-3)'
|
||||
}}>
|
||||
<SearchInput />
|
||||
</div>
|
||||
<>
|
||||
<div style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-3)'
|
||||
}}>
|
||||
<SearchInput />
|
||||
</div>
|
||||
<div style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-3)'
|
||||
}}>
|
||||
<FileActions />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<div style={{ flex: 1, minHeight: 0 }}>
|
||||
<FileListArea
|
||||
scrollAreaHeight={`calc(${modalHeight} )`}
|
||||
scrollAreaStyle={{
|
||||
scrollAreaStyle={{
|
||||
height: activeSource === 'recent' && recentFiles.length > 0 ? modalHeight : '100%',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
@@ -66,12 +75,12 @@ const DesktopLayout: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Grid.Col>
|
||||
|
||||
|
||||
{/* Column 3: File Details */}
|
||||
<Grid.Col p="xl" span="content" style={{
|
||||
minWidth: '25rem',
|
||||
width: '25rem',
|
||||
flexShrink: 0,
|
||||
<Grid.Col p="xl" span="content" style={{
|
||||
minWidth: '25rem',
|
||||
width: '25rem',
|
||||
flexShrink: 0,
|
||||
height: '100%',
|
||||
maxWidth: '18rem'
|
||||
}}>
|
||||
@@ -79,11 +88,11 @@ const DesktopLayout: React.FC = () => {
|
||||
<FileDetails />
|
||||
</div>
|
||||
</Grid.Col>
|
||||
|
||||
|
||||
{/* Hidden file input for local file selection */}
|
||||
<HiddenFileInput />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default DesktopLayout;
|
||||
export default DesktopLayout;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import React from "react";
|
||||
import { Group, Text, ActionIcon, Tooltip } from "@mantine/core";
|
||||
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileManagerContext } from "../../contexts/FileManagerContext";
|
||||
|
||||
const FileActions: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { recentFiles, selectedFileIds, filteredFiles, onSelectAll, onDeleteSelected, onDownloadSelected } =
|
||||
useFileManagerContext();
|
||||
|
||||
const handleSelectAll = () => {
|
||||
onSelectAll();
|
||||
};
|
||||
|
||||
const handleDeleteSelected = () => {
|
||||
if (selectedFileIds.length > 0) {
|
||||
onDeleteSelected();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadSelected = () => {
|
||||
if (selectedFileIds.length > 0) {
|
||||
onDownloadSelected();
|
||||
}
|
||||
};
|
||||
|
||||
// Only show actions if there are files
|
||||
if (recentFiles.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allFilesSelected = filteredFiles.length > 0 && selectedFileIds.length === filteredFiles.length;
|
||||
const hasSelection = selectedFileIds.length > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "0.75rem 1rem",
|
||||
backgroundColor: "var(--mantine-color-gray-1)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
minHeight: "3rem",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{/* Left: Select All */}
|
||||
<div>
|
||||
<Tooltip
|
||||
label={allFilesSelected ? t("fileManager.deselectAll", "Deselect All") : t("fileManager.selectAll", "Select All")}
|
||||
>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={handleSelectAll}
|
||||
disabled={filteredFiles.length === 0}
|
||||
radius="sm"
|
||||
>
|
||||
<SelectAllIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* Center: Selected count */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
>
|
||||
{hasSelection && (
|
||||
<Text size="sm" c="dimmed" fw={500}>
|
||||
{t("fileManager.selectedCount", "{{count}} selected", { count: selectedFileIds.length })}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: Delete and Download */}
|
||||
<Group gap="xs">
|
||||
<Tooltip label={t("fileManager.deleteSelected", "Delete Selected")}>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={handleDeleteSelected}
|
||||
disabled={!hasSelection}
|
||||
radius="sm"
|
||||
>
|
||||
<DeleteIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("fileManager.downloadSelected", "Download Selected")}>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={handleDownloadSelected}
|
||||
disabled={!hasSelection}
|
||||
radius="sm"
|
||||
>
|
||||
<DownloadIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileActions;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Stack, Button, Box } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIndexedDBThumbnail } from '../../hooks/useIndexedDBThumbnail';
|
||||
@@ -11,27 +11,26 @@ interface FileDetailsProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
compact = false
|
||||
}) => {
|
||||
const { selectedFiles, onOpenFiles, modalHeight } = useFileManagerContext();
|
||||
const { t } = useTranslation();
|
||||
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
|
||||
|
||||
// Get the currently displayed file
|
||||
const currentFile = selectedFiles.length > 0 ? selectedFiles[currentFileIndex] : null;
|
||||
const hasSelection = selectedFiles.length > 0;
|
||||
const hasMultipleFiles = selectedFiles.length > 1;
|
||||
|
||||
// Use IndexedDB hook for the current file
|
||||
const { thumbnail: currentThumbnail } = useIndexedDBThumbnail(currentFile);
|
||||
|
||||
|
||||
// Get thumbnail for current file
|
||||
const getCurrentThumbnail = () => {
|
||||
return currentThumbnail;
|
||||
};
|
||||
|
||||
|
||||
const handlePrevious = () => {
|
||||
if (isAnimating) return;
|
||||
setIsAnimating(true);
|
||||
@@ -40,7 +39,7 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
setIsAnimating(false);
|
||||
}, 150);
|
||||
};
|
||||
|
||||
|
||||
const handleNext = () => {
|
||||
if (isAnimating) return;
|
||||
setIsAnimating(true);
|
||||
@@ -49,14 +48,14 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
setIsAnimating(false);
|
||||
}, 150);
|
||||
};
|
||||
|
||||
|
||||
// Reset index when selection changes
|
||||
React.useEffect(() => {
|
||||
if (currentFileIndex >= selectedFiles.length) {
|
||||
setCurrentFileIndex(0);
|
||||
}
|
||||
}, [selectedFiles.length, currentFileIndex]);
|
||||
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<CompactFileDetails
|
||||
@@ -88,26 +87,26 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
onNext={handleNext}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Section 2: File Details */}
|
||||
<FileInfoCard
|
||||
currentFile={currentFile}
|
||||
modalHeight={modalHeight}
|
||||
/>
|
||||
|
||||
<Button
|
||||
size="md"
|
||||
|
||||
<Button
|
||||
size="md"
|
||||
mb="xl"
|
||||
onClick={onOpenFiles}
|
||||
disabled={!hasSelection}
|
||||
fullWidth
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
|
||||
color: 'white'
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
|
||||
color: 'white'
|
||||
}}
|
||||
>
|
||||
{selectedFiles.length > 1
|
||||
{selectedFiles.length > 1
|
||||
? t('fileManager.openFiles', `Open ${selectedFiles.length} Files`)
|
||||
: t('fileManager.openFile', 'Open File')
|
||||
}
|
||||
@@ -116,4 +115,4 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FileDetails;
|
||||
export default FileDetails;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { Box, Text, Collapse, Group } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StirlingFileStub } from '../../types/fileContext';
|
||||
import FileListItem from './FileListItem';
|
||||
|
||||
interface FileHistoryGroupProps {
|
||||
leafFile: StirlingFileStub;
|
||||
historyFiles: StirlingFileStub[];
|
||||
isExpanded: boolean;
|
||||
onDownloadSingle: (file: StirlingFileStub) => void;
|
||||
onFileDoubleClick: (file: StirlingFileStub) => void;
|
||||
onHistoryFileRemove: (file: StirlingFileStub) => void;
|
||||
isFileSupported: (fileName: string) => boolean;
|
||||
}
|
||||
|
||||
const FileHistoryGroup: React.FC<FileHistoryGroupProps> = ({
|
||||
leafFile,
|
||||
historyFiles,
|
||||
isExpanded,
|
||||
onDownloadSingle,
|
||||
onFileDoubleClick,
|
||||
onHistoryFileRemove,
|
||||
isFileSupported,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Sort history files by version number (oldest first, excluding the current leaf file)
|
||||
const sortedHistory = historyFiles
|
||||
.filter(file => file.id !== leafFile.id) // Exclude the leaf file itself
|
||||
.sort((a, b) => (b.versionNumber || 1) - (a.versionNumber || 1));
|
||||
|
||||
if (!isExpanded || sortedHistory.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse in={isExpanded}>
|
||||
<Box ml="md" mt="xs" mb="sm">
|
||||
<Group align="center" mb="sm">
|
||||
<Text size="xs" fw={600} c="dimmed">
|
||||
{t('fileManager.fileHistory', 'File History')} ({sortedHistory.length})
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Box ml="md">
|
||||
{sortedHistory.map((historyFile, _index) => (
|
||||
<FileListItem
|
||||
key={`history-${historyFile.id}-${historyFile.versionNumber || 1}`}
|
||||
file={historyFile}
|
||||
isSelected={false} // History files are not selectable
|
||||
isSupported={isFileSupported(historyFile.name)}
|
||||
onSelect={() => {}} // No selection for history files
|
||||
onRemove={() => onHistoryFileRemove(historyFile)} // Remove specific history file
|
||||
onDownload={() => onDownloadSingle(historyFile)}
|
||||
onDoubleClick={() => onFileDoubleClick(historyFile)}
|
||||
isHistoryFile={true} // This enables "Add to Recents" in menu
|
||||
isLatestVersion={false} // History files are never latest
|
||||
// onAddToRecents is accessed from context by FileListItem
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileHistoryGroup;
|
||||
@@ -2,10 +2,11 @@ import React from 'react';
|
||||
import { Stack, Card, Box, Text, Badge, Group, Divider, ScrollArea } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { detectFileExtension, getFileSize } from '../../utils/fileUtils';
|
||||
import { FileWithUrl } from '../../types/file';
|
||||
import { StirlingFileStub } from '../../types/fileContext';
|
||||
import ToolChain from '../shared/ToolChain';
|
||||
|
||||
interface FileInfoCardProps {
|
||||
currentFile: FileWithUrl | null;
|
||||
currentFile: StirlingFileStub | null;
|
||||
modalHeight: string;
|
||||
}
|
||||
|
||||
@@ -17,7 +18,7 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
|
||||
return (
|
||||
<Card withBorder p={0} h={`calc(${modalHeight} * 0.32 - 1rem)`} style={{ flex: 1, overflow: 'hidden' }}>
|
||||
<Box bg="blue.6" p="sm" style={{ borderTopLeftRadius: 'var(--mantine-radius-md)', borderTopRightRadius: 'var(--mantine-radius-md)' }}>
|
||||
<Box bg="gray.4" p="sm" style={{ borderTopLeftRadius: 'var(--mantine-radius-md)', borderTopRightRadius: 'var(--mantine-radius-md)' }}>
|
||||
<Text size="sm" fw={500} ta="center" c="white">
|
||||
{t('fileManager.details', 'File Details')}
|
||||
</Text>
|
||||
@@ -31,7 +32,7 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileFormat', 'Format')}</Text>
|
||||
{currentFile ? (
|
||||
@@ -43,7 +44,7 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
)}
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileSize', 'Size')}</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
@@ -51,17 +52,42 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileVersion', 'Version')}</Text>
|
||||
<Text size="sm" c="dimmed">{t('fileManager.lastModified', 'Last Modified')}</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{currentFile ? '1.0' : ''}
|
||||
{currentFile ? new Date(currentFile.lastModified).toLocaleDateString() : ''}
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileVersion', 'Version')}</Text>
|
||||
{currentFile &&
|
||||
<Badge size="sm" variant="light" color={currentFile?.versionNumber ? 'blue' : 'gray'}>
|
||||
v{currentFile ? (currentFile.versionNumber || 1) : ''}
|
||||
</Badge>}
|
||||
|
||||
</Group>
|
||||
|
||||
{/* Tool Chain Display */}
|
||||
{currentFile?.toolHistory && currentFile.toolHistory.length > 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<Box py="xs">
|
||||
<Text size="xs" c="dimmed" mb="xs">{t('fileManager.toolChain', 'Tools Applied')}</Text>
|
||||
<ToolChain
|
||||
toolChain={currentFile.toolHistory}
|
||||
displayStyle="badges"
|
||||
size="xs"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileInfoCard;
|
||||
export default FileInfoCard;
|
||||
|
||||
@@ -4,6 +4,7 @@ import CloudIcon from '@mui/icons-material/Cloud';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FileListItem from './FileListItem';
|
||||
import FileHistoryGroup from './FileHistoryGroup';
|
||||
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||
|
||||
interface FileListAreaProps {
|
||||
@@ -19,22 +20,26 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
activeSource,
|
||||
recentFiles,
|
||||
filteredFiles,
|
||||
selectedFileIds,
|
||||
selectedFilesSet,
|
||||
expandedFileIds,
|
||||
loadedHistoryFiles,
|
||||
onFileSelect,
|
||||
onFileRemove,
|
||||
onHistoryFileRemove,
|
||||
onFileDoubleClick,
|
||||
onDownloadSingle,
|
||||
isFileSupported,
|
||||
} = useFileManagerContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (activeSource === 'recent') {
|
||||
return (
|
||||
<ScrollArea
|
||||
<ScrollArea
|
||||
h={scrollAreaHeight}
|
||||
style={{
|
||||
style={{
|
||||
...scrollAreaStyle
|
||||
}}
|
||||
type="always"
|
||||
type="always"
|
||||
scrollbarSize={8}
|
||||
>
|
||||
<Stack gap={0}>
|
||||
@@ -49,17 +54,37 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
filteredFiles.map((file, index) => (
|
||||
<FileListItem
|
||||
key={file.id || file.name}
|
||||
file={file}
|
||||
isSelected={selectedFileIds.includes(file.id || file.name)}
|
||||
isSupported={isFileSupported(file.name)}
|
||||
onSelect={() => onFileSelect(file)}
|
||||
onRemove={() => onFileRemove(index)}
|
||||
onDoubleClick={() => onFileDoubleClick(file)}
|
||||
/>
|
||||
))
|
||||
filteredFiles.map((file, index) => {
|
||||
// All files in filteredFiles are now leaf files only
|
||||
const historyFiles = loadedHistoryFiles.get(file.id) || [];
|
||||
const isExpanded = expandedFileIds.has(file.id);
|
||||
|
||||
return (
|
||||
<React.Fragment key={file.id}>
|
||||
<FileListItem
|
||||
file={file}
|
||||
isSelected={selectedFilesSet.has(file.id)}
|
||||
isSupported={isFileSupported(file.name)}
|
||||
onSelect={(shiftKey) => onFileSelect(file, index, shiftKey)}
|
||||
onRemove={() => onFileRemove(index)}
|
||||
onDownload={() => onDownloadSingle(file)}
|
||||
onDoubleClick={() => onFileDoubleClick(file)}
|
||||
isHistoryFile={false} // All files here are leaf files
|
||||
isLatestVersion={true} // All files here are the latest versions
|
||||
/>
|
||||
|
||||
<FileHistoryGroup
|
||||
leafFile={file}
|
||||
historyFiles={historyFiles}
|
||||
isExpanded={isExpanded}
|
||||
onDownloadSingle={onDownloadSingle}
|
||||
onFileDoubleClick={onFileDoubleClick}
|
||||
onHistoryFileRemove={onHistoryFileRemove}
|
||||
isFileSupported={isFileSupported}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
@@ -77,4 +102,4 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FileListArea;
|
||||
export default FileListArea;
|
||||
|
||||
@@ -1,79 +1,208 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Group, Box, Text, ActionIcon, Checkbox, Divider } from '@mantine/core';
|
||||
import { Group, Box, Text, ActionIcon, Checkbox, Divider, Menu, Badge } from '@mantine/core';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import RestoreIcon from '@mui/icons-material/Restore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getFileSize, getFileDate } from '../../utils/fileUtils';
|
||||
import { FileWithUrl } from '../../types/file';
|
||||
import { FileId, StirlingFileStub } from '../../types/fileContext';
|
||||
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||
import ToolChain from '../shared/ToolChain';
|
||||
|
||||
interface FileListItemProps {
|
||||
file: FileWithUrl;
|
||||
file: StirlingFileStub;
|
||||
isSelected: boolean;
|
||||
isSupported: boolean;
|
||||
onSelect: () => void;
|
||||
onSelect: (shiftKey?: boolean) => void;
|
||||
onRemove: () => void;
|
||||
onDownload?: () => void;
|
||||
onDoubleClick?: () => void;
|
||||
isLast?: boolean;
|
||||
isHistoryFile?: boolean; // Whether this is a history file (indented)
|
||||
isLatestVersion?: boolean; // Whether this is the latest version (shows chevron)
|
||||
}
|
||||
|
||||
const FileListItem: React.FC<FileListItemProps> = ({
|
||||
file,
|
||||
isSelected,
|
||||
isSupported,
|
||||
onSelect,
|
||||
onRemove,
|
||||
onDoubleClick
|
||||
const FileListItem: React.FC<FileListItemProps> = ({
|
||||
file,
|
||||
isSelected,
|
||||
isSupported,
|
||||
onSelect,
|
||||
onRemove,
|
||||
onDownload,
|
||||
onDoubleClick,
|
||||
isHistoryFile = false,
|
||||
isLatestVersion = false
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const {expandedFileIds, onToggleExpansion, onAddToRecents } = useFileManagerContext();
|
||||
|
||||
// Keep item in hovered state if menu is open
|
||||
const shouldShowHovered = isHovered || isMenuOpen;
|
||||
|
||||
// Get version information for this file
|
||||
const leafFileId = (isLatestVersion ? file.id : (file.originalFileId || file.id)) as FileId;
|
||||
const hasVersionHistory = (file.versionNumber || 1) > 1; // Show history for any processed file (v2+)
|
||||
const currentVersion = file.versionNumber || 1; // Display original files as v1
|
||||
const isExpanded = expandedFileIds.has(leafFileId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
p="sm"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
backgroundColor: isSelected ? 'var(--mantine-color-gray-0)' : (isHovered ? 'var(--mantine-color-gray-0)' : 'var(--bg-file-list)'),
|
||||
<Box
|
||||
p="sm"
|
||||
style={{
|
||||
cursor: isHistoryFile ? 'default' : 'pointer',
|
||||
backgroundColor: isSelected
|
||||
? 'var(--mantine-color-gray-1)'
|
||||
: (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
|
||||
opacity: isSupported ? 1 : 0.5,
|
||||
transition: 'background-color 0.15s ease'
|
||||
transition: 'background-color 0.15s ease',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files
|
||||
borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history
|
||||
}}
|
||||
onClick={onSelect}
|
||||
onClick={isHistoryFile ? undefined : (e) => onSelect(e.shiftKey)}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Group gap="sm">
|
||||
<Box>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => {}} // Handled by parent onClick
|
||||
size="sm"
|
||||
pl="sm"
|
||||
pr="xs"
|
||||
styles={{
|
||||
input: {
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{!isHistoryFile && (
|
||||
<Box>
|
||||
{/* Checkbox for regular files only */}
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => {}} // Handled by parent onClick
|
||||
size="sm"
|
||||
pl="sm"
|
||||
pr="xs"
|
||||
styles={{
|
||||
input: {
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} truncate>{file.name}</Text>
|
||||
<Text size="xs" c="dimmed">{getFileSize(file)} • {getFileDate(file)}</Text>
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="sm" fw={500} truncate style={{ flex: 1 }}>{file.name}</Text>
|
||||
<Badge size="xs" variant="light" color={"blue"}>
|
||||
v{currentVersion}
|
||||
</Badge>
|
||||
|
||||
</Group>
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="xs" c="dimmed">
|
||||
{getFileSize(file)} • {getFileDate(file)}
|
||||
</Text>
|
||||
|
||||
{/* Tool chain for processed files */}
|
||||
{file.toolHistory && file.toolHistory.length > 0 && (
|
||||
<ToolChain
|
||||
toolChain={file.toolHistory}
|
||||
maxWidth={'150px'}
|
||||
displayStyle="text"
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</Box>
|
||||
{/* Delete button - fades in/out on hover */}
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
size="md"
|
||||
onClick={(e) => { e.stopPropagation(); onRemove(); }}
|
||||
style={{
|
||||
opacity: isHovered ? 1 : 0,
|
||||
transform: isHovered ? 'scale(1)' : 'scale(0.8)',
|
||||
transition: 'opacity 0.3s ease, transform 0.3s ease',
|
||||
pointerEvents: isHovered ? 'auto' : 'none'
|
||||
}}
|
||||
|
||||
{/* Three dots menu - fades in/out on hover */}
|
||||
<Menu
|
||||
position="bottom-end"
|
||||
withinPortal
|
||||
onOpen={() => setIsMenuOpen(true)}
|
||||
onClose={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<DeleteIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
size="md"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
opacity: shouldShowHovered ? 1 : 0,
|
||||
transform: shouldShowHovered ? 'scale(1)' : 'scale(0.8)',
|
||||
transition: 'opacity 0.3s ease, transform 0.3s ease',
|
||||
pointerEvents: shouldShowHovered ? 'auto' : 'none'
|
||||
}}
|
||||
>
|
||||
<MoreVertIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
|
||||
<Menu.Dropdown>
|
||||
{onDownload && (
|
||||
<Menu.Item
|
||||
leftSection={<DownloadIcon style={{ fontSize: 16 }} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDownload();
|
||||
}}
|
||||
>
|
||||
{t('fileManager.download', 'Download')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{/* Show/Hide History option for latest version files */}
|
||||
{isLatestVersion && hasVersionHistory && (
|
||||
<>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
<HistoryIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleExpansion(leafFileId);
|
||||
}}
|
||||
>
|
||||
{
|
||||
(isExpanded ?
|
||||
t('fileManager.hideHistory', 'Hide History') :
|
||||
t('fileManager.showHistory', 'Show History')
|
||||
)
|
||||
}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Restore option for history files */}
|
||||
{isHistoryFile && (
|
||||
<>
|
||||
<Menu.Item
|
||||
leftSection={<RestoreIcon style={{ fontSize: 16 }} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAddToRecents(file);
|
||||
}}
|
||||
>
|
||||
{t('fileManager.restore', 'Restore')}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<DeleteIcon style={{ fontSize: 16 }} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
>
|
||||
{t('fileManager.delete', 'Delete')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Group>
|
||||
</Box>
|
||||
{ <Divider color="var(--mantine-color-gray-3)" />}
|
||||
@@ -81,4 +210,4 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FileListItem;
|
||||
export default FileListItem;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Stack, Text, Button, Group } from '@mantine/core';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import UploadIcon from '@mui/icons-material/Upload';
|
||||
import CloudIcon from '@mui/icons-material/Cloud';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||
@@ -10,7 +10,7 @@ interface FileSourceButtonsProps {
|
||||
horizontal?: boolean;
|
||||
}
|
||||
|
||||
const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
horizontal = false
|
||||
}) => {
|
||||
const { activeSource, onSourceChange, onLocalFileClick } = useFileManagerContext();
|
||||
@@ -44,11 +44,11 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
>
|
||||
{horizontal ? t('fileManager.recent', 'Recent') : t('fileManager.recent', 'Recent')}
|
||||
</Button>
|
||||
|
||||
|
||||
<Button
|
||||
variant="subtle"
|
||||
color='var(--mantine-color-gray-6)'
|
||||
leftSection={<FolderIcon />}
|
||||
leftSection={<UploadIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
onClick={onLocalFileClick}
|
||||
fullWidth={!horizontal}
|
||||
@@ -63,9 +63,9 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{horizontal ? t('fileManager.localFiles', 'Local') : t('fileManager.localFiles', 'Local Files')}
|
||||
{horizontal ? t('fileUpload.uploadFiles', 'Upload') : t('fileUpload.uploadFiles', 'Upload Files')}
|
||||
</Button>
|
||||
|
||||
|
||||
<Button
|
||||
variant={buttonProps.variant('drive')}
|
||||
leftSection={<CloudIcon />}
|
||||
@@ -100,4 +100,4 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FileSourceButtons;
|
||||
export default FileSourceButtons;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Stack, Box } from '@mantine/core';
|
||||
import { Box } from '@mantine/core';
|
||||
import FileSourceButtons from './FileSourceButtons';
|
||||
import FileDetails from './FileDetails';
|
||||
import SearchInput from './SearchInput';
|
||||
import FileListArea from './FileListArea';
|
||||
import FileActions from './FileActions';
|
||||
import HiddenFileInput from './HiddenFileInput';
|
||||
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||
|
||||
@@ -18,14 +19,15 @@ const MobileLayout: React.FC = () => {
|
||||
const calculateFileListHeight = () => {
|
||||
// Base modal height minus padding and gaps
|
||||
const baseHeight = `calc(${modalHeight} - 2rem)`; // Account for Stack padding
|
||||
|
||||
|
||||
// Estimate heights of fixed components
|
||||
const fileSourceHeight = '3rem'; // FileSourceButtons height
|
||||
const fileDetailsHeight = selectedFiles.length > 0 ? '10rem' : '8rem'; // FileDetails compact height
|
||||
const fileActionsHeight = activeSource === 'recent' ? '3rem' : '0rem'; // FileActions height (now at bottom)
|
||||
const searchHeight = activeSource === 'recent' ? '3rem' : '0rem'; // SearchInput height
|
||||
const gapHeight = activeSource === 'recent' ? '3rem' : '2rem'; // Stack gaps
|
||||
|
||||
return `calc(${baseHeight} - ${fileSourceHeight} - ${fileDetailsHeight} - ${searchHeight} - ${gapHeight})`;
|
||||
const gapHeight = activeSource === 'recent' ? '3.75rem' : '2rem'; // Stack gaps
|
||||
|
||||
return `calc(${baseHeight} - ${fileSourceHeight} - ${fileDetailsHeight} - ${fileActionsHeight} - ${searchHeight} - ${gapHeight})`;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -34,15 +36,15 @@ const MobileLayout: React.FC = () => {
|
||||
<Box style={{ flexShrink: 0 }}>
|
||||
<FileSourceButtons horizontal={true} />
|
||||
</Box>
|
||||
|
||||
|
||||
<Box style={{ flexShrink: 0 }}>
|
||||
<FileDetails compact={true} />
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Section 3 & 4: Search Bar + File List - Unified background extending to modal edge */}
|
||||
<Box style={{
|
||||
<Box style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: 'var(--bg-file-list)',
|
||||
borderRadius: '0.5rem',
|
||||
@@ -51,18 +53,26 @@ const MobileLayout: React.FC = () => {
|
||||
minHeight: 0
|
||||
}}>
|
||||
{activeSource === 'recent' && (
|
||||
<Box style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
||||
}}>
|
||||
<SearchInput />
|
||||
</Box>
|
||||
<>
|
||||
<Box style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
||||
}}>
|
||||
<SearchInput />
|
||||
</Box>
|
||||
<Box style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
||||
}}>
|
||||
<FileActions />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<Box style={{ flex: 1, minHeight: 0 }}>
|
||||
<FileListArea
|
||||
scrollAreaHeight={calculateFileListHeight()}
|
||||
scrollAreaStyle={{
|
||||
scrollAreaStyle={{
|
||||
height: calculateFileListHeight(),
|
||||
maxHeight: '60vh',
|
||||
minHeight: '9.375rem',
|
||||
@@ -73,11 +83,11 @@ const MobileLayout: React.FC = () => {
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Hidden file input for local file selection */}
|
||||
<HiddenFileInput />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileLayout;
|
||||
export default MobileLayout;
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Stack,
|
||||
Paper,
|
||||
Text,
|
||||
Badge,
|
||||
Group,
|
||||
Collapse,
|
||||
Box,
|
||||
ScrollArea,
|
||||
Code,
|
||||
Divider
|
||||
} from '@mantine/core';
|
||||
import { useFileContext } from '../../contexts/FileContext';
|
||||
import { FileOperation, FileOperationHistory as FileOperationHistoryType } from '../../types/fileContext';
|
||||
import { PageOperation } from '../../types/pageEditor';
|
||||
|
||||
interface FileOperationHistoryProps {
|
||||
fileId: string;
|
||||
showOnlyApplied?: boolean;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
const FileOperationHistory: React.FC<FileOperationHistoryProps> = ({
|
||||
fileId,
|
||||
showOnlyApplied = false,
|
||||
maxHeight = 400
|
||||
}) => {
|
||||
const { getFileHistory, getAppliedOperations } = useFileContext();
|
||||
|
||||
const history = getFileHistory(fileId);
|
||||
const allOperations = showOnlyApplied ? getAppliedOperations(fileId) : history?.operations || [];
|
||||
const operations = allOperations.filter(op => 'fileIds' in op) as FileOperation[];
|
||||
|
||||
const formatTimestamp = (timestamp: number) => {
|
||||
return new Date(timestamp).toLocaleString();
|
||||
};
|
||||
|
||||
const getOperationIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'split': return '✂️';
|
||||
case 'merge': return '🔗';
|
||||
case 'compress': return '🗜️';
|
||||
case 'rotate': return '🔄';
|
||||
case 'delete': return '🗑️';
|
||||
case 'move': return '↕️';
|
||||
case 'insert': return '📄';
|
||||
case 'upload': return '⬆️';
|
||||
case 'add': return '➕';
|
||||
case 'remove': return '➖';
|
||||
case 'replace': return '🔄';
|
||||
case 'convert': return '🔄';
|
||||
default: return '⚙️';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'applied': return 'green';
|
||||
case 'failed': return 'red';
|
||||
case 'pending': return 'yellow';
|
||||
default: return 'gray';
|
||||
}
|
||||
};
|
||||
|
||||
const renderOperationDetails = (operation: FileOperation) => {
|
||||
if ('metadata' in operation && operation.metadata) {
|
||||
const { metadata } = operation;
|
||||
return (
|
||||
<Box mt="xs">
|
||||
{metadata.parameters && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Parameters: <Code>{JSON.stringify(metadata.parameters, null, 2)}</Code>
|
||||
</Text>
|
||||
)}
|
||||
{metadata.originalFileName && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Original file: {metadata.originalFileName}
|
||||
</Text>
|
||||
)}
|
||||
{metadata.outputFileNames && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Output files: {metadata.outputFileNames.join(', ')}
|
||||
</Text>
|
||||
)}
|
||||
{metadata.fileSize && (
|
||||
<Text size="xs" c="dimmed">
|
||||
File size: {(metadata.fileSize / 1024 / 1024).toFixed(2)} MB
|
||||
</Text>
|
||||
)}
|
||||
{metadata.pageCount && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Pages: {metadata.pageCount}
|
||||
</Text>
|
||||
)}
|
||||
{metadata.error && (
|
||||
<Text size="xs" c="red">
|
||||
Error: {metadata.error}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!history || operations.length === 0) {
|
||||
return (
|
||||
<Paper p="md" withBorder>
|
||||
<Text c="dimmed" ta="center">
|
||||
{showOnlyApplied ? 'No applied operations found' : 'No operation history available'}
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper p="md" withBorder>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Text fw={500}>
|
||||
{showOnlyApplied ? 'Applied Operations' : 'Operation History'}
|
||||
</Text>
|
||||
<Badge variant="light" color="blue">
|
||||
{operations.length} operations
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<ScrollArea h={maxHeight}>
|
||||
<Stack gap="sm">
|
||||
{operations.map((operation, index) => (
|
||||
<Paper key={operation.id} p="sm" withBorder radius="sm" bg="gray.0">
|
||||
<Group justify="space-between" align="start">
|
||||
<Group gap="xs">
|
||||
<Text span size="lg">
|
||||
{getOperationIcon(operation.type)}
|
||||
</Text>
|
||||
<Box>
|
||||
<Text fw={500} size="sm">
|
||||
{operation.type.charAt(0).toUpperCase() + operation.type.slice(1)}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{formatTimestamp(operation.timestamp)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Badge
|
||||
variant="filled"
|
||||
color={getStatusColor(operation.status)}
|
||||
size="sm"
|
||||
>
|
||||
{operation.status}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
{renderOperationDetails(operation)}
|
||||
|
||||
{index < operations.length - 1 && <Divider mt="sm" />}
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
|
||||
{history && (
|
||||
<Group justify="space-between" mt="sm" pt="sm" style={{ borderTop: '1px solid var(--mantine-color-gray-3)' }}>
|
||||
<Text size="xs" c="dimmed">
|
||||
Created: {formatTimestamp(history.createdAt)}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Last modified: {formatTimestamp(history.lastModified)}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileOperationHistory;
|
||||
@@ -0,0 +1,21 @@
|
||||
.workbench-scrollable {
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.workbench-scrollable::-webkit-scrollbar {
|
||||
width: 0.375rem;
|
||||
}
|
||||
|
||||
.workbench-scrollable::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.workbench-scrollable::-webkit-scrollbar-thumb {
|
||||
background-color: var(--mantine-color-gray-4);
|
||||
border-radius: 0.1875rem;
|
||||
}
|
||||
|
||||
.workbench-scrollable::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--mantine-color-gray-5);
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||
import { useFileHandler } from '../../hooks/useFileHandler';
|
||||
import { useFileContext } from '../../contexts/FileContext';
|
||||
import { useFileState } from '../../contexts/FileContext';
|
||||
import { useNavigationState, useNavigationActions } from '../../contexts/NavigationContext';
|
||||
import { useToolManagement } from '../../hooks/useToolManagement';
|
||||
import './Workbench.css';
|
||||
|
||||
import TopControls from '../shared/TopControls';
|
||||
import FileEditor from '../fileEditor/FileEditor';
|
||||
import PageEditor from '../pageEditor/PageEditor';
|
||||
import PageEditorControls from '../pageEditor/PageEditorControls';
|
||||
import Viewer from '../viewer/Viewer';
|
||||
import ToolRenderer from '../tools/ToolRenderer';
|
||||
import LandingPage from '../shared/LandingPage';
|
||||
import Footer from '../shared/Footer';
|
||||
|
||||
// No props needed - component uses contexts directly
|
||||
export default function Workbench() {
|
||||
const { t } = useTranslation();
|
||||
const { isRainbowMode } = useRainbowThemeContext();
|
||||
|
||||
// Use context-based hooks to eliminate all prop drilling
|
||||
const { activeFiles, currentView, setCurrentView } = useFileContext();
|
||||
const { state } = useFileState();
|
||||
const { workbench: currentView } = useNavigationState();
|
||||
const { actions: navActions } = useNavigationActions();
|
||||
const setCurrentView = navActions.setWorkbench;
|
||||
const activeFiles = state.files.ids;
|
||||
const {
|
||||
previewFile,
|
||||
pageEditorFunctions,
|
||||
@@ -30,8 +34,15 @@ export default function Workbench() {
|
||||
setSidebarsVisible
|
||||
} = useToolWorkflow();
|
||||
|
||||
const { selectedToolKey, selectedTool, handleToolSelect } = useToolWorkflow();
|
||||
const { addToActiveFiles } = useFileHandler();
|
||||
const { handleToolSelect } = useToolWorkflow();
|
||||
|
||||
// Get navigation state - this is the source of truth
|
||||
const { selectedTool: selectedToolId } = useNavigationState();
|
||||
|
||||
// Get tool registry to look up selected tool
|
||||
const { toolRegistry } = useToolManagement();
|
||||
const selectedTool = selectedToolId ? toolRegistry[selectedToolId] : null;
|
||||
const { addFiles } = useFileHandler();
|
||||
|
||||
const handlePreviewClose = () => {
|
||||
setPreviewFile(null);
|
||||
@@ -47,12 +58,12 @@ export default function Workbench() {
|
||||
handleToolSelect('convert');
|
||||
sessionStorage.removeItem('previousMode');
|
||||
} else {
|
||||
setCurrentView('fileEditor' as any);
|
||||
setCurrentView('fileEditor');
|
||||
}
|
||||
};
|
||||
|
||||
const renderMainContent = () => {
|
||||
if (!activeFiles[0]) {
|
||||
if (activeFiles.length === 0) {
|
||||
return (
|
||||
<LandingPage
|
||||
/>
|
||||
@@ -63,17 +74,15 @@ export default function Workbench() {
|
||||
case "fileEditor":
|
||||
return (
|
||||
<FileEditor
|
||||
toolMode={!!selectedToolKey}
|
||||
showUpload={true}
|
||||
showBulkActions={!selectedToolKey}
|
||||
toolMode={!!selectedToolId}
|
||||
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
||||
{...(!selectedToolKey && {
|
||||
onOpenPageEditor: (file) => {
|
||||
setCurrentView("pageEditor" as any);
|
||||
{...(!selectedToolId && {
|
||||
onOpenPageEditor: () => {
|
||||
setCurrentView("pageEditor");
|
||||
},
|
||||
onMergeFiles: (filesToMerge) => {
|
||||
filesToMerge.forEach(addToActiveFiles);
|
||||
setCurrentView("viewer" as any);
|
||||
addFiles(filesToMerge);
|
||||
setCurrentView("viewer");
|
||||
}
|
||||
})}
|
||||
/>
|
||||
@@ -105,25 +114,22 @@ export default function Workbench() {
|
||||
onRotate={pageEditorFunctions.handleRotate}
|
||||
onDelete={pageEditorFunctions.handleDelete}
|
||||
onSplit={pageEditorFunctions.handleSplit}
|
||||
onExportSelected={pageEditorFunctions.onExportSelected}
|
||||
onSplitAll={pageEditorFunctions.handleSplitAll}
|
||||
onPageBreak={pageEditorFunctions.handlePageBreak}
|
||||
onPageBreakAll={pageEditorFunctions.handlePageBreakAll}
|
||||
onExportAll={pageEditorFunctions.onExportAll}
|
||||
exportLoading={pageEditorFunctions.exportLoading}
|
||||
selectionMode={pageEditorFunctions.selectionMode}
|
||||
selectedPages={pageEditorFunctions.selectedPages}
|
||||
selectedPageIds={pageEditorFunctions.selectedPageIds}
|
||||
displayDocument={pageEditorFunctions.displayDocument}
|
||||
splitPositions={pageEditorFunctions.splitPositions}
|
||||
totalPages={pageEditorFunctions.totalPages}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
default:
|
||||
// Check if it's a tool view
|
||||
if (selectedToolKey && selectedTool) {
|
||||
return (
|
||||
<ToolRenderer
|
||||
selectedToolKey={selectedToolKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<LandingPage/>
|
||||
);
|
||||
@@ -132,7 +138,7 @@ export default function Workbench() {
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="flex-1 h-screen min-w-80 relative flex flex-col"
|
||||
className="flex-1 h-full min-w-80 relative flex flex-col"
|
||||
style={
|
||||
isRainbowMode
|
||||
? {} // No background color in rainbow mode
|
||||
@@ -142,19 +148,22 @@ export default function Workbench() {
|
||||
{/* Top Controls */}
|
||||
<TopControls
|
||||
currentView={currentView}
|
||||
setCurrentView={setCurrentView as any /* FIX ME */}
|
||||
selectedToolKey={selectedToolKey}
|
||||
setCurrentView={setCurrentView}
|
||||
selectedToolKey={selectedToolId}
|
||||
/>
|
||||
|
||||
{/* Main content area */}
|
||||
<Box
|
||||
className="flex-1 min-h-0 relative z-10"
|
||||
className="flex-1 min-h-0 relative z-10 workbench-scrollable "
|
||||
style={{
|
||||
transition: 'opacity 0.15s ease-in-out',
|
||||
marginTop: '1rem',
|
||||
}}
|
||||
>
|
||||
{renderMainContent()}
|
||||
</Box>
|
||||
|
||||
<Footer analyticsEnabled />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Paper, Group, TextInput, Button, Text } from '@mantine/core';
|
||||
import { Group, TextInput, Button, Text } from '@mantine/core';
|
||||
|
||||
interface BulkSelectionPanelProps {
|
||||
csvInput: string;
|
||||
setCsvInput: (value: string) => void;
|
||||
selectedPages: number[];
|
||||
selectedPageIds: string[];
|
||||
displayDocument?: { pages: { id: string; pageNumber: number }[] };
|
||||
onUpdatePagesFromCSV: () => void;
|
||||
}
|
||||
|
||||
const BulkSelectionPanel = ({
|
||||
csvInput,
|
||||
setCsvInput,
|
||||
selectedPages,
|
||||
selectedPageIds,
|
||||
displayDocument,
|
||||
onUpdatePagesFromCSV,
|
||||
}: BulkSelectionPanelProps) => {
|
||||
return (
|
||||
<Paper p="md" mb="md" withBorder>
|
||||
<>
|
||||
<Group>
|
||||
<TextInput
|
||||
value={csvInput}
|
||||
@@ -30,12 +32,15 @@ const BulkSelectionPanel = ({
|
||||
Apply
|
||||
</Button>
|
||||
</Group>
|
||||
{selectedPages.length > 0 && (
|
||||
{selectedPageIds.length > 0 && (
|
||||
<Text size="sm" c="dimmed" mt="sm">
|
||||
Selected: {selectedPages.length} pages
|
||||
Selected: {selectedPageIds.length} pages ({displayDocument ? selectedPageIds.map(id => {
|
||||
const page = displayDocument.pages.find(p => p.id === id);
|
||||
return page?.pageNumber || 0;
|
||||
}).filter(n => n > 0).join(', ') : ''})
|
||||
</Text>
|
||||
)}
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,135 +1,156 @@
|
||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import styles from './PageEditor.module.css';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { GRID_CONSTANTS } from './constants';
|
||||
|
||||
interface DragDropItem {
|
||||
id: string;
|
||||
splitBefore?: boolean;
|
||||
splitAfter?: boolean;
|
||||
}
|
||||
|
||||
interface DragDropGridProps<T extends DragDropItem> {
|
||||
items: T[];
|
||||
selectedItems: number[];
|
||||
selectedItems: string[];
|
||||
selectionMode: boolean;
|
||||
isAnimating: boolean;
|
||||
onDragStart: (pageNumber: number) => void;
|
||||
onDragEnd: () => void;
|
||||
onDragOver: (e: React.DragEvent) => void;
|
||||
onDragEnter: (pageNumber: number) => void;
|
||||
onDragLeave: () => void;
|
||||
onDrop: (e: React.DragEvent, targetPageNumber: number | 'end') => void;
|
||||
onEndZoneDragEnter: () => void;
|
||||
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
|
||||
renderItem: (item: T, index: number, refs: React.MutableRefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
|
||||
renderSplitMarker?: (item: T, index: number) => React.ReactNode;
|
||||
draggedItem: number | null;
|
||||
dropTarget: number | 'end' | null;
|
||||
multiItemDrag: {pageNumbers: number[], count: number} | null;
|
||||
dragPosition: {x: number, y: number} | null;
|
||||
}
|
||||
|
||||
const DragDropGrid = <T extends DragDropItem>({
|
||||
items,
|
||||
selectedItems,
|
||||
selectionMode,
|
||||
isAnimating,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
onDragOver,
|
||||
onDragEnter,
|
||||
onDragLeave,
|
||||
onDrop,
|
||||
onEndZoneDragEnter,
|
||||
renderItem,
|
||||
renderSplitMarker,
|
||||
draggedItem,
|
||||
dropTarget,
|
||||
multiItemDrag,
|
||||
dragPosition,
|
||||
}: DragDropGridProps<T>) => {
|
||||
const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Global drag cleanup
|
||||
// Responsive grid configuration
|
||||
const [itemsPerRow, setItemsPerRow] = useState(4);
|
||||
const OVERSCAN = items.length > 1000 ? GRID_CONSTANTS.OVERSCAN_LARGE : GRID_CONSTANTS.OVERSCAN_SMALL;
|
||||
|
||||
// Calculate items per row based on container width
|
||||
const calculateItemsPerRow = useCallback(() => {
|
||||
if (!containerRef.current) return 4; // Default fallback
|
||||
|
||||
const containerWidth = containerRef.current.offsetWidth;
|
||||
if (containerWidth === 0) return 4; // Container not measured yet
|
||||
|
||||
// Convert rem to pixels for calculation
|
||||
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
const ITEM_WIDTH = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
|
||||
const ITEM_GAP = parseFloat(GRID_CONSTANTS.ITEM_GAP) * remToPx;
|
||||
|
||||
// Calculate how many items fit: (width - gap) / (itemWidth + gap)
|
||||
const availableWidth = containerWidth - ITEM_GAP; // Account for first gap
|
||||
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
|
||||
const calculated = Math.floor(availableWidth / itemWithGap);
|
||||
|
||||
return Math.max(1, calculated); // At least 1 item per row
|
||||
}, []);
|
||||
|
||||
// Update items per row when container resizes
|
||||
useEffect(() => {
|
||||
const handleGlobalDragEnd = () => {
|
||||
onDragEnd();
|
||||
const updateLayout = () => {
|
||||
const newItemsPerRow = calculateItemsPerRow();
|
||||
setItemsPerRow(newItemsPerRow);
|
||||
};
|
||||
|
||||
const handleGlobalDrop = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
// Initial calculation
|
||||
updateLayout();
|
||||
|
||||
if (draggedItem) {
|
||||
document.addEventListener('dragend', handleGlobalDragEnd);
|
||||
document.addEventListener('drop', handleGlobalDrop);
|
||||
// Listen for window resize
|
||||
window.addEventListener('resize', updateLayout);
|
||||
|
||||
// Use ResizeObserver for container size changes
|
||||
const resizeObserver = new ResizeObserver(updateLayout);
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('dragend', handleGlobalDragEnd);
|
||||
document.removeEventListener('drop', handleGlobalDrop);
|
||||
window.removeEventListener('resize', updateLayout);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [draggedItem, onDragEnd]);
|
||||
}, [calculateItemsPerRow]);
|
||||
|
||||
// Virtualization with react-virtual library
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: Math.ceil(items.length / itemsPerRow),
|
||||
getScrollElement: () => containerRef.current?.closest('[data-scrolling-container]') as Element,
|
||||
estimateSize: () => {
|
||||
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
return parseFloat(GRID_CONSTANTS.ITEM_HEIGHT) * remToPx;
|
||||
},
|
||||
overscan: OVERSCAN,
|
||||
});
|
||||
|
||||
// Calculate optimal width for centering
|
||||
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
const itemWidth = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
|
||||
const itemGap = parseFloat(GRID_CONSTANTS.ITEM_GAP) * remToPx;
|
||||
const gridWidth = itemsPerRow * itemWidth + (itemsPerRow - 1) * itemGap;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
ref={containerRef}
|
||||
style={{
|
||||
// Basic container styles
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '1.5rem',
|
||||
justifyContent: 'flex-start',
|
||||
paddingBottom: '100px',
|
||||
// Performance optimizations for smooth scrolling
|
||||
willChange: 'scroll-position',
|
||||
transform: 'translateZ(0)', // Force hardware acceleration
|
||||
backfaceVisibility: 'hidden',
|
||||
// Use containment for better rendering performance
|
||||
contain: 'layout style paint',
|
||||
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
margin: '0 auto',
|
||||
maxWidth: `${gridWidth}px`,
|
||||
}}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<React.Fragment key={item.id}>
|
||||
{/* Split marker */}
|
||||
{renderSplitMarker && item.splitBefore && index > 0 && renderSplitMarker(item, index)}
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const startIndex = virtualRow.index * itemsPerRow;
|
||||
const endIndex = Math.min(startIndex + itemsPerRow, items.length);
|
||||
const rowItems = items.slice(startIndex, endIndex);
|
||||
|
||||
{/* Item */}
|
||||
{renderItem(item, index, itemRefs)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
return (
|
||||
<div
|
||||
key={virtualRow.index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: `${virtualRow.size}px`,
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: GRID_CONSTANTS.ITEM_GAP,
|
||||
justifyContent: 'flex-start',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{rowItems.map((item, itemIndex) => {
|
||||
const actualIndex = startIndex + itemIndex;
|
||||
return (
|
||||
<React.Fragment key={item.id}>
|
||||
{/* Item */}
|
||||
{renderItem(item, actualIndex, itemRefs)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* End drop zone */}
|
||||
<div className="w-[20rem] h-[20rem] flex items-center justify-center flex-shrink-0">
|
||||
<div
|
||||
data-drop-zone="end"
|
||||
className={`cursor-pointer select-none w-[15rem] h-[15rem] flex items-center justify-center flex-shrink-0 shadow-sm hover:shadow-md transition-all relative ${
|
||||
dropTarget === 'end'
|
||||
? 'ring-2 ring-green-500 bg-green-50'
|
||||
: 'bg-white hover:bg-blue-50 border-2 border-dashed border-gray-300 hover:border-blue-400'
|
||||
}`}
|
||||
style={{ borderRadius: '12px' }}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={onEndZoneDragEnter}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={(e) => onDrop(e, 'end')}
|
||||
>
|
||||
<div className="text-gray-500 text-sm text-center font-medium">
|
||||
Drop here to<br />move to end
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Multi-item drag indicator */}
|
||||
{multiItemDrag && dragPosition && (
|
||||
<div
|
||||
className={styles.multiDragIndicator}
|
||||
style={{
|
||||
left: dragPosition.x,
|
||||
top: dragPosition.y,
|
||||
}}
|
||||
>
|
||||
{multiItemDrag.count} items
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Text, Checkbox, Tooltip, ActionIcon, Badge, Modal } from '@mantine/core';
|
||||
import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
||||
import { ActionIcon, CheckboxIndicator } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
|
||||
import styles from './PageEditor.module.css';
|
||||
import FileOperationHistory from '../history/FileOperationHistory';
|
||||
import { useFileContext } from '../../contexts/FileContext';
|
||||
import { FileId } from '../../types/file';
|
||||
|
||||
interface FileItem {
|
||||
id: string;
|
||||
id: FileId;
|
||||
name: string;
|
||||
pageCount: number;
|
||||
thumbnail: string;
|
||||
thumbnail: string | null;
|
||||
size: number;
|
||||
splitBefore?: boolean;
|
||||
modifiedAt?: number | string | Date;
|
||||
}
|
||||
|
||||
interface FileThumbnailProps {
|
||||
@@ -26,20 +28,12 @@ interface FileThumbnailProps {
|
||||
totalFiles: number;
|
||||
selectedFiles: string[];
|
||||
selectionMode: boolean;
|
||||
draggedFile: string | null;
|
||||
dropTarget: string | null;
|
||||
isAnimating: boolean;
|
||||
fileRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
|
||||
onDragStart: (fileId: string) => void;
|
||||
onDragEnd: () => void;
|
||||
onDragOver: (e: React.DragEvent) => void;
|
||||
onDragEnter: (fileId: string) => void;
|
||||
onDragLeave: () => void;
|
||||
onDrop: (e: React.DragEvent, fileId: string) => void;
|
||||
onToggleFile: (fileId: string) => void;
|
||||
onDeleteFile: (fileId: string) => void;
|
||||
onViewFile: (fileId: string) => void;
|
||||
onToggleFile: (fileId: FileId) => void;
|
||||
onDeleteFile: (fileId: FileId) => void;
|
||||
onViewFile: (fileId: FileId) => void;
|
||||
onSetStatus: (status: string) => void;
|
||||
onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void;
|
||||
onDownloadFile?: (fileId: FileId) => void;
|
||||
toolMode?: boolean;
|
||||
isSupported?: boolean;
|
||||
}
|
||||
@@ -47,122 +41,259 @@ interface FileThumbnailProps {
|
||||
const FileThumbnail = ({
|
||||
file,
|
||||
index,
|
||||
totalFiles,
|
||||
selectedFiles,
|
||||
selectionMode,
|
||||
draggedFile,
|
||||
dropTarget,
|
||||
isAnimating,
|
||||
fileRefs,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
onDragOver,
|
||||
onDragEnter,
|
||||
onDragLeave,
|
||||
onDrop,
|
||||
onToggleFile,
|
||||
onDeleteFile,
|
||||
onViewFile,
|
||||
onSetStatus,
|
||||
toolMode = false,
|
||||
onReorderFiles,
|
||||
onDownloadFile,
|
||||
isSupported = true,
|
||||
}: FileThumbnailProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
const { pinnedFiles, pinFile, unpinFile, isFilePinned, activeFiles } = useFileContext();
|
||||
const { pinFile, unpinFile, isFilePinned, activeFiles } = useFileContext();
|
||||
|
||||
// Find the actual File object that corresponds to this FileItem
|
||||
const actualFile = activeFiles.find(f => f.name === file.name && f.size === file.size);
|
||||
// ---- Drag state ----
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragElementRef = useRef<HTMLDivElement | null>(null);
|
||||
const [actionsWidth, setActionsWidth] = useState<number | undefined>(undefined);
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
// Resolve the actual File object for pin/unpin operations
|
||||
const actualFile = useMemo(() => {
|
||||
return activeFiles.find(f => f.fileId === file.id);
|
||||
}, [activeFiles, file.id]);
|
||||
const isPinned = actualFile ? isFilePinned(actualFile) : false;
|
||||
|
||||
const downloadSelectedFile = useCallback(() => {
|
||||
// Prefer parent-provided handler if available
|
||||
if (typeof onDownloadFile === 'function') {
|
||||
onDownloadFile(file.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: attempt to download using the File object if provided
|
||||
const maybeFile = (file as unknown as { file?: File }).file;
|
||||
if (maybeFile instanceof File) {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(maybeFile);
|
||||
link.download = maybeFile.name || file.name || 'download';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(link.href);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we can't find a way to download, surface a status message
|
||||
onSetStatus?.(typeof t === 'function' ? t('downloadUnavailable', 'Download unavailable for this item') : 'Download unavailable for this item');
|
||||
}, [file, onDownloadFile, onSetStatus, t]);
|
||||
const handleRef = useRef<HTMLSpanElement | null>(null);
|
||||
|
||||
// ---- Selection ----
|
||||
const isSelected = selectedFiles.includes(file.id);
|
||||
|
||||
// ---- Drag & drop wiring ----
|
||||
const fileElementRef = useCallback((element: HTMLDivElement | null) => {
|
||||
if (!element) return;
|
||||
|
||||
dragElementRef.current = element;
|
||||
|
||||
const dragCleanup = draggable({
|
||||
element,
|
||||
getInitialData: () => ({
|
||||
type: 'file',
|
||||
fileId: file.id,
|
||||
fileName: file.name,
|
||||
selectedFiles: [file.id] // Always drag only this file, ignore selection state
|
||||
}),
|
||||
onDragStart: () => {
|
||||
setIsDragging(true);
|
||||
},
|
||||
onDrop: () => {
|
||||
setIsDragging(false);
|
||||
}
|
||||
});
|
||||
|
||||
const dropCleanup = dropTargetForElements({
|
||||
element,
|
||||
getData: () => ({
|
||||
type: 'file',
|
||||
fileId: file.id
|
||||
}),
|
||||
canDrop: ({ source }) => {
|
||||
const sourceData = source.data;
|
||||
return sourceData.type === 'file' && sourceData.fileId !== file.id;
|
||||
},
|
||||
onDrop: ({ source }) => {
|
||||
const sourceData = source.data;
|
||||
if (sourceData.type === 'file' && onReorderFiles) {
|
||||
const sourceFileId = sourceData.fileId as FileId;
|
||||
const selectedFileIds = sourceData.selectedFiles as FileId[];
|
||||
onReorderFiles(sourceFileId, file.id, selectedFileIds);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
dragCleanup();
|
||||
dropCleanup();
|
||||
};
|
||||
}, [file.id, file.name, selectedFiles, onReorderFiles]);
|
||||
|
||||
// Update dropdown width on resize
|
||||
useEffect(() => {
|
||||
const update = () => {
|
||||
if (dragElementRef.current) setActionsWidth(dragElementRef.current.offsetWidth);
|
||||
};
|
||||
update();
|
||||
window.addEventListener('resize', update);
|
||||
return () => window.removeEventListener('resize', update);
|
||||
}, []);
|
||||
|
||||
// Close the actions dropdown when hovering outside this file card (and its dropdown)
|
||||
useEffect(() => {
|
||||
if (!showActions) return;
|
||||
|
||||
const isInsideCard = (target: EventTarget | null) => {
|
||||
const container = dragElementRef.current;
|
||||
if (!container) return false;
|
||||
return target instanceof Node && container.contains(target);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isInsideCard(e.target)) {
|
||||
setShowActions(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
// On touch devices, close if the touch target is outside the card
|
||||
if (!isInsideCard(e.target)) {
|
||||
setShowActions(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('touchstart', handleTouchStart, { passive: true });
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('touchstart', handleTouchStart);
|
||||
};
|
||||
}, [showActions]);
|
||||
|
||||
// ---- Card interactions ----
|
||||
const handleCardClick = () => {
|
||||
if (!isSupported) return;
|
||||
onToggleFile(file.id);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
fileRefs.current.set(file.id, el);
|
||||
} else {
|
||||
fileRefs.current.delete(file.id);
|
||||
}
|
||||
}}
|
||||
ref={fileElementRef}
|
||||
data-file-id={file.id}
|
||||
data-testid="file-thumbnail"
|
||||
className={`
|
||||
${styles.pageContainer}
|
||||
!rounded-lg
|
||||
cursor-grab
|
||||
select-none
|
||||
w-[20rem]
|
||||
h-[24rem]
|
||||
flex flex-col items-center justify-center
|
||||
flex-shrink-0
|
||||
shadow-sm
|
||||
hover:shadow-md
|
||||
transition-all
|
||||
relative
|
||||
${selectionMode
|
||||
? 'bg-white hover:bg-gray-50'
|
||||
: 'bg-white hover:bg-gray-50'}
|
||||
${draggedFile === file.id ? 'opacity-50 scale-95' : ''}
|
||||
`}
|
||||
data-selected={isSelected}
|
||||
data-supported={isSupported}
|
||||
className={`${styles.card} w-[18rem] h-[22rem] select-none flex flex-col shadow-sm transition-all relative`}
|
||||
style={{
|
||||
transform: (() => {
|
||||
if (!isAnimating && draggedFile && file.id !== draggedFile && dropTarget === file.id) {
|
||||
return 'translateX(20px)';
|
||||
}
|
||||
return 'translateX(0)';
|
||||
})(),
|
||||
transition: isAnimating ? 'none' : 'transform 0.2s ease-in-out',
|
||||
opacity: isSupported ? 1 : 0.5,
|
||||
filter: isSupported ? 'none' : 'grayscale(50%)'
|
||||
opacity: isSupported ? (isDragging ? 0.9 : 1) : 0.5,
|
||||
filter: isSupported ? 'none' : 'grayscale(50%)',
|
||||
}}
|
||||
draggable
|
||||
onDragStart={() => onDragStart(file.id)}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={() => onDragEnter(file.id)}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={(e) => onDrop(e, file.id)}
|
||||
tabIndex={0}
|
||||
role="listitem"
|
||||
aria-selected={isSelected}
|
||||
onClick={handleCardClick}
|
||||
>
|
||||
{selectionMode && (
|
||||
<div
|
||||
className={styles.checkboxContainer}
|
||||
data-testid="file-thumbnail-checkbox"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 4,
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '4px',
|
||||
padding: '2px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
pointerEvents: 'auto'
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onDragStart={(e) => {
|
||||
e.preventDefault();
|
||||
{/* Header bar */}
|
||||
<div
|
||||
className={`${styles.header} ${
|
||||
isSelected ? styles.headerSelected : styles.headerResting
|
||||
}`}
|
||||
>
|
||||
{/* Logo/checkbox area */}
|
||||
<div className={styles.logoMark}>
|
||||
{isSupported ? (
|
||||
<CheckboxIndicator
|
||||
checked={isSelected}
|
||||
onChange={() => onToggleFile(file.id)}
|
||||
color="var(--checkbox-checked-bg)"
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.unsupportedPill}>
|
||||
<span>
|
||||
{t('unsupported', 'Unsupported')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Centered index */}
|
||||
<div className={styles.headerIndex} aria-label={`Position ${index + 1}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
|
||||
{/* Kebab menu */}
|
||||
<ActionIcon
|
||||
aria-label={t('moreOptions', 'More options')}
|
||||
variant="subtle"
|
||||
className={styles.kebab}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowActions((v) => !v);
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedFiles.includes(file.id)}
|
||||
onChange={(event) => {
|
||||
event.stopPropagation();
|
||||
if (isSupported) {
|
||||
onToggleFile(file.id);
|
||||
<MoreVertIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
|
||||
{/* Actions overlay */}
|
||||
{showActions && (
|
||||
<div
|
||||
className={styles.actionsOverlay}
|
||||
style={{ width: actionsWidth }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className={styles.actionRow}
|
||||
onClick={() => {
|
||||
if (actualFile) {
|
||||
if (isPinned) {
|
||||
unpinFile(actualFile);
|
||||
onSetStatus?.(`Unpinned ${file.name}`);
|
||||
} else {
|
||||
pinFile(actualFile);
|
||||
onSetStatus?.(`Pinned ${file.name}`);
|
||||
}
|
||||
}
|
||||
setShowActions(false);
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
disabled={!isSupported}
|
||||
size="sm"
|
||||
/>
|
||||
>
|
||||
{isPinned ? <PushPinIcon fontSize="small" /> : <PushPinOutlinedIcon fontSize="small" />}
|
||||
<span>{isPinned ? t('unpin', 'Unpin') : t('pin', 'Pin')}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.actionRow}
|
||||
onClick={() => { downloadSelectedFile(); setShowActions(false); }}
|
||||
>
|
||||
<DownloadOutlinedIcon fontSize="small" />
|
||||
<span>{t('download', 'Download')}</span>
|
||||
</button>
|
||||
|
||||
<div className={styles.actionsDivider} />
|
||||
|
||||
<button
|
||||
className={`${styles.actionRow} ${styles.actionDanger}`}
|
||||
onClick={() => {
|
||||
onDeleteFile(file.id);
|
||||
onSetStatus(`Deleted ${file.name}`);
|
||||
setShowActions(false);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlineIcon fontSize="small" />
|
||||
<span>{t('delete', 'Delete')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -184,210 +315,46 @@ const FileThumbnail = ({
|
||||
boxShadow: '2px 2px 0 rgba(0,0,0,0.1), 4px 4px 0 rgba(0,0,0,0.05)'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={file.thumbnail}
|
||||
alt={file.name}
|
||||
{file.thumbnail && (
|
||||
<img
|
||||
src={file.thumbnail}
|
||||
alt={file.name}
|
||||
draggable={false}
|
||||
onError={(e) => {
|
||||
// Hide broken image if blob URL was revoked
|
||||
const img = e.target as HTMLImageElement;
|
||||
img.style.display = 'none';
|
||||
}}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: '80%',
|
||||
maxHeight: '80%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: 2,
|
||||
borderRadius: 0,
|
||||
background: '#ffffff',
|
||||
border: '1px solid var(--border-default)',
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
alignSelf: 'start'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Page count badge */}
|
||||
<Badge
|
||||
size="sm"
|
||||
variant="filled"
|
||||
color="blue"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
left: 8,
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{file.pageCount} pages
|
||||
</Badge>
|
||||
|
||||
{/* Unsupported badge */}
|
||||
{!isSupported && (
|
||||
<Badge
|
||||
size="sm"
|
||||
variant="filled"
|
||||
color="orange"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: selectionMode ? 48 : 8, // Avoid overlap with checkbox
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{t("fileManager.unsupported", "Unsupported")}
|
||||
</Badge>
|
||||
{/* Pin indicator (bottom-left) */}
|
||||
{isPinned && (
|
||||
<span className={styles.pinIndicator} aria-hidden>
|
||||
<PushPinIcon fontSize="small" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* File name overlay */}
|
||||
<Text
|
||||
className={styles.pageNumber}
|
||||
size="xs"
|
||||
fw={500}
|
||||
c="white"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 5,
|
||||
left: 5,
|
||||
right: 5,
|
||||
background: 'rgba(0, 0, 0, 0.8)',
|
||||
padding: '4px 6px',
|
||||
borderRadius: 4,
|
||||
zIndex: 2,
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{file.name}
|
||||
</Text>
|
||||
|
||||
{/* Hover controls */}
|
||||
<div
|
||||
className={styles.pageHoverControls}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
background: 'rgba(0, 0, 0, 0.8)',
|
||||
padding: '8px 12px',
|
||||
borderRadius: 20,
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
zIndex: 3,
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{!toolMode && isSupported && (
|
||||
<>
|
||||
<Tooltip label="View File">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewFile(file.id);
|
||||
onSetStatus(`Opened ${file.name}`);
|
||||
}}
|
||||
>
|
||||
<VisibilityIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
</>
|
||||
)}
|
||||
|
||||
<Tooltip label="View History">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowHistory(true);
|
||||
onSetStatus(`Viewing history for ${file.name}`);
|
||||
}}
|
||||
>
|
||||
<HistoryIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
{actualFile && (
|
||||
<Tooltip label={isFilePinned(actualFile) ? "Unpin File" : "Pin File"}>
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c={isFilePinned(actualFile) ? "yellow" : "white"}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (isFilePinned(actualFile)) {
|
||||
unpinFile(actualFile);
|
||||
onSetStatus(`Unpinned ${file.name}`);
|
||||
} else {
|
||||
pinFile(actualFile);
|
||||
onSetStatus(`Pinned ${file.name}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isFilePinned(actualFile) ? (
|
||||
<PushPinIcon style={{ fontSize: 20 }} />
|
||||
) : (
|
||||
<PushPinOutlinedIcon style={{ fontSize: 20 }} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip label="Close File">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="orange"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDeleteFile(file.id);
|
||||
onSetStatus(`Closed ${file.name}`);
|
||||
}}
|
||||
>
|
||||
<CloseIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<DragIndicatorIcon
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 4,
|
||||
right: 4,
|
||||
color: 'rgba(0,0,0,0.3)',
|
||||
fontSize: 16,
|
||||
zIndex: 1
|
||||
}}
|
||||
/>
|
||||
{/* Drag handle (span wrapper so we can attach a ref reliably) */}
|
||||
<span ref={handleRef} className={styles.dragHandle} aria-hidden>
|
||||
<DragIndicatorIcon fontSize="small" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* File info */}
|
||||
<div className="w-full px-4 py-2 text-center">
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{file.name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{formatFileSize(file.size)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* History Modal */}
|
||||
<Modal
|
||||
opened={showHistory}
|
||||
onClose={() => setShowHistory(false)}
|
||||
title={`Operation History - ${file.name}`}
|
||||
size="lg"
|
||||
scrollAreaComponent={'div' as any}
|
||||
>
|
||||
<FileOperationHistory
|
||||
fileId={file.name}
|
||||
showOnlyApplied={true}
|
||||
maxHeight={500}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileThumbnail;
|
||||
export default React.memo(FileThumbnail);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
.pageContainer:hover .pageHoverControls {
|
||||
opacity: 1 !important;
|
||||
opacity: 0.95 !important;
|
||||
}
|
||||
|
||||
/* Checkbox container - prevent transform inheritance */
|
||||
@@ -62,6 +62,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
/* Action styles */
|
||||
.actionRow:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.actionDanger {
|
||||
color: var(--text-brand-accent);
|
||||
}
|
||||
|
||||
.actionsDivider {
|
||||
height: 1px;
|
||||
background: var(--border-default);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.pinIndicator {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
z-index: 1;
|
||||
color: rgba(0, 0, 0, 0.35); /* match drag handle color */
|
||||
}
|
||||
|
||||
.unsupportedPill {
|
||||
margin-left: 1.75rem;
|
||||
background: #6B7280;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 80px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,14 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
ActionIcon,
|
||||
Paper
|
||||
} from "@mantine/core";
|
||||
import UndoIcon from "@mui/icons-material/Undo";
|
||||
import RedoIcon from "@mui/icons-material/Redo";
|
||||
import ContentCutIcon from "@mui/icons-material/ContentCut";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import RotateLeftIcon from "@mui/icons-material/RotateLeft";
|
||||
import RotateRightIcon from "@mui/icons-material/RotateRight";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import InsertPageBreakIcon from "@mui/icons-material/InsertPageBreak";
|
||||
|
||||
interface PageEditorControlsProps {
|
||||
// Close/Reset functions
|
||||
@@ -27,19 +24,25 @@ interface PageEditorControlsProps {
|
||||
onRotate: (direction: 'left' | 'right') => void;
|
||||
onDelete: () => void;
|
||||
onSplit: () => void;
|
||||
onSplitAll: () => void;
|
||||
onPageBreak: () => void;
|
||||
onPageBreakAll: () => void;
|
||||
|
||||
// Export functions
|
||||
onExportSelected: () => void;
|
||||
// Export functions (moved to right rail)
|
||||
onExportAll: () => void;
|
||||
exportLoading: boolean;
|
||||
|
||||
// Selection state
|
||||
selectionMode: boolean;
|
||||
selectedPages: number[];
|
||||
selectedPageIds: string[];
|
||||
displayDocument?: { pages: { id: string; pageNumber: number }[] };
|
||||
|
||||
// Split state (for tooltip logic)
|
||||
splitPositions?: Set<number>;
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
const PageEditorControls = ({
|
||||
onClosePdf,
|
||||
onUndo,
|
||||
onRedo,
|
||||
canUndo,
|
||||
@@ -47,19 +50,62 @@ const PageEditorControls = ({
|
||||
onRotate,
|
||||
onDelete,
|
||||
onSplit,
|
||||
onExportSelected,
|
||||
onExportAll,
|
||||
exportLoading,
|
||||
selectionMode,
|
||||
selectedPages
|
||||
onPageBreak,
|
||||
selectedPageIds,
|
||||
displayDocument,
|
||||
splitPositions,
|
||||
totalPages
|
||||
}: PageEditorControlsProps) => {
|
||||
// Calculate split tooltip text using smart toggle logic
|
||||
const getSplitTooltip = () => {
|
||||
if (!splitPositions || !totalPages || selectedPageIds.length === 0) {
|
||||
return "Split Selected";
|
||||
}
|
||||
|
||||
// Convert selected pages to split positions (same logic as handleSplit)
|
||||
const selectedPageNumbers = displayDocument ? selectedPageIds.map(id => {
|
||||
const page = displayDocument.pages.find(p => p.id === id);
|
||||
return page?.pageNumber || 0;
|
||||
}).filter(num => num > 0) : [];
|
||||
const selectedSplitPositions = selectedPageNumbers.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1);
|
||||
|
||||
if (selectedSplitPositions.length === 0) {
|
||||
return "Split Selected";
|
||||
}
|
||||
|
||||
// Smart toggle logic: follow the majority, default to adding splits if equal
|
||||
const existingSplitsCount = selectedSplitPositions.filter(pos => splitPositions.has(pos)).length;
|
||||
const noSplitsCount = selectedSplitPositions.length - existingSplitsCount;
|
||||
|
||||
// Remove splits only if majority already have splits
|
||||
// If equal (50/50), default to adding splits
|
||||
const willRemoveSplits = existingSplitsCount > noSplitsCount;
|
||||
|
||||
if (willRemoveSplits) {
|
||||
return existingSplitsCount === selectedSplitPositions.length
|
||||
? "Remove All Selected Splits"
|
||||
: "Remove Selected Splits";
|
||||
} else {
|
||||
return existingSplitsCount === 0
|
||||
? "Split Selected"
|
||||
: "Complete Selected Splits";
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate page break tooltip text
|
||||
const getPageBreakTooltip = () => {
|
||||
return selectedPageIds.length > 0
|
||||
? `Insert ${selectedPageIds.length} Page Break${selectedPageIds.length > 1 ? 's' : ''}`
|
||||
: "Insert Page Breaks";
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
bottom: '20px',
|
||||
transform: 'translateX(-50%)',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 50,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -67,43 +113,37 @@ const PageEditorControls = ({
|
||||
background: 'transparent',
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
radius="xl"
|
||||
shadow="lg"
|
||||
p={16}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
borderRadius: 32,
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.12)',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
boxShadow: '0 -2px 8px rgba(0,0,0,0.04)',
|
||||
backgroundColor: 'var(--bg-toolbar)',
|
||||
border: '1px solid var(--border-default)',
|
||||
borderRadius: '16px 16px 0 0',
|
||||
pointerEvents: 'auto',
|
||||
minWidth: 400,
|
||||
justifyContent: 'center'
|
||||
minWidth: 360,
|
||||
maxWidth: 700,
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'center',
|
||||
padding: "1rem",
|
||||
paddingBottom: "1rem"
|
||||
}}
|
||||
>
|
||||
{/* Close PDF */}
|
||||
<Tooltip label="Close PDF">
|
||||
<ActionIcon
|
||||
onClick={onClosePdf}
|
||||
color="red"
|
||||
variant="light"
|
||||
size="lg"
|
||||
>
|
||||
<CloseIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<div style={{ width: 1, height: 28, backgroundColor: 'var(--mantine-color-gray-3)', margin: '0 8px' }} />
|
||||
|
||||
{/* Undo/Redo */}
|
||||
<Tooltip label="Undo">
|
||||
<ActionIcon onClick={onUndo} disabled={!canUndo} size="lg">
|
||||
<ActionIcon onClick={onUndo} disabled={!canUndo} variant="subtle" radius="md" size="lg">
|
||||
<UndoIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Redo">
|
||||
<ActionIcon onClick={onRedo} disabled={!canRedo} size="lg">
|
||||
<ActionIcon onClick={onRedo} disabled={!canRedo} variant="subtle" radius="md" size="lg">
|
||||
<RedoIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
@@ -111,79 +151,67 @@ const PageEditorControls = ({
|
||||
<div style={{ width: 1, height: 28, backgroundColor: 'var(--mantine-color-gray-3)', margin: '0 8px' }} />
|
||||
|
||||
{/* Page Operations */}
|
||||
<Tooltip label={selectionMode ? "Rotate Selected Left" : "Rotate All Left"}>
|
||||
<Tooltip label="Rotate Selected Left">
|
||||
<ActionIcon
|
||||
onClick={() => onRotate('left')}
|
||||
disabled={selectionMode && selectedPages.length === 0}
|
||||
variant={selectionMode && selectedPages.length > 0 ? "light" : "default"}
|
||||
color={selectionMode && selectedPages.length > 0 ? "blue" : undefined}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
radius="md"
|
||||
size="lg"
|
||||
>
|
||||
<RotateLeftIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={selectionMode ? "Rotate Selected Right" : "Rotate All Right"}>
|
||||
<Tooltip label="Rotate Selected Right">
|
||||
<ActionIcon
|
||||
onClick={() => onRotate('right')}
|
||||
disabled={selectionMode && selectedPages.length === 0}
|
||||
variant={selectionMode && selectedPages.length > 0 ? "light" : "default"}
|
||||
color={selectionMode && selectedPages.length > 0 ? "blue" : undefined}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
radius="md"
|
||||
size="lg"
|
||||
>
|
||||
<RotateRightIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={selectionMode ? "Delete Selected" : "Delete All"}>
|
||||
<Tooltip label="Delete Selected">
|
||||
<ActionIcon
|
||||
onClick={onDelete}
|
||||
disabled={selectionMode && selectedPages.length === 0}
|
||||
color="red"
|
||||
variant={selectionMode && selectedPages.length > 0 ? "light" : "default"}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
radius="md"
|
||||
size="lg"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={selectionMode ? "Split Selected" : "Split All"}>
|
||||
<Tooltip label={getSplitTooltip()}>
|
||||
<ActionIcon
|
||||
onClick={onSplit}
|
||||
disabled={selectionMode && selectedPages.length === 0}
|
||||
variant={selectionMode && selectedPages.length > 0 ? "light" : "default"}
|
||||
color={selectionMode && selectedPages.length > 0 ? "blue" : undefined}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
radius="md"
|
||||
size="lg"
|
||||
>
|
||||
<ContentCutIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<div style={{ width: 1, height: 28, backgroundColor: 'var(--mantine-color-gray-3)', margin: '0 8px' }} />
|
||||
|
||||
{/* Export Controls */}
|
||||
{selectionMode && selectedPages.length > 0 && (
|
||||
<Tooltip label="Export Selected">
|
||||
<ActionIcon
|
||||
onClick={onExportSelected}
|
||||
disabled={exportLoading}
|
||||
color="blue"
|
||||
variant="light"
|
||||
size="lg"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip label="Export All">
|
||||
<Tooltip label={getPageBreakTooltip()}>
|
||||
<ActionIcon
|
||||
onClick={onExportAll}
|
||||
disabled={exportLoading}
|
||||
color="green"
|
||||
variant="light"
|
||||
onClick={onPageBreak}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
radius="md"
|
||||
size="lg"
|
||||
>
|
||||
<DownloadIcon />
|
||||
<InsertPageBreakIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,140 +1,286 @@
|
||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import { Text, Checkbox, Tooltip, ActionIcon, Loader } from '@mantine/core';
|
||||
import { Text, Checkbox, Tooltip, ActionIcon } from '@mantine/core';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import RotateLeftIcon from '@mui/icons-material/RotateLeft';
|
||||
import RotateRightIcon from '@mui/icons-material/RotateRight';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import ContentCutIcon from '@mui/icons-material/ContentCut';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import { PDFPage, PDFDocument } from '../../types/pageEditor';
|
||||
import { RotatePagesCommand, DeletePagesCommand, ToggleSplitCommand } from '../../commands/pageCommands';
|
||||
import { Command } from '../../hooks/useUndoRedo';
|
||||
import { useThumbnailGeneration } from '../../hooks/useThumbnailGeneration';
|
||||
import { useFilesModalContext } from '../../contexts/FilesModalContext';
|
||||
import styles from './PageEditor.module.css';
|
||||
import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist';
|
||||
|
||||
// Ensure PDF.js worker is available
|
||||
if (!GlobalWorkerOptions.workerSrc) {
|
||||
GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||||
console.log('📸 PageThumbnail: Set PDF.js worker source to /pdf.worker.js');
|
||||
} else {
|
||||
console.log('📸 PageThumbnail: PDF.js worker source already set to', GlobalWorkerOptions.workerSrc);
|
||||
}
|
||||
|
||||
interface PageThumbnailProps {
|
||||
page: PDFPage;
|
||||
index: number;
|
||||
totalPages: number;
|
||||
originalFile?: File; // For lazy thumbnail generation
|
||||
selectedPages: number[];
|
||||
originalFile?: File;
|
||||
selectedPageIds: string[];
|
||||
selectionMode: boolean;
|
||||
draggedPage: number | null;
|
||||
dropTarget: number | 'end' | null;
|
||||
movingPage: number | null;
|
||||
isAnimating: boolean;
|
||||
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
|
||||
onDragStart: (pageNumber: number) => void;
|
||||
onDragEnd: () => void;
|
||||
onDragOver: (e: React.DragEvent) => void;
|
||||
onDragEnter: (pageNumber: number) => void;
|
||||
onDragLeave: () => void;
|
||||
onDrop: (e: React.DragEvent, pageNumber: number) => void;
|
||||
onTogglePage: (pageNumber: number) => void;
|
||||
onAnimateReorder: (pageNumber: number, targetIndex: number) => void;
|
||||
onExecuteCommand: (command: Command) => void;
|
||||
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
|
||||
onTogglePage: (pageId: string) => void;
|
||||
onAnimateReorder: () => void;
|
||||
onExecuteCommand: (command: { execute: () => void }) => void;
|
||||
onSetStatus: (status: string) => void;
|
||||
onSetMovingPage: (pageNumber: number | null) => void;
|
||||
RotatePagesCommand: typeof RotatePagesCommand;
|
||||
DeletePagesCommand: typeof DeletePagesCommand;
|
||||
ToggleSplitCommand: typeof ToggleSplitCommand;
|
||||
onSetMovingPage: (page: number | null) => void;
|
||||
onDeletePage: (pageNumber: number) => void;
|
||||
createRotateCommand: (pageIds: string[], rotation: number) => { execute: () => void };
|
||||
createDeleteCommand: (pageIds: string[]) => { execute: () => void };
|
||||
createSplitCommand: (position: number) => { execute: () => void };
|
||||
pdfDocument: PDFDocument;
|
||||
setPdfDocument: (doc: PDFDocument) => void;
|
||||
splitPositions: Set<number>;
|
||||
onInsertFiles?: (files: File[], insertAfterPage: number) => void;
|
||||
}
|
||||
|
||||
const PageThumbnail = React.memo(({
|
||||
const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
||||
page,
|
||||
index,
|
||||
totalPages,
|
||||
originalFile,
|
||||
selectedPages,
|
||||
selectedPageIds,
|
||||
selectionMode,
|
||||
draggedPage,
|
||||
dropTarget,
|
||||
movingPage,
|
||||
isAnimating,
|
||||
pageRefs,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
onDragOver,
|
||||
onDragEnter,
|
||||
onDragLeave,
|
||||
onDrop,
|
||||
onReorderPages,
|
||||
onTogglePage,
|
||||
onAnimateReorder,
|
||||
onExecuteCommand,
|
||||
onSetStatus,
|
||||
onSetMovingPage,
|
||||
RotatePagesCommand,
|
||||
DeletePagesCommand,
|
||||
ToggleSplitCommand,
|
||||
onDeletePage,
|
||||
createRotateCommand,
|
||||
createSplitCommand,
|
||||
pdfDocument,
|
||||
setPdfDocument,
|
||||
splitPositions,
|
||||
onInsertFiles,
|
||||
}: PageThumbnailProps) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||
const [mouseStartPos, setMouseStartPos] = useState<{x: number, y: number} | null>(null);
|
||||
const dragElementRef = useRef<HTMLDivElement>(null);
|
||||
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(page.thumbnail);
|
||||
const [isLoadingThumbnail, setIsLoadingThumbnail] = useState(false);
|
||||
const { getThumbnailFromCache, requestThumbnail } = useThumbnailGeneration();
|
||||
const { openFilesModal } = useFilesModalContext();
|
||||
|
||||
// Calculate document aspect ratio from first non-blank page
|
||||
const getDocumentAspectRatio = useCallback(() => {
|
||||
// Find first non-blank page with a thumbnail to get aspect ratio
|
||||
const firstRealPage = pdfDocument.pages.find(p => !p.isBlankPage && p.thumbnail);
|
||||
if (firstRealPage?.thumbnail) {
|
||||
// Try to get aspect ratio from an actual thumbnail image
|
||||
// For now, default to A4 but could be enhanced to measure image dimensions
|
||||
return '1 / 1.414'; // A4 ratio as fallback
|
||||
}
|
||||
return '1 / 1.414'; // Default A4 ratio
|
||||
}, [pdfDocument.pages]);
|
||||
|
||||
// Update thumbnail URL when page prop changes
|
||||
useEffect(() => {
|
||||
if (page.thumbnail && page.thumbnail !== thumbnailUrl) {
|
||||
console.log(`📸 PageThumbnail: Updating thumbnail URL for page ${page.pageNumber}`, page.thumbnail.substring(0, 50) + '...');
|
||||
setThumbnailUrl(page.thumbnail);
|
||||
}
|
||||
}, [page.thumbnail, page.pageNumber, page.id, thumbnailUrl]);
|
||||
}, [page.thumbnail, thumbnailUrl]);
|
||||
|
||||
// Listen for ready thumbnails from Web Workers (only if no existing thumbnail)
|
||||
// Request thumbnail if missing (on-demand, virtualized approach)
|
||||
useEffect(() => {
|
||||
if (thumbnailUrl) {
|
||||
console.log(`📸 PageThumbnail: Page ${page.pageNumber} already has thumbnail, skipping worker listener`);
|
||||
return; // Skip if we already have a thumbnail
|
||||
let isCancelled = false;
|
||||
|
||||
// If we already have a thumbnail, use it
|
||||
if (page.thumbnail) {
|
||||
setThumbnailUrl(page.thumbnail);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📸 PageThumbnail: Setting up worker listener for page ${page.pageNumber} (${page.id})`);
|
||||
// Check cache first
|
||||
const cachedThumbnail = getThumbnailFromCache(page.id);
|
||||
if (cachedThumbnail) {
|
||||
setThumbnailUrl(cachedThumbnail);
|
||||
return;
|
||||
}
|
||||
|
||||
const handleThumbnailReady = (event: CustomEvent) => {
|
||||
const { pageNumber, thumbnail, pageId } = event.detail;
|
||||
console.log(`📸 PageThumbnail: Received worker thumbnail for page ${pageNumber}, looking for page ${page.pageNumber} (${page.id})`);
|
||||
// Request thumbnail generation if we have the original file
|
||||
if (originalFile) {
|
||||
const pageNumber = page.originalPageNumber;
|
||||
|
||||
if (pageNumber === page.pageNumber && pageId === page.id) {
|
||||
console.log(`✓ PageThumbnail: Thumbnail matched for page ${page.pageNumber}, setting URL`);
|
||||
setThumbnailUrl(thumbnail);
|
||||
}
|
||||
};
|
||||
requestThumbnail(page.id, originalFile, pageNumber)
|
||||
.then(thumbnail => {
|
||||
if (!isCancelled && thumbnail) {
|
||||
setThumbnailUrl(thumbnail);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.warn(`Failed to generate thumbnail for ${page.id}:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('thumbnailReady', handleThumbnailReady as EventListener);
|
||||
return () => {
|
||||
console.log(`📸 PageThumbnail: Cleaning up worker listener for page ${page.pageNumber}`);
|
||||
window.removeEventListener('thumbnailReady', handleThumbnailReady as EventListener);
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [page.pageNumber, page.id, thumbnailUrl]);
|
||||
}, [page.id, page.thumbnail, originalFile, getThumbnailFromCache, requestThumbnail]);
|
||||
|
||||
|
||||
// Register this component with pageRefs for animations
|
||||
const pageElementRef = useCallback((element: HTMLDivElement | null) => {
|
||||
if (element) {
|
||||
pageRefs.current.set(page.id, element);
|
||||
dragElementRef.current = element;
|
||||
|
||||
const dragCleanup = draggable({
|
||||
element,
|
||||
getInitialData: () => ({
|
||||
pageNumber: page.pageNumber,
|
||||
pageId: page.id,
|
||||
selectedPageIds: [page.id]
|
||||
}),
|
||||
onDragStart: () => {
|
||||
setIsDragging(true);
|
||||
},
|
||||
onDrop: ({ location }) => {
|
||||
setIsDragging(false);
|
||||
|
||||
if (location.current.dropTargets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dropTarget = location.current.dropTargets[0];
|
||||
const targetData = dropTarget.data;
|
||||
|
||||
if (targetData.type === 'page') {
|
||||
const targetPageNumber = targetData.pageNumber as number;
|
||||
const targetIndex = pdfDocument.pages.findIndex(p => p.pageNumber === targetPageNumber);
|
||||
if (targetIndex !== -1) {
|
||||
onReorderPages(page.pageNumber, targetIndex, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
element.style.cursor = 'grab';
|
||||
|
||||
const dropCleanup = dropTargetForElements({
|
||||
element,
|
||||
getData: () => ({
|
||||
type: 'page',
|
||||
pageNumber: page.pageNumber
|
||||
}),
|
||||
onDrop: (_) => {}
|
||||
});
|
||||
|
||||
(element as any).__dragCleanup = () => {
|
||||
dragCleanup();
|
||||
dropCleanup();
|
||||
};
|
||||
} else {
|
||||
pageRefs.current.delete(page.id);
|
||||
if (dragElementRef.current && (dragElementRef.current as any).__dragCleanup) {
|
||||
(dragElementRef.current as any).__dragCleanup();
|
||||
}
|
||||
}
|
||||
}, [page.id, pageRefs]);
|
||||
}, [page.id, page.pageNumber, pageRefs, selectionMode, selectedPageIds, pdfDocument.pages, onReorderPages]);
|
||||
|
||||
// DOM command handlers
|
||||
const handleRotateLeft = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
// Use the command system for undo/redo support
|
||||
const command = createRotateCommand([page.id], -90);
|
||||
onExecuteCommand(command);
|
||||
onSetStatus(`Rotated page ${page.pageNumber} left`);
|
||||
}, [page.id, page.pageNumber, onExecuteCommand, onSetStatus, createRotateCommand]);
|
||||
|
||||
const handleRotateRight = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
// Use the command system for undo/redo support
|
||||
const command = createRotateCommand([page.id], 90);
|
||||
onExecuteCommand(command);
|
||||
onSetStatus(`Rotated page ${page.pageNumber} right`);
|
||||
}, [page.id, page.pageNumber, onExecuteCommand, onSetStatus, createRotateCommand]);
|
||||
|
||||
const handleDelete = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onDeletePage(page.pageNumber);
|
||||
onSetStatus(`Deleted page ${page.pageNumber}`);
|
||||
}, [page.pageNumber, onDeletePage, onSetStatus]);
|
||||
|
||||
const handleSplit = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Create a command to toggle split at this position
|
||||
const command = createSplitCommand(index);
|
||||
onExecuteCommand(command);
|
||||
|
||||
const hasSplit = splitPositions.has(index);
|
||||
const action = hasSplit ? 'removed' : 'added';
|
||||
onSetStatus(`Split marker ${action} after position ${index + 1}`);
|
||||
}, [index, splitPositions, onExecuteCommand, onSetStatus, createSplitCommand]);
|
||||
|
||||
const handleInsertFileAfter = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (onInsertFiles) {
|
||||
// Open file manager modal with custom handler for page insertion
|
||||
openFilesModal({
|
||||
insertAfterPage: page.pageNumber,
|
||||
customHandler: (files: File[], insertAfterPage?: number) => {
|
||||
if (insertAfterPage !== undefined) {
|
||||
onInsertFiles(files, insertAfterPage);
|
||||
}
|
||||
}
|
||||
});
|
||||
onSetStatus(`Select files to insert after page ${page.pageNumber}`);
|
||||
} else {
|
||||
// Fallback to normal file handling
|
||||
openFilesModal({ insertAfterPage: page.pageNumber });
|
||||
onSetStatus(`Select files to insert after page ${page.pageNumber}`);
|
||||
}
|
||||
}, [openFilesModal, page.pageNumber, onSetStatus, onInsertFiles]);
|
||||
|
||||
// Handle click vs drag differentiation
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
setIsMouseDown(true);
|
||||
setMouseStartPos({ x: e.clientX, y: e.clientY });
|
||||
}, []);
|
||||
|
||||
const handleMouseUp = useCallback((e: React.MouseEvent) => {
|
||||
if (!isMouseDown || !mouseStartPos) {
|
||||
setIsMouseDown(false);
|
||||
setMouseStartPos(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate distance moved
|
||||
const deltaX = Math.abs(e.clientX - mouseStartPos.x);
|
||||
const deltaY = Math.abs(e.clientY - mouseStartPos.y);
|
||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
// If mouse moved less than 5 pixels, consider it a click (not a drag)
|
||||
if (distance < 5 && !isDragging) {
|
||||
onTogglePage(page.id);
|
||||
}
|
||||
|
||||
setIsMouseDown(false);
|
||||
setMouseStartPos(null);
|
||||
}, [isMouseDown, mouseStartPos, isDragging, page.id, onTogglePage]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsMouseDown(false);
|
||||
setMouseStartPos(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={pageElementRef}
|
||||
data-page-id={page.id}
|
||||
data-page-number={page.pageNumber}
|
||||
className={`
|
||||
${styles.pageContainer}
|
||||
!rounded-lg
|
||||
cursor-grab
|
||||
${selectionMode ? 'cursor-pointer' : 'cursor-grab'}
|
||||
select-none
|
||||
w-[20rem]
|
||||
h-[20rem]
|
||||
@@ -147,27 +293,18 @@ const PageThumbnail = React.memo(({
|
||||
${selectionMode
|
||||
? 'bg-white hover:bg-gray-50'
|
||||
: 'bg-white hover:bg-gray-50'}
|
||||
${draggedPage === page.pageNumber ? 'opacity-50 scale-95' : ''}
|
||||
${isDragging ? 'opacity-50 scale-95' : ''}
|
||||
${movingPage === page.pageNumber ? 'page-moving' : ''}
|
||||
`}
|
||||
style={{
|
||||
transform: (() => {
|
||||
if (!isAnimating && draggedPage && page.pageNumber !== draggedPage && dropTarget === page.pageNumber) {
|
||||
return 'translateX(20px)';
|
||||
}
|
||||
return 'translateX(0)';
|
||||
})(),
|
||||
transition: isAnimating ? 'none' : 'transform 0.2s ease-in-out'
|
||||
}}
|
||||
draggable
|
||||
onDragStart={() => onDragStart(page.pageNumber)}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={() => onDragEnter(page.pageNumber)}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={(e) => onDrop(e, page.pageNumber)}
|
||||
draggable={false}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{selectionMode && (
|
||||
{
|
||||
<div
|
||||
className={styles.checkboxContainer}
|
||||
style={{
|
||||
@@ -175,36 +312,34 @@ const PageThumbnail = React.memo(({
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 10,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
border: '1px solid #ccc',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '4px',
|
||||
padding: '4px',
|
||||
padding: '2px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
pointerEvents: 'auto',
|
||||
cursor: 'pointer'
|
||||
pointerEvents: 'auto'
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
onTogglePage(page.id);
|
||||
}}
|
||||
onMouseUp={(e) => e.stopPropagation()}
|
||||
onDragStart={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
console.log('📸 Checkbox clicked for page', page.pageNumber);
|
||||
e.stopPropagation();
|
||||
onTogglePage(page.pageNumber);
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={Array.isArray(selectedPages) ? selectedPages.includes(page.pageNumber) : false}
|
||||
checked={Array.isArray(selectedPageIds) ? selectedPageIds.includes(page.id) : false}
|
||||
onChange={() => {
|
||||
// onChange is handled by the parent div click
|
||||
// Selection is handled by container mouseDown
|
||||
}}
|
||||
size="sm"
|
||||
style={{ pointerEvents: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
|
||||
<div className="page-container w-[90%] h-[90%]">
|
||||
<div className="page-container w-[90%] h-[90%]" draggable={false}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
@@ -218,10 +353,27 @@ const PageThumbnail = React.memo(({
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{thumbnailUrl ? (
|
||||
{page.isBlankPage ? (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<div style={{
|
||||
width: '70%',
|
||||
aspectRatio: getDocumentAspectRatio(),
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: 2
|
||||
}}></div>
|
||||
</div>
|
||||
) : thumbnailUrl ? (
|
||||
<img
|
||||
src={thumbnailUrl}
|
||||
alt={`Page ${page.pageNumber}`}
|
||||
draggable={false}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@@ -231,11 +383,6 @@ const PageThumbnail = React.memo(({
|
||||
transition: 'transform 0.3s ease-in-out'
|
||||
}}
|
||||
/>
|
||||
) : isLoadingThumbnail ? (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Loader size="sm" />
|
||||
<Text size="xs" c="dimmed" mt={4}>Loading...</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text size="lg" c="dimmed">📄</Text>
|
||||
@@ -248,12 +395,12 @@ const PageThumbnail = React.memo(({
|
||||
className={styles.pageNumber}
|
||||
size="sm"
|
||||
fw={500}
|
||||
c="white"
|
||||
style={{
|
||||
color: 'var(--mantine-color-white)', // Use theme token for consistency
|
||||
position: 'absolute',
|
||||
top: 5,
|
||||
left: 5,
|
||||
background: 'rgba(162, 201, 255, 0.8)',
|
||||
background: page.isBlankPage ? 'rgba(255, 165, 0, 0.8)' : 'rgba(162, 201, 255, 0.8)',
|
||||
padding: '6px 8px',
|
||||
borderRadius: 8,
|
||||
zIndex: 2,
|
||||
@@ -271,7 +418,8 @@ const PageThumbnail = React.memo(({
|
||||
bottom: 8,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
background: 'rgba(0, 0, 0, 0.8)',
|
||||
backgroundColor: 'var(--bg-toolbar)',
|
||||
border: '1px solid var(--border-default)',
|
||||
padding: '6px 12px',
|
||||
borderRadius: 20,
|
||||
opacity: 0,
|
||||
@@ -282,19 +430,23 @@ const PageThumbnail = React.memo(({
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onMouseUp={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Tooltip label="Move Left">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
disabled={index === 0}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (index > 0 && !movingPage && !isAnimating) {
|
||||
onSetMovingPage(page.pageNumber);
|
||||
onAnimateReorder(page.pageNumber, index - 1);
|
||||
setTimeout(() => onSetMovingPage(null), 500);
|
||||
// Actually move the page left (swap with previous page)
|
||||
onReorderPages(page.pageNumber, index - 1);
|
||||
setTimeout(() => onSetMovingPage(null), 650);
|
||||
onSetStatus(`Moved page ${page.pageNumber} left`);
|
||||
}
|
||||
}}
|
||||
@@ -307,14 +459,15 @@ const PageThumbnail = React.memo(({
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
disabled={index === totalPages - 1}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (index < totalPages - 1 && !movingPage && !isAnimating) {
|
||||
onSetMovingPage(page.pageNumber);
|
||||
onAnimateReorder(page.pageNumber, index + 1);
|
||||
setTimeout(() => onSetMovingPage(null), 500);
|
||||
// Actually move the page right (swap with next page)
|
||||
onReorderPages(page.pageNumber, index + 1);
|
||||
setTimeout(() => onSetMovingPage(null), 650);
|
||||
onSetStatus(`Moved page ${page.pageNumber} right`);
|
||||
}
|
||||
}}
|
||||
@@ -327,18 +480,8 @@ const PageThumbnail = React.memo(({
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const command = new RotatePagesCommand(
|
||||
pdfDocument,
|
||||
setPdfDocument,
|
||||
[page.id],
|
||||
-90
|
||||
);
|
||||
onExecuteCommand(command);
|
||||
onSetStatus(`Rotated page ${page.pageNumber} left`);
|
||||
}}
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
onClick={handleRotateLeft}
|
||||
>
|
||||
<RotateLeftIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
@@ -348,18 +491,8 @@ const PageThumbnail = React.memo(({
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const command = new RotatePagesCommand(
|
||||
pdfDocument,
|
||||
setPdfDocument,
|
||||
[page.id],
|
||||
90
|
||||
);
|
||||
onExecuteCommand(command);
|
||||
onSetStatus(`Rotated page ${page.pageNumber} right`);
|
||||
}}
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
onClick={handleRotateRight}
|
||||
>
|
||||
<RotateRightIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
@@ -370,71 +503,41 @@ const PageThumbnail = React.memo(({
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="red"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const command = new DeletePagesCommand(
|
||||
pdfDocument,
|
||||
setPdfDocument,
|
||||
[page.id]
|
||||
);
|
||||
onExecuteCommand(command);
|
||||
onSetStatus(`Deleted page ${page.pageNumber}`);
|
||||
}}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<DeleteIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
{index > 0 && (
|
||||
<Tooltip label="Split Here">
|
||||
{index < totalPages - 1 && (
|
||||
<Tooltip label="Split After">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const command = new ToggleSplitCommand(
|
||||
pdfDocument,
|
||||
setPdfDocument,
|
||||
[page.id]
|
||||
);
|
||||
onExecuteCommand(command);
|
||||
onSetStatus(`Split marker toggled for page ${page.pageNumber}`);
|
||||
}}
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
onClick={handleSplit}
|
||||
>
|
||||
<ContentCutIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip label="Insert File After">
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||
onClick={handleInsertFileAfter}
|
||||
>
|
||||
<AddIcon style={{ fontSize: 20 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<DragIndicatorIcon
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 4,
|
||||
right: 4,
|
||||
color: 'rgba(0,0,0,0.3)',
|
||||
fontSize: 16,
|
||||
zIndex: 1
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}, (prevProps, nextProps) => {
|
||||
// Only re-render if essential props change
|
||||
return (
|
||||
prevProps.page.id === nextProps.page.id &&
|
||||
prevProps.page.pageNumber === nextProps.page.pageNumber &&
|
||||
prevProps.page.rotation === nextProps.page.rotation &&
|
||||
prevProps.page.thumbnail === nextProps.page.thumbnail &&
|
||||
prevProps.selectedPages === nextProps.selectedPages && // Compare array reference - will re-render when selection changes
|
||||
prevProps.selectionMode === nextProps.selectionMode &&
|
||||
prevProps.draggedPage === nextProps.draggedPage &&
|
||||
prevProps.dropTarget === nextProps.dropTarget &&
|
||||
prevProps.movingPage === nextProps.movingPage &&
|
||||
prevProps.isAnimating === nextProps.isAnimating
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default PageThumbnail;
|
||||
|
||||
@@ -0,0 +1,880 @@
|
||||
import { FileId } from '../../../types/file';
|
||||
import { PDFDocument, PDFPage } from '../../../types/pageEditor';
|
||||
|
||||
// V1-style DOM-first command system (replaces the old React state commands)
|
||||
export abstract class DOMCommand {
|
||||
abstract execute(): void;
|
||||
abstract undo(): void;
|
||||
abstract description: string;
|
||||
}
|
||||
|
||||
export class RotatePageCommand extends DOMCommand {
|
||||
constructor(
|
||||
private pageId: string,
|
||||
private degrees: number
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Only update DOM for immediate visual feedback
|
||||
const pageElement = document.querySelector(`[data-page-id="${this.pageId}"]`);
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img) {
|
||||
// Extract current rotation from transform property to match the animated CSS
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
const newRotation = currentRotation + this.degrees;
|
||||
img.style.transform = `rotate(${newRotation}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Only update DOM
|
||||
const pageElement = document.querySelector(`[data-page-id="${this.pageId}"]`);
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img) {
|
||||
// Extract current rotation from transform property
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
const previousRotation = currentRotation - this.degrees;
|
||||
img.style.transform = `rotate(${previousRotation}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Rotate page ${this.degrees > 0 ? 'right' : 'left'}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class DeletePagesCommand extends DOMCommand {
|
||||
private originalDocument: PDFDocument | null = null;
|
||||
private originalSplitPositions: Set<number> = new Set();
|
||||
private originalSelectedPages: number[] = [];
|
||||
private hasExecuted: boolean = false;
|
||||
private pageIdsToDelete: string[] = [];
|
||||
|
||||
constructor(
|
||||
private pagesToDelete: number[],
|
||||
private getCurrentDocument: () => PDFDocument | null,
|
||||
private setDocument: (doc: PDFDocument) => void,
|
||||
private setSelectedPages: (pages: number[]) => void,
|
||||
private getSplitPositions: () => Set<number>,
|
||||
private setSplitPositions: (positions: Set<number>) => void,
|
||||
private getSelectedPages: () => number[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const currentDoc = this.getCurrentDocument();
|
||||
if (!currentDoc || this.pagesToDelete.length === 0) return;
|
||||
|
||||
// Store complete original state for undo (only on first execution)
|
||||
if (!this.hasExecuted) {
|
||||
this.originalDocument = {
|
||||
...currentDoc,
|
||||
pages: currentDoc.pages.map(page => ({...page})) // Deep copy pages
|
||||
};
|
||||
this.originalSplitPositions = new Set(this.getSplitPositions());
|
||||
this.originalSelectedPages = [...this.getSelectedPages()];
|
||||
|
||||
// Convert page numbers to page IDs for stable identification
|
||||
this.pageIdsToDelete = this.pagesToDelete.map(pageNum => {
|
||||
const page = currentDoc.pages.find(p => p.pageNumber === pageNum);
|
||||
return page?.id || '';
|
||||
}).filter(id => id);
|
||||
|
||||
this.hasExecuted = true;
|
||||
}
|
||||
|
||||
// Filter out deleted pages by ID (stable across undo/redo)
|
||||
const remainingPages = currentDoc.pages.filter(page =>
|
||||
!this.pageIdsToDelete.includes(page.id)
|
||||
);
|
||||
|
||||
if (remainingPages.length === 0) return; // Safety check
|
||||
|
||||
// Renumber remaining pages
|
||||
remainingPages.forEach((page, index) => {
|
||||
page.pageNumber = index + 1;
|
||||
});
|
||||
|
||||
// Update document
|
||||
const updatedDocument: PDFDocument = {
|
||||
...currentDoc,
|
||||
pages: remainingPages,
|
||||
totalPages: remainingPages.length,
|
||||
};
|
||||
|
||||
// Adjust split positions
|
||||
const currentSplitPositions = this.getSplitPositions();
|
||||
const newPositions = new Set<number>();
|
||||
currentSplitPositions.forEach(pos => {
|
||||
if (pos < remainingPages.length - 1) {
|
||||
newPositions.add(pos);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply changes
|
||||
this.setDocument(updatedDocument);
|
||||
this.setSelectedPages([]);
|
||||
this.setSplitPositions(newPositions);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
if (!this.originalDocument) return;
|
||||
|
||||
// Simply restore the complete original document state
|
||||
this.setDocument(this.originalDocument);
|
||||
this.setSplitPositions(this.originalSplitPositions);
|
||||
this.setSelectedPages(this.originalSelectedPages);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Delete ${this.pagesToDelete.length} page(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ReorderPagesCommand extends DOMCommand {
|
||||
private originalPages: PDFPage[] = [];
|
||||
|
||||
constructor(
|
||||
private sourcePageNumber: number,
|
||||
private targetIndex: number,
|
||||
private selectedPages: number[] | undefined,
|
||||
private getCurrentDocument: () => PDFDocument | null,
|
||||
private setDocument: (doc: PDFDocument) => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const currentDoc = this.getCurrentDocument();
|
||||
if (!currentDoc) return;
|
||||
|
||||
// Store original state for undo
|
||||
this.originalPages = currentDoc.pages.map(page => ({...page}));
|
||||
|
||||
// Perform the reorder
|
||||
const sourceIndex = currentDoc.pages.findIndex(p => p.pageNumber === this.sourcePageNumber);
|
||||
if (sourceIndex === -1) return;
|
||||
|
||||
const newPages = [...currentDoc.pages];
|
||||
|
||||
if (this.selectedPages && this.selectedPages.length > 1 && this.selectedPages.includes(this.sourcePageNumber)) {
|
||||
// Multi-page reorder
|
||||
const selectedPageObjects = this.selectedPages
|
||||
.map(pageNum => currentDoc.pages.find(p => p.pageNumber === pageNum))
|
||||
.filter(page => page !== undefined) as PDFPage[];
|
||||
|
||||
const remainingPages = newPages.filter(page => !this.selectedPages!.includes(page.pageNumber));
|
||||
remainingPages.splice(this.targetIndex, 0, ...selectedPageObjects);
|
||||
|
||||
remainingPages.forEach((page, index) => {
|
||||
page.pageNumber = index + 1;
|
||||
});
|
||||
|
||||
newPages.splice(0, newPages.length, ...remainingPages);
|
||||
} else {
|
||||
// Single page reorder
|
||||
const [movedPage] = newPages.splice(sourceIndex, 1);
|
||||
newPages.splice(this.targetIndex, 0, movedPage);
|
||||
|
||||
newPages.forEach((page, index) => {
|
||||
page.pageNumber = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
const reorderedDocument: PDFDocument = {
|
||||
...currentDoc,
|
||||
pages: newPages,
|
||||
totalPages: newPages.length,
|
||||
};
|
||||
|
||||
this.setDocument(reorderedDocument);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
const currentDoc = this.getCurrentDocument();
|
||||
if (!currentDoc || this.originalPages.length === 0) return;
|
||||
|
||||
// Restore original page order
|
||||
const restoredDocument: PDFDocument = {
|
||||
...currentDoc,
|
||||
pages: this.originalPages,
|
||||
totalPages: this.originalPages.length,
|
||||
};
|
||||
|
||||
this.setDocument(restoredDocument);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Reorder page(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
export class SplitCommand extends DOMCommand {
|
||||
private originalSplitPositions: Set<number> = new Set();
|
||||
|
||||
constructor(
|
||||
private position: number,
|
||||
private getSplitPositions: () => Set<number>,
|
||||
private setSplitPositions: (positions: Set<number>) => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Store original state for undo
|
||||
this.originalSplitPositions = new Set(this.getSplitPositions());
|
||||
|
||||
// Toggle the split position
|
||||
const currentPositions = this.getSplitPositions();
|
||||
const newPositions = new Set(currentPositions);
|
||||
|
||||
if (newPositions.has(this.position)) {
|
||||
newPositions.delete(this.position);
|
||||
} else {
|
||||
newPositions.add(this.position);
|
||||
}
|
||||
|
||||
this.setSplitPositions(newPositions);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Restore original split positions
|
||||
this.setSplitPositions(this.originalSplitPositions);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const currentPositions = this.getSplitPositions();
|
||||
const willAdd = !currentPositions.has(this.position);
|
||||
return `${willAdd ? 'Add' : 'Remove'} split at position ${this.position + 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class BulkRotateCommand extends DOMCommand {
|
||||
private originalRotations: Map<string, number> = new Map();
|
||||
|
||||
constructor(
|
||||
private pageIds: string[],
|
||||
private degrees: number
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
this.pageIds.forEach(pageId => {
|
||||
const pageElement = document.querySelector(`[data-page-id="${pageId}"]`);
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img) {
|
||||
// Store original rotation for undo (only on first execution)
|
||||
if (!this.originalRotations.has(pageId)) {
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
this.originalRotations.set(pageId, currentRotation);
|
||||
}
|
||||
|
||||
// Apply rotation using transform to trigger CSS animation
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
const newRotation = currentRotation + this.degrees;
|
||||
img.style.transform = `rotate(${newRotation}deg)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
this.pageIds.forEach(pageId => {
|
||||
const pageElement = document.querySelector(`[data-page-id="${pageId}"]`);
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img && this.originalRotations.has(pageId)) {
|
||||
img.style.transform = `rotate(${this.originalRotations.get(pageId)}deg)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Rotate ${this.pageIds.length} page(s) ${this.degrees > 0 ? 'right' : 'left'}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class BulkSplitCommand extends DOMCommand {
|
||||
private originalSplitPositions: Set<number> = new Set();
|
||||
|
||||
constructor(
|
||||
private positions: number[],
|
||||
private getSplitPositions: () => Set<number>,
|
||||
private setSplitPositions: (positions: Set<number>) => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Store original state for undo (only on first execution)
|
||||
if (this.originalSplitPositions.size === 0) {
|
||||
this.originalSplitPositions = new Set(this.getSplitPositions());
|
||||
}
|
||||
|
||||
// Toggle each position
|
||||
const currentPositions = new Set(this.getSplitPositions());
|
||||
this.positions.forEach(position => {
|
||||
if (currentPositions.has(position)) {
|
||||
currentPositions.delete(position);
|
||||
} else {
|
||||
currentPositions.add(position);
|
||||
}
|
||||
});
|
||||
|
||||
this.setSplitPositions(currentPositions);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Restore original split positions
|
||||
this.setSplitPositions(this.originalSplitPositions);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Toggle ${this.positions.length} split position(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
export class SplitAllCommand extends DOMCommand {
|
||||
private originalSplitPositions: Set<number> = new Set();
|
||||
private allPossibleSplits: Set<number> = new Set();
|
||||
|
||||
constructor(
|
||||
private totalPages: number,
|
||||
private getSplitPositions: () => Set<number>,
|
||||
private setSplitPositions: (positions: Set<number>) => void
|
||||
) {
|
||||
super();
|
||||
// Calculate all possible split positions (between pages, not after last page)
|
||||
for (let i = 0; i < this.totalPages - 1; i++) {
|
||||
this.allPossibleSplits.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Store original state for undo
|
||||
this.originalSplitPositions = new Set(this.getSplitPositions());
|
||||
|
||||
// Check if all splits are already active
|
||||
const currentSplits = this.getSplitPositions();
|
||||
const hasAllSplits = Array.from(this.allPossibleSplits).every(pos => currentSplits.has(pos));
|
||||
|
||||
if (hasAllSplits) {
|
||||
// Remove all splits
|
||||
this.setSplitPositions(new Set());
|
||||
} else {
|
||||
// Add all splits
|
||||
this.setSplitPositions(this.allPossibleSplits);
|
||||
}
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Restore original split positions
|
||||
this.setSplitPositions(this.originalSplitPositions);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const currentSplits = this.getSplitPositions();
|
||||
const hasAllSplits = Array.from(this.allPossibleSplits).every(pos => currentSplits.has(pos));
|
||||
return hasAllSplits ? 'Remove all splits' : 'Split all pages';
|
||||
}
|
||||
}
|
||||
|
||||
export class PageBreakCommand extends DOMCommand {
|
||||
private insertedPages: PDFPage[] = [];
|
||||
private originalDocument: PDFDocument | null = null;
|
||||
|
||||
constructor(
|
||||
private selectedPageNumbers: number[],
|
||||
private getCurrentDocument: () => PDFDocument | null,
|
||||
private setDocument: (doc: PDFDocument) => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const currentDoc = this.getCurrentDocument();
|
||||
if (!currentDoc || this.selectedPageNumbers.length === 0) return;
|
||||
|
||||
// Store original state for undo
|
||||
this.originalDocument = {
|
||||
...currentDoc,
|
||||
pages: currentDoc.pages.map(page => ({...page}))
|
||||
};
|
||||
|
||||
// Create new pages array with blank pages inserted
|
||||
const newPages: PDFPage[] = [];
|
||||
this.insertedPages = [];
|
||||
let pageNumberCounter = 1;
|
||||
|
||||
currentDoc.pages.forEach((page, index) => {
|
||||
// Add the current page
|
||||
const updatedPage = { ...page, pageNumber: pageNumberCounter++ };
|
||||
newPages.push(updatedPage);
|
||||
|
||||
// If this page is selected for page break insertion, add a blank page after it
|
||||
if (this.selectedPageNumbers.includes(page.pageNumber)) {
|
||||
const blankPage: PDFPage = {
|
||||
id: `blank-${Date.now()}-${index}`,
|
||||
pageNumber: pageNumberCounter++,
|
||||
originalPageNumber: -1, // Mark as blank page
|
||||
thumbnail: null,
|
||||
rotation: 0,
|
||||
selected: false,
|
||||
splitAfter: false,
|
||||
isBlankPage: true // Custom flag for blank pages
|
||||
};
|
||||
newPages.push(blankPage);
|
||||
this.insertedPages.push(blankPage);
|
||||
}
|
||||
});
|
||||
|
||||
// Update document
|
||||
const updatedDocument: PDFDocument = {
|
||||
...currentDoc,
|
||||
pages: newPages,
|
||||
totalPages: newPages.length,
|
||||
};
|
||||
|
||||
this.setDocument(updatedDocument);
|
||||
|
||||
// No need to maintain selection - page IDs remain stable, so selection persists automatically
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
if (!this.originalDocument) return;
|
||||
this.setDocument(this.originalDocument);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Insert ${this.selectedPageNumbers.length} page break(s)`;
|
||||
}
|
||||
}
|
||||
|
||||
export class BulkPageBreakCommand extends DOMCommand {
|
||||
private insertedPages: PDFPage[] = [];
|
||||
private originalDocument: PDFDocument | null = null;
|
||||
private originalSelectedPages: number[] = [];
|
||||
|
||||
constructor(
|
||||
private getCurrentDocument: () => PDFDocument | null,
|
||||
private setDocument: (doc: PDFDocument) => void,
|
||||
private setSelectedPages: (pages: number[]) => void,
|
||||
private getSelectedPages: () => number[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const currentDoc = this.getCurrentDocument();
|
||||
if (!currentDoc) return;
|
||||
|
||||
// Store original selection to restore later
|
||||
this.originalSelectedPages = this.getSelectedPages();
|
||||
|
||||
// Store original state for undo
|
||||
this.originalDocument = {
|
||||
...currentDoc,
|
||||
pages: currentDoc.pages.map(page => ({...page}))
|
||||
};
|
||||
|
||||
// Create new pages array with blank pages inserted after each page (except the last)
|
||||
const newPages: PDFPage[] = [];
|
||||
this.insertedPages = [];
|
||||
let pageNumberCounter = 1;
|
||||
|
||||
currentDoc.pages.forEach((page, index) => {
|
||||
// Add the current page
|
||||
const updatedPage = { ...page, pageNumber: pageNumberCounter++ };
|
||||
newPages.push(updatedPage);
|
||||
|
||||
// Add blank page after each page except the last one
|
||||
if (index < currentDoc.pages.length - 1) {
|
||||
const blankPage: PDFPage = {
|
||||
id: `blank-${Date.now()}-${index}`,
|
||||
pageNumber: pageNumberCounter++,
|
||||
originalPageNumber: -1,
|
||||
thumbnail: null,
|
||||
rotation: 0,
|
||||
selected: false,
|
||||
splitAfter: false,
|
||||
isBlankPage: true
|
||||
};
|
||||
newPages.push(blankPage);
|
||||
this.insertedPages.push(blankPage);
|
||||
}
|
||||
});
|
||||
|
||||
// Update document
|
||||
const updatedDocument: PDFDocument = {
|
||||
...currentDoc,
|
||||
pages: newPages,
|
||||
totalPages: newPages.length,
|
||||
};
|
||||
|
||||
this.setDocument(updatedDocument);
|
||||
|
||||
// Maintain existing selection by mapping original selected pages to their new positions
|
||||
const updatedSelection: number[] = [];
|
||||
this.originalSelectedPages.forEach(originalPageNum => {
|
||||
// Find the original page by matching the page ID from the original document
|
||||
const originalPage = this.originalDocument?.pages[originalPageNum - 1];
|
||||
if (originalPage) {
|
||||
const foundPage = newPages.find(page => page.id === originalPage.id && !page.isBlankPage);
|
||||
if (foundPage) {
|
||||
updatedSelection.push(foundPage.pageNumber);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.setSelectedPages(updatedSelection);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
if (!this.originalDocument) return;
|
||||
this.setDocument(this.originalDocument);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Insert page breaks after all pages`;
|
||||
}
|
||||
}
|
||||
|
||||
export class InsertFilesCommand extends DOMCommand {
|
||||
private insertedPages: PDFPage[] = [];
|
||||
private originalDocument: PDFDocument | null = null;
|
||||
private fileDataMap = new Map<FileId, ArrayBuffer>(); // Store file data for thumbnail generation
|
||||
private originalProcessedFile: any = null; // Store original ProcessedFile for undo
|
||||
private insertedFileMap = new Map<FileId, File>(); // Store inserted files for export
|
||||
|
||||
constructor(
|
||||
private files: File[],
|
||||
private insertAfterPageNumber: number,
|
||||
private getCurrentDocument: () => PDFDocument | null,
|
||||
private setDocument: (doc: PDFDocument) => void,
|
||||
private setSelectedPages: (pages: number[]) => void,
|
||||
private getSelectedPages: () => number[],
|
||||
private updateFileContext?: (updatedDocument: PDFDocument, insertedFiles?: Map<FileId, File>) => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
const currentDoc = this.getCurrentDocument();
|
||||
if (!currentDoc || this.files.length === 0) return;
|
||||
|
||||
// Store original state for undo
|
||||
this.originalDocument = {
|
||||
...currentDoc,
|
||||
pages: currentDoc.pages.map(page => ({...page}))
|
||||
};
|
||||
|
||||
try {
|
||||
// Process each file to extract pages and wait for all to complete
|
||||
const allNewPages: PDFPage[] = [];
|
||||
|
||||
// Process all files and wait for their completion
|
||||
const baseTimestamp = Date.now();
|
||||
const extractionPromises = this.files.map(async (file, index) => {
|
||||
const fileId = `inserted-${file.name}-${baseTimestamp + index}` as FileId;
|
||||
// Store inserted file for export
|
||||
this.insertedFileMap.set(fileId, file);
|
||||
// Use base timestamp + index to ensure unique but predictable file IDs
|
||||
return await this.extractPagesFromFile(file, baseTimestamp + index);
|
||||
});
|
||||
|
||||
const extractedPageArrays = await Promise.all(extractionPromises);
|
||||
|
||||
// Flatten all extracted pages
|
||||
for (const pages of extractedPageArrays) {
|
||||
allNewPages.push(...pages);
|
||||
}
|
||||
|
||||
if (allNewPages.length === 0) return;
|
||||
|
||||
// Find insertion point (after the specified page)
|
||||
const insertIndex = this.insertAfterPageNumber; // Insert after page N means insert at index N
|
||||
|
||||
// Create new pages array with inserted pages
|
||||
const newPages: PDFPage[] = [];
|
||||
let pageNumberCounter = 1;
|
||||
|
||||
// Add pages before insertion point
|
||||
for (let i = 0; i < insertIndex && i < currentDoc.pages.length; i++) {
|
||||
const page = { ...currentDoc.pages[i], pageNumber: pageNumberCounter++ };
|
||||
newPages.push(page);
|
||||
}
|
||||
|
||||
// Add inserted pages
|
||||
for (const newPage of allNewPages) {
|
||||
const insertedPage: PDFPage = {
|
||||
...newPage,
|
||||
pageNumber: pageNumberCounter++,
|
||||
selected: false,
|
||||
splitAfter: false
|
||||
};
|
||||
newPages.push(insertedPage);
|
||||
this.insertedPages.push(insertedPage);
|
||||
}
|
||||
|
||||
// Add remaining pages after insertion point
|
||||
for (let i = insertIndex; i < currentDoc.pages.length; i++) {
|
||||
const page = { ...currentDoc.pages[i], pageNumber: pageNumberCounter++ };
|
||||
newPages.push(page);
|
||||
}
|
||||
|
||||
// Update document
|
||||
const updatedDocument: PDFDocument = {
|
||||
...currentDoc,
|
||||
pages: newPages,
|
||||
totalPages: newPages.length,
|
||||
};
|
||||
|
||||
this.setDocument(updatedDocument);
|
||||
|
||||
// Update FileContext with the new document structure and inserted files
|
||||
if (this.updateFileContext) {
|
||||
this.updateFileContext(updatedDocument, this.insertedFileMap);
|
||||
}
|
||||
|
||||
// Generate thumbnails for inserted pages (all files should be read by now)
|
||||
this.generateThumbnailsForInsertedPages(updatedDocument);
|
||||
|
||||
// Maintain existing selection by mapping original selected pages to their new positions
|
||||
const originalSelection = this.getSelectedPages();
|
||||
const updatedSelection: number[] = [];
|
||||
|
||||
originalSelection.forEach(originalPageNum => {
|
||||
if (originalPageNum <= this.insertAfterPageNumber) {
|
||||
// Pages before insertion point keep same number
|
||||
updatedSelection.push(originalPageNum);
|
||||
} else {
|
||||
// Pages after insertion point are shifted by number of inserted pages
|
||||
updatedSelection.push(originalPageNum + allNewPages.length);
|
||||
}
|
||||
});
|
||||
|
||||
this.setSelectedPages(updatedSelection);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to insert files:', error);
|
||||
// Revert to original state if error occurs
|
||||
if (this.originalDocument) {
|
||||
this.setDocument(this.originalDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async generateThumbnailsForInsertedPages(updatedDocument: PDFDocument): Promise<void> {
|
||||
try {
|
||||
const { thumbnailGenerationService } = await import('../../../services/thumbnailGenerationService');
|
||||
|
||||
// Group pages by file ID to generate thumbnails efficiently
|
||||
const pagesByFileId = new Map<FileId, PDFPage[]>();
|
||||
|
||||
for (const page of this.insertedPages) {
|
||||
const fileId = page.id.substring(0, page.id.lastIndexOf('-page-')) as FileId /* FIX ME: This looks wrong - like we've thrown away info too early and need to recreate it */;
|
||||
if (!pagesByFileId.has(fileId)) {
|
||||
pagesByFileId.set(fileId, []);
|
||||
}
|
||||
pagesByFileId.get(fileId)!.push(page);
|
||||
}
|
||||
|
||||
// Generate thumbnails for each file
|
||||
for (const [fileId, pages] of pagesByFileId) {
|
||||
const arrayBuffer = this.fileDataMap.get(fileId);
|
||||
|
||||
console.log('Generating thumbnails for file:', fileId);
|
||||
console.log('Pages:', pages.length);
|
||||
console.log('ArrayBuffer size:', arrayBuffer?.byteLength || 'undefined');
|
||||
|
||||
if (arrayBuffer && arrayBuffer.byteLength > 0) {
|
||||
// Extract page numbers for all pages from this file
|
||||
const pageNumbers = pages.map(page => {
|
||||
const pageNumMatch = page.id.match(/-page-(\d+)$/);
|
||||
return pageNumMatch ? parseInt(pageNumMatch[1]) : 1;
|
||||
});
|
||||
|
||||
console.log('Generating thumbnails for page numbers:', pageNumbers);
|
||||
|
||||
// Generate thumbnails for all pages from this file at once
|
||||
const results = await thumbnailGenerationService.generateThumbnails(
|
||||
fileId,
|
||||
arrayBuffer,
|
||||
pageNumbers,
|
||||
{ scale: 0.2, quality: 0.8 }
|
||||
);
|
||||
|
||||
console.log('Thumbnail generation results:', results.length, 'thumbnails generated');
|
||||
|
||||
// Update pages with generated thumbnails
|
||||
for (let i = 0; i < results.length && i < pages.length; i++) {
|
||||
const result = results[i];
|
||||
const page = pages[i];
|
||||
|
||||
if (result.success) {
|
||||
const pageIndex = updatedDocument.pages.findIndex(p => p.id === page.id);
|
||||
if (pageIndex >= 0) {
|
||||
updatedDocument.pages[pageIndex].thumbnail = result.thumbnail;
|
||||
console.log('Updated thumbnail for page:', page.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger re-render by updating the document
|
||||
this.setDocument({ ...updatedDocument });
|
||||
} else {
|
||||
console.error('No valid ArrayBuffer found for file ID:', fileId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to generate thumbnails for inserted pages:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async extractPagesFromFile(file: File, baseTimestamp: number): Promise<PDFPage[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
const arrayBuffer = event.target?.result as ArrayBuffer;
|
||||
console.log('File reader onload - arrayBuffer size:', arrayBuffer?.byteLength || 'undefined');
|
||||
|
||||
if (!arrayBuffer) {
|
||||
reject(new Error('Failed to read file'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clone the ArrayBuffer before passing to PDF.js (it might consume it)
|
||||
const clonedArrayBuffer = arrayBuffer.slice(0);
|
||||
|
||||
// Use PDF.js via the worker manager to extract pages
|
||||
const { pdfWorkerManager } = await import('../../../services/pdfWorkerManager');
|
||||
const pdf = await pdfWorkerManager.createDocument(clonedArrayBuffer);
|
||||
|
||||
const pageCount = pdf.numPages;
|
||||
const pages: PDFPage[] = [];
|
||||
const fileId = `inserted-${file.name}-${baseTimestamp}` as FileId;
|
||||
|
||||
console.log('Original ArrayBuffer size:', arrayBuffer.byteLength);
|
||||
console.log('Storing ArrayBuffer for fileId:', fileId, 'size:', arrayBuffer.byteLength);
|
||||
|
||||
// Store the original ArrayBuffer for thumbnail generation
|
||||
this.fileDataMap.set(fileId, arrayBuffer);
|
||||
|
||||
console.log('After storing - fileDataMap size:', this.fileDataMap.size);
|
||||
console.log('Stored value size:', this.fileDataMap.get(fileId)?.byteLength || 'undefined');
|
||||
|
||||
for (let i = 1; i <= pageCount; i++) {
|
||||
const pageId = `${fileId}-page-${i}`;
|
||||
pages.push({
|
||||
id: pageId,
|
||||
pageNumber: i, // Will be renumbered in execute()
|
||||
originalPageNumber: i,
|
||||
thumbnail: null, // Will be generated after insertion
|
||||
rotation: 0,
|
||||
selected: false,
|
||||
splitAfter: false,
|
||||
isBlankPage: false
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up PDF document
|
||||
pdfWorkerManager.destroyDocument(pdf);
|
||||
|
||||
resolve(pages);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
reader.onerror = () => reject(new Error('Failed to read file'));
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
if (!this.originalDocument) return;
|
||||
this.setDocument(this.originalDocument);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Insert ${this.files.length} file(s) after page ${this.insertAfterPageNumber}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple undo manager for DOM commands
|
||||
export class UndoManager {
|
||||
private undoStack: DOMCommand[] = [];
|
||||
private redoStack: DOMCommand[] = [];
|
||||
private onStateChange?: () => void;
|
||||
|
||||
setStateChangeCallback(callback: () => void): void {
|
||||
this.onStateChange = callback;
|
||||
}
|
||||
|
||||
executeCommand(command: DOMCommand): void {
|
||||
command.execute();
|
||||
this.undoStack.push(command);
|
||||
this.redoStack = [];
|
||||
this.onStateChange?.();
|
||||
}
|
||||
|
||||
// For async commands that need to be executed manually
|
||||
addToUndoStack(command: DOMCommand): void {
|
||||
this.undoStack.push(command);
|
||||
this.redoStack = [];
|
||||
this.onStateChange?.();
|
||||
}
|
||||
|
||||
undo(): boolean {
|
||||
const command = this.undoStack.pop();
|
||||
if (command) {
|
||||
command.undo();
|
||||
this.redoStack.push(command);
|
||||
this.onStateChange?.();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
redo(): boolean {
|
||||
const command = this.redoStack.pop();
|
||||
if (command) {
|
||||
command.execute();
|
||||
this.undoStack.push(command);
|
||||
this.onStateChange?.();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canUndo(): boolean {
|
||||
return this.undoStack.length > 0;
|
||||
}
|
||||
|
||||
canRedo(): boolean {
|
||||
return this.redoStack.length > 0;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.undoStack = [];
|
||||
this.redoStack = [];
|
||||
this.onStateChange?.();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Shared constants for PageEditor grid layout
|
||||
export const GRID_CONSTANTS = {
|
||||
ITEM_WIDTH: '20rem', // page width
|
||||
ITEM_HEIGHT: '21.5rem', // 20rem + 1.5rem gap
|
||||
ITEM_GAP: '1.5rem', // gap between items
|
||||
OVERSCAN_SMALL: 4, // Overscan for normal documents
|
||||
OVERSCAN_LARGE: 8, // Overscan for large documents (>1000 pages)
|
||||
} as const;
|
||||
@@ -0,0 +1,174 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useFileState } from '../../../contexts/FileContext';
|
||||
import { PDFDocument, PDFPage } from '../../../types/pageEditor';
|
||||
import { FileId } from '../../../types/file';
|
||||
|
||||
export interface PageDocumentHook {
|
||||
document: PDFDocument | null;
|
||||
isVeryLargeDocument: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing PDF document state and metadata in PageEditor
|
||||
* Handles document merging, large document detection, and loading states
|
||||
*/
|
||||
export function usePageDocument(): PageDocumentHook {
|
||||
const { state, selectors } = useFileState();
|
||||
|
||||
// Prefer IDs + selectors to avoid array identity churn
|
||||
const activeFileIds = state.files.ids;
|
||||
const primaryFileId = activeFileIds[0] ?? null;
|
||||
|
||||
// Stable signature for effects (prevents loops)
|
||||
const filesSignature = selectors.getFilesSignature();
|
||||
|
||||
// UI state
|
||||
const globalProcessing = state.ui.isProcessing;
|
||||
|
||||
// Get primary file record outside useMemo to track processedFile changes
|
||||
const primaryStirlingFileStub = primaryFileId ? selectors.getStirlingFileStub(primaryFileId) : null;
|
||||
const processedFilePages = primaryStirlingFileStub?.processedFile?.pages;
|
||||
const processedFileTotalPages = primaryStirlingFileStub?.processedFile?.totalPages;
|
||||
|
||||
// Compute merged document with stable signature (prevents infinite loops)
|
||||
const mergedPdfDocument = useMemo((): PDFDocument | null => {
|
||||
if (activeFileIds.length === 0) return null;
|
||||
|
||||
const primaryFile = primaryFileId ? selectors.getFile(primaryFileId) : null;
|
||||
|
||||
// If we have file IDs but no file record, something is wrong - return null to show loading
|
||||
if (!primaryStirlingFileStub) {
|
||||
console.log('🎬 PageEditor: No primary file record found, showing loading');
|
||||
return null;
|
||||
}
|
||||
|
||||
const name =
|
||||
activeFileIds.length === 1
|
||||
? (primaryStirlingFileStub.name ?? 'document.pdf')
|
||||
: activeFileIds
|
||||
.map(id => (selectors.getStirlingFileStub(id)?.name ?? 'file').replace(/\.pdf$/i, ''))
|
||||
.join(' + ');
|
||||
|
||||
// Build page insertion map from files with insertion positions
|
||||
const insertionMap = new Map<string, FileId[]>(); // insertAfterPageId -> fileIds
|
||||
const originalFileIds: FileId[] = [];
|
||||
|
||||
activeFileIds.forEach(fileId => {
|
||||
const record = selectors.getStirlingFileStub(fileId);
|
||||
if (record?.insertAfterPageId !== undefined) {
|
||||
if (!insertionMap.has(record.insertAfterPageId)) {
|
||||
insertionMap.set(record.insertAfterPageId, []);
|
||||
}
|
||||
insertionMap.get(record.insertAfterPageId)!.push(fileId);
|
||||
} else {
|
||||
originalFileIds.push(fileId);
|
||||
}
|
||||
});
|
||||
|
||||
// Build pages by interleaving original pages with insertions
|
||||
let pages: PDFPage[] = [];
|
||||
|
||||
// Helper function to create pages from a file
|
||||
const createPagesFromFile = (fileId: FileId, startPageNumber: number): PDFPage[] => {
|
||||
const stirlingFileStub = selectors.getStirlingFileStub(fileId);
|
||||
if (!stirlingFileStub) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const processedFile = stirlingFileStub.processedFile;
|
||||
let filePages: PDFPage[] = [];
|
||||
|
||||
if (processedFile?.pages && processedFile.pages.length > 0) {
|
||||
// Use fully processed pages with thumbnails
|
||||
filePages = processedFile.pages.map((page, pageIndex) => ({
|
||||
id: `${fileId}-${page.pageNumber}`,
|
||||
pageNumber: startPageNumber + pageIndex,
|
||||
thumbnail: page.thumbnail || null,
|
||||
rotation: page.rotation || 0,
|
||||
selected: false,
|
||||
splitAfter: page.splitAfter || false,
|
||||
originalPageNumber: page.originalPageNumber || page.pageNumber || pageIndex + 1,
|
||||
originalFileId: fileId,
|
||||
}));
|
||||
} else if (processedFile?.totalPages) {
|
||||
// Fallback: create pages without thumbnails but with correct count
|
||||
filePages = Array.from({ length: processedFile.totalPages }, (_, pageIndex) => ({
|
||||
id: `${fileId}-${pageIndex + 1}`,
|
||||
pageNumber: startPageNumber + pageIndex,
|
||||
originalPageNumber: pageIndex + 1,
|
||||
originalFileId: fileId,
|
||||
rotation: 0,
|
||||
thumbnail: null,
|
||||
selected: false,
|
||||
splitAfter: false,
|
||||
}));
|
||||
}
|
||||
|
||||
return filePages;
|
||||
};
|
||||
|
||||
// Collect all pages from original files (without renumbering yet)
|
||||
const originalFilePages: PDFPage[] = [];
|
||||
originalFileIds.forEach(fileId => {
|
||||
const filePages = createPagesFromFile(fileId, 1); // Temporary numbering
|
||||
originalFilePages.push(...filePages);
|
||||
});
|
||||
|
||||
// Start with all original pages numbered sequentially
|
||||
pages = originalFilePages.map((page, index) => ({
|
||||
...page,
|
||||
pageNumber: index + 1
|
||||
}));
|
||||
|
||||
// Process each insertion by finding the page ID and inserting after it
|
||||
for (const [insertAfterPageId, fileIds] of insertionMap.entries()) {
|
||||
const targetPageIndex = pages.findIndex(p => p.id === insertAfterPageId);
|
||||
|
||||
if (targetPageIndex === -1) continue;
|
||||
|
||||
// Collect all pages to insert
|
||||
const allNewPages: PDFPage[] = [];
|
||||
fileIds.forEach(fileId => {
|
||||
const insertedPages = createPagesFromFile(fileId, 1);
|
||||
allNewPages.push(...insertedPages);
|
||||
});
|
||||
|
||||
// Insert all new pages after the target page
|
||||
pages.splice(targetPageIndex + 1, 0, ...allNewPages);
|
||||
|
||||
// Renumber all pages after insertion
|
||||
pages.forEach((page, index) => {
|
||||
page.pageNumber = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
if (pages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mergedDoc: PDFDocument = {
|
||||
id: activeFileIds.join('-'),
|
||||
name,
|
||||
file: primaryFile!,
|
||||
pages,
|
||||
totalPages: pages.length,
|
||||
};
|
||||
|
||||
return mergedDoc;
|
||||
}, [activeFileIds, primaryFileId, primaryStirlingFileStub, processedFilePages, processedFileTotalPages, selectors, filesSignature]);
|
||||
|
||||
// Large document detection for smart loading
|
||||
const isVeryLargeDocument = useMemo(() => {
|
||||
return mergedPdfDocument ? mergedPdfDocument.totalPages > 2000 : false;
|
||||
}, [mergedPdfDocument?.totalPages]);
|
||||
|
||||
// Loading state
|
||||
const isLoading = globalProcessing && !mergedPdfDocument;
|
||||
|
||||
return {
|
||||
document: mergedPdfDocument,
|
||||
isVeryLargeDocument,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export interface PageEditorState {
|
||||
// Selection state
|
||||
selectionMode: boolean;
|
||||
selectedPageIds: string[];
|
||||
|
||||
// Animation state
|
||||
movingPage: number | null;
|
||||
isAnimating: boolean;
|
||||
|
||||
// Split state
|
||||
splitPositions: Set<number>;
|
||||
|
||||
// Export state
|
||||
exportLoading: boolean;
|
||||
|
||||
// Actions
|
||||
setSelectionMode: (mode: boolean) => void;
|
||||
setSelectedPageIds: (pages: string[]) => void;
|
||||
setMovingPage: (pageNumber: number | null) => void;
|
||||
setIsAnimating: (animating: boolean) => void;
|
||||
setSplitPositions: (positions: Set<number>) => void;
|
||||
setExportLoading: (loading: boolean) => void;
|
||||
|
||||
// Helper functions
|
||||
togglePage: (pageId: string) => void;
|
||||
toggleSelectAll: (allPageIds: string[]) => void;
|
||||
animateReorder: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing PageEditor UI state
|
||||
* Handles selection, animation, splits, and export states
|
||||
*/
|
||||
export function usePageEditorState(): PageEditorState {
|
||||
// Selection state
|
||||
const [selectionMode, setSelectionMode] = useState(false);
|
||||
const [selectedPageIds, setSelectedPageIds] = useState<string[]>([]);
|
||||
|
||||
// Animation state
|
||||
const [movingPage, setMovingPage] = useState<number | null>(null);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
|
||||
// Split state - position-based split tracking (replaces page-based splitAfter)
|
||||
const [splitPositions, setSplitPositions] = useState<Set<number>>(new Set());
|
||||
|
||||
// Export state
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
|
||||
// Helper functions
|
||||
const togglePage = useCallback((pageId: string) => {
|
||||
setSelectedPageIds(prev =>
|
||||
prev.includes(pageId)
|
||||
? prev.filter(id => id !== pageId)
|
||||
: [...prev, pageId]
|
||||
);
|
||||
}, []);
|
||||
|
||||
const toggleSelectAll = useCallback((allPageIds: string[]) => {
|
||||
if (!allPageIds.length) return;
|
||||
|
||||
setSelectedPageIds(prev =>
|
||||
prev.length === allPageIds.length ? [] : allPageIds
|
||||
);
|
||||
}, []);
|
||||
|
||||
const animateReorder = useCallback(() => {
|
||||
setIsAnimating(true);
|
||||
setTimeout(() => setIsAnimating(false), 500);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// State
|
||||
selectionMode,
|
||||
selectedPageIds,
|
||||
movingPage,
|
||||
isAnimating,
|
||||
splitPositions,
|
||||
exportLoading,
|
||||
|
||||
// Setters
|
||||
setSelectionMode,
|
||||
setSelectedPageIds,
|
||||
setMovingPage,
|
||||
setIsAnimating,
|
||||
setSplitPositions,
|
||||
setExportLoading,
|
||||
|
||||
// Helpers
|
||||
togglePage,
|
||||
toggleSelectAll,
|
||||
animateReorder,
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import AppsIcon from '@mui/icons-material/AppsRounded';
|
||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||
import { useSidebarNavigation } from '../../hooks/useSidebarNavigation';
|
||||
import { handleUnlessSpecialClick } from '../../utils/clickHandlers';
|
||||
|
||||
interface AllToolsNavButtonProps {
|
||||
activeButton: string;
|
||||
@@ -13,6 +15,7 @@ interface AllToolsNavButtonProps {
|
||||
const AllToolsNavButton: React.FC<AllToolsNavButtonProps> = ({ activeButton, setActiveButton }) => {
|
||||
const { t } = useTranslation();
|
||||
const { handleReaderToggle, handleBackToTools, selectedToolKey, leftPanelView } = useToolWorkflow();
|
||||
const { getHomeNavigation } = useSidebarNavigation();
|
||||
|
||||
const handleClick = () => {
|
||||
setActiveButton('tools');
|
||||
@@ -24,25 +27,34 @@ const AllToolsNavButton: React.FC<AllToolsNavButtonProps> = ({ activeButton, set
|
||||
// Do not highlight All Tools when a specific tool is open (indicator is shown)
|
||||
const isActive = activeButton === 'tools' && !selectedToolKey && leftPanelView === 'toolPicker';
|
||||
|
||||
const navProps = getHomeNavigation();
|
||||
|
||||
const handleNavClick = (e: React.MouseEvent) => {
|
||||
handleUnlessSpecialClick(e, handleClick);
|
||||
};
|
||||
|
||||
const iconNode = (
|
||||
<span className="iconContainer">
|
||||
<AppsIcon sx={{ fontSize: '1.5rem' }} />
|
||||
<AppsIcon sx={{ fontSize: '2rem' }} />
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
<Tooltip content={t("quickAccess.allTools", "All Tools")} position="right" arrow containerStyle={{ marginTop: "-1rem" }} maxWidth={200}>
|
||||
<div className="flex flex-col items-center gap-1 mt-4 mb-2">
|
||||
<ActionIcon
|
||||
component="a"
|
||||
href={navProps.href}
|
||||
onClick={handleNavClick}
|
||||
size={'lg'}
|
||||
variant="subtle"
|
||||
onClick={handleClick}
|
||||
aria-label={t("quickAccess.allTools", "All Tools")}
|
||||
style={{
|
||||
backgroundColor: isActive ? 'var(--icon-tools-bg)' : 'var(--icon-inactive-bg)',
|
||||
color: isActive ? 'var(--icon-tools-color)' : 'var(--icon-inactive-color)',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
className={isActive ? 'activeIconScale' : ''}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import ButtonSelector from './ButtonSelector';
|
||||
|
||||
// Wrapper component to provide Mantine context
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider>{children}</MantineProvider>
|
||||
);
|
||||
|
||||
describe('ButtonSelector', () => {
|
||||
const mockOnChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should render all options as buttons', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
label="Test Label"
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test Label')).toBeInTheDocument();
|
||||
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should highlight selected button with filled variant', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
label="Selection Label"
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const selectedButton = screen.getByRole('button', { name: 'Option 1' });
|
||||
const unselectedButton = screen.getByRole('button', { name: 'Option 2' });
|
||||
|
||||
// Check data-variant attribute for filled/outline
|
||||
expect(selectedButton).toHaveAttribute('data-variant', 'filled');
|
||||
expect(unselectedButton).toHaveAttribute('data-variant', 'outline');
|
||||
expect(screen.getByText('Selection Label')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call onChange when button is clicked', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Option 2' }));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('option2');
|
||||
});
|
||||
|
||||
test('should handle undefined value (no selection)', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value={undefined}
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Both buttons should be outlined when no value is selected
|
||||
const button1 = screen.getByRole('button', { name: 'Option 1' });
|
||||
const button2 = screen.getByRole('button', { name: 'Option 2' });
|
||||
|
||||
expect(button1).toHaveAttribute('data-variant', 'outline');
|
||||
expect(button2).toHaveAttribute('data-variant', 'outline');
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
description: 'disable buttons when disabled prop is true',
|
||||
options: [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
],
|
||||
globalDisabled: true,
|
||||
expectedStates: [true, true],
|
||||
},
|
||||
{
|
||||
description: 'disable individual options when option.disabled is true',
|
||||
options: [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2', disabled: true },
|
||||
],
|
||||
globalDisabled: false,
|
||||
expectedStates: [false, true],
|
||||
},
|
||||
])('should $description', ({ options, globalDisabled, expectedStates }) => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
disabled={globalDisabled}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
options.forEach((option, index) => {
|
||||
const button = screen.getByRole('button', { name: option.label });
|
||||
expect(button).toHaveProperty('disabled', expectedStates[index]);
|
||||
});
|
||||
});
|
||||
|
||||
test('should not call onChange when disabled button is clicked', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2', disabled: true },
|
||||
];
|
||||
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Option 2' }));
|
||||
|
||||
expect(mockOnChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not apply fullWidth styling when fullWidth is false', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
fullWidth={false}
|
||||
label="Layout Label"
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Option 1' });
|
||||
expect(button).not.toHaveStyle({ flex: '1' });
|
||||
expect(screen.getByText('Layout Label')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render label element when not provided', () => {
|
||||
const options = [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
onChange={mockOnChange}
|
||||
options={options}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Should render buttons
|
||||
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
||||
|
||||
// Stack should only contain the Group (buttons), no Text element for label
|
||||
const stackElement = container.querySelector('[class*="mantine-Stack-root"]');
|
||||
expect(stackElement?.children).toHaveLength(1); // Only the Group, no label Text
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Button, Group, Stack, Text } from "@mantine/core";
|
||||
|
||||
export interface ButtonOption<T> {
|
||||
value: T;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface ButtonSelectorProps<T> {
|
||||
value: T | undefined;
|
||||
onChange: (value: T) => void;
|
||||
options: ButtonOption<T>[];
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
const ButtonSelector = <T extends string>({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
label = undefined,
|
||||
disabled = false,
|
||||
fullWidth = true,
|
||||
}: ButtonSelectorProps<T>) => {
|
||||
return (
|
||||
<Stack gap='var(--mantine-spacing-sm)'>
|
||||
{/* Label (if it exists) */}
|
||||
{label && <Text style={{
|
||||
fontSize: "var(--mantine-font-size-sm)",
|
||||
lineHeight: "var(--mantine-line-height-sm)",
|
||||
fontWeight: "var(--font-weight-medium)",
|
||||
}}>{label}</Text>}
|
||||
|
||||
{/* Buttons */}
|
||||
<Group gap='4px'>
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
key={option.value}
|
||||
variant={value === option.value ? 'filled' : 'outline'}
|
||||
color={value === option.value ? 'var(--color-primary-500)' : 'var(--text-muted)'}
|
||||
onClick={() => onChange(option.value)}
|
||||
disabled={disabled || option.disabled}
|
||||
style={{
|
||||
flex: fullWidth ? 1 : undefined,
|
||||
height: 'auto',
|
||||
minHeight: '2.5rem',
|
||||
fontSize: 'var(--mantine-font-size-sm)'
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ButtonSelector;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Stack, Card, Text, Flex } from '@mantine/core';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface CardOption<T = string> {
|
||||
value: T;
|
||||
prefixKey: string;
|
||||
nameKey: string;
|
||||
tooltipKey?: string;
|
||||
tooltipContent?: any[];
|
||||
}
|
||||
|
||||
export interface CardSelectorProps<T, K extends CardOption<T>> {
|
||||
options: K[];
|
||||
onSelect: (value: T) => void;
|
||||
disabled?: boolean;
|
||||
getTooltipContent?: (option: K) => any[];
|
||||
}
|
||||
|
||||
const CardSelector = <T, K extends CardOption<T>>({
|
||||
options,
|
||||
onSelect,
|
||||
disabled = false,
|
||||
getTooltipContent
|
||||
}: CardSelectorProps<T, K>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleOptionClick = (value: T) => {
|
||||
if (!disabled) {
|
||||
onSelect(value);
|
||||
}
|
||||
};
|
||||
|
||||
const getTooltips = (option: K) => {
|
||||
if (getTooltipContent) {
|
||||
return getTooltipContent(option);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
{options.map((option) => (
|
||||
<Tooltip
|
||||
key={option.value as string}
|
||||
sidebarTooltip
|
||||
tips={getTooltips(option)}
|
||||
>
|
||||
<Card
|
||||
radius="md"
|
||||
w="100%"
|
||||
h={'2.8rem'}
|
||||
style={{
|
||||
cursor: disabled ? 'default' : 'pointer',
|
||||
backgroundColor: 'var(--mantine-color-gray-2)',
|
||||
borderColor: 'var(--mantine-color-gray-3)',
|
||||
opacity: disabled ? 0.6 : 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!disabled) {
|
||||
e.currentTarget.style.backgroundColor = 'var(--mantine-color-gray-3)';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
e.currentTarget.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!disabled) {
|
||||
e.currentTarget.style.backgroundColor = 'var(--mantine-color-gray-2)';
|
||||
e.currentTarget.style.transform = 'translateY(0px)';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}
|
||||
}}
|
||||
onClick={() => handleOptionClick(option.value)}
|
||||
>
|
||||
<Flex align={'center'} pl="sm" w="100%">
|
||||
<Text size="sm" c="dimmed" ta="center" fw={350}>
|
||||
{t(option.prefixKey, "Prefix")}
|
||||
</Text>
|
||||
<Text
|
||||
fw={600}
|
||||
size="sm"
|
||||
c={undefined}
|
||||
ta="center"
|
||||
style={{ marginLeft: '0.25rem' }}
|
||||
>
|
||||
{t(option.nameKey, "Option Name")}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardSelector;
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { Text, Button, Stack } from '@mantine/core';
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: React.ReactNode;
|
||||
fallback?: React.ComponentType<{error?: Error; retry: () => void}>;
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||
}
|
||||
|
||||
retry = () => {
|
||||
this.setState({ hasError: false, error: undefined });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) {
|
||||
const Fallback = this.props.fallback;
|
||||
return <Fallback error={this.state.error} retry={this.retry} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack align="center" justify="center" style={{ minHeight: '200px', padding: '2rem' }}>
|
||||
<Text size="lg" fw={500} c="red">Something went wrong</Text>
|
||||
{process.env.NODE_ENV === 'development' && this.state.error && (
|
||||
<Text size="sm" c="dimmed" style={{ textAlign: 'center', fontFamily: 'monospace' }}>
|
||||
{this.state.error.message}
|
||||
</Text>
|
||||
)}
|
||||
<Button onClick={this.retry} variant="light">
|
||||
Try Again
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import StorageIcon from "@mui/icons-material/Storage";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
|
||||
import { FileWithUrl } from "../../types/file";
|
||||
import { StirlingFileStub } from "../../types/fileContext";
|
||||
import { getFileSize, getFileDate } from "../../utils/fileUtils";
|
||||
import { useIndexedDBThumbnail } from "../../hooks/useIndexedDBThumbnail";
|
||||
|
||||
interface FileCardProps {
|
||||
file: FileWithUrl;
|
||||
file: File;
|
||||
fileStub?: StirlingFileStub;
|
||||
onRemove: () => void;
|
||||
onDoubleClick?: () => void;
|
||||
onView?: () => void;
|
||||
@@ -21,9 +22,11 @@ interface FileCardProps {
|
||||
isSupported?: boolean; // Whether the file format is supported by the current tool
|
||||
}
|
||||
|
||||
const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, onSelect, isSupported = true }: FileCardProps) => {
|
||||
const FileCard = ({ file, fileStub, onRemove, onDoubleClick, onView, onEdit, isSelected, onSelect, isSupported = true }: FileCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { thumbnail: thumb, isGenerating } = useIndexedDBThumbnail(file);
|
||||
// Use record thumbnail if available, otherwise fall back to IndexedDB lookup
|
||||
const { thumbnail: indexedDBThumb, isGenerating } = useIndexedDBThumbnail(fileStub);
|
||||
const thumb = fileStub?.thumbnailUrl || indexedDBThumb;
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -173,7 +176,7 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
||||
<Badge color="blue" variant="light" size="sm">
|
||||
{getFileDate(file)}
|
||||
</Badge>
|
||||
{file.storedInIndexedDB && (
|
||||
{fileStub?.id && (
|
||||
<Badge
|
||||
color="green"
|
||||
variant="light"
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Flex, Group, Text, Button, TextInput, Select, Badge } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import { Box, Flex, Group, Text, Button, TextInput, Select } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import SortIcon from "@mui/icons-material/Sort";
|
||||
import FileCard from "./FileCard";
|
||||
import { FileWithUrl } from "../../types/file";
|
||||
import { StirlingFileStub } from "../../types/fileContext";
|
||||
import { FileId } from "../../types/file";
|
||||
|
||||
interface FileGridProps {
|
||||
files: FileWithUrl[];
|
||||
files: Array<{ file: File; record?: StirlingFileStub }>;
|
||||
onRemove?: (index: number) => void;
|
||||
onDoubleClick?: (file: FileWithUrl) => void;
|
||||
onView?: (file: FileWithUrl) => void;
|
||||
onEdit?: (file: FileWithUrl) => void;
|
||||
onSelect?: (fileId: string) => void;
|
||||
selectedFiles?: string[];
|
||||
onDoubleClick?: (item: { file: File; record?: StirlingFileStub }) => void;
|
||||
onView?: (item: { file: File; record?: StirlingFileStub }) => void;
|
||||
onEdit?: (item: { file: File; record?: StirlingFileStub }) => void;
|
||||
onSelect?: (fileId: FileId) => void;
|
||||
selectedFiles?: FileId[];
|
||||
showSearch?: boolean;
|
||||
showSort?: boolean;
|
||||
maxDisplay?: number; // If set, shows only this many files with "Show All" option
|
||||
@@ -46,19 +47,19 @@ const FileGrid = ({
|
||||
const [sortBy, setSortBy] = useState<SortOption>('date');
|
||||
|
||||
// Filter files based on search term
|
||||
const filteredFiles = files.filter(file =>
|
||||
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const filteredFiles = files.filter(item =>
|
||||
item.file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
// Sort files
|
||||
const sortedFiles = [...filteredFiles].sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'date':
|
||||
return (b.lastModified || 0) - (a.lastModified || 0);
|
||||
return (b.file.lastModified || 0) - (a.file.lastModified || 0);
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name);
|
||||
return a.file.name.localeCompare(b.file.name);
|
||||
case 'size':
|
||||
return (b.size || 0) - (a.size || 0);
|
||||
return (b.file.size || 0) - (a.file.size || 0);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -119,21 +120,30 @@ const FileGrid = ({
|
||||
direction="row"
|
||||
wrap="wrap"
|
||||
gap="md"
|
||||
h="30rem"
|
||||
h="30rem"
|
||||
style={{ overflowY: "auto", width: "100%" }}
|
||||
>
|
||||
{displayFiles.map((file, idx) => {
|
||||
const fileId = file.id || file.name;
|
||||
const originalIdx = files.findIndex(f => (f.id || f.name) === fileId);
|
||||
const supported = isFileSupported ? isFileSupported(file.name) : true;
|
||||
{displayFiles
|
||||
.filter(item => {
|
||||
if (!item.record?.id) {
|
||||
console.error('FileGrid: File missing StirlingFileStub with proper ID:', item.file.name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((item, idx) => {
|
||||
const fileId = item.record!.id; // Safe to assert after filter
|
||||
const originalIdx = files.findIndex(f => f.record?.id === fileId);
|
||||
const supported = isFileSupported ? isFileSupported(item.file.name) : true;
|
||||
return (
|
||||
<FileCard
|
||||
key={fileId + idx}
|
||||
file={file}
|
||||
file={item.file}
|
||||
fileStub={item.record}
|
||||
onRemove={onRemove ? () => onRemove(originalIdx) : () => {}}
|
||||
onDoubleClick={onDoubleClick && supported ? () => onDoubleClick(file) : undefined}
|
||||
onView={onView && supported ? () => onView(file) : undefined}
|
||||
onEdit={onEdit && supported ? () => onEdit(file) : undefined}
|
||||
onDoubleClick={onDoubleClick && supported ? () => onDoubleClick(item) : undefined}
|
||||
onView={onView && supported ? () => onView(item) : undefined}
|
||||
onEdit={onEdit && supported ? () => onEdit(item) : undefined}
|
||||
isSelected={selectedFiles.includes(fileId)}
|
||||
onSelect={onSelect && supported ? () => onSelect(fileId) : undefined}
|
||||
isSupported={supported}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Text,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Checkbox,
|
||||
ScrollArea,
|
||||
import {
|
||||
Modal,
|
||||
Text,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Checkbox,
|
||||
ScrollArea,
|
||||
Box,
|
||||
Image,
|
||||
Badge,
|
||||
@@ -15,11 +15,12 @@ import {
|
||||
} from '@mantine/core';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FileId } from '../../types/file';
|
||||
|
||||
interface FilePickerModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
storedFiles: any[]; // Files from storage (FileWithUrl format)
|
||||
storedFiles: any[]; // Files from storage (various formats supported)
|
||||
onSelectFiles: (selectedFiles: File[]) => void;
|
||||
}
|
||||
|
||||
@@ -30,7 +31,7 @@ const FilePickerModal = ({
|
||||
onSelectFiles,
|
||||
}: FilePickerModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFileIds, setSelectedFileIds] = useState<string[]>([]);
|
||||
const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]);
|
||||
|
||||
// Reset selection when modal opens
|
||||
useEffect(() => {
|
||||
@@ -39,16 +40,16 @@ const FilePickerModal = ({
|
||||
}
|
||||
}, [opened]);
|
||||
|
||||
const toggleFileSelection = (fileId: string) => {
|
||||
const toggleFileSelection = (fileId: FileId) => {
|
||||
setSelectedFileIds(prev => {
|
||||
return prev.includes(fileId)
|
||||
return prev.includes(fileId)
|
||||
? prev.filter(id => id !== fileId)
|
||||
: [...prev, fileId];
|
||||
});
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
setSelectedFileIds(storedFiles.map(f => f.id || f.name));
|
||||
setSelectedFileIds(storedFiles.map(f => f.id).filter(Boolean));
|
||||
};
|
||||
|
||||
const selectNone = () => {
|
||||
@@ -56,10 +57,10 @@ const FilePickerModal = ({
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const selectedFiles = storedFiles.filter(f =>
|
||||
selectedFileIds.includes(f.id || f.name)
|
||||
const selectedFiles = storedFiles.filter(f =>
|
||||
selectedFileIds.includes(f.id)
|
||||
);
|
||||
|
||||
|
||||
// Convert stored files to File objects
|
||||
const convertedFiles = await Promise.all(
|
||||
selectedFiles.map(async (fileItem) => {
|
||||
@@ -68,12 +69,12 @@ const FilePickerModal = ({
|
||||
if (fileItem instanceof File) {
|
||||
return fileItem;
|
||||
}
|
||||
|
||||
|
||||
// If it has a file property, use that
|
||||
if (fileItem.file && fileItem.file instanceof File) {
|
||||
return fileItem.file;
|
||||
}
|
||||
|
||||
|
||||
// If it's from IndexedDB storage, reconstruct the File
|
||||
if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') {
|
||||
const arrayBuffer = await fileItem.arrayBuffer();
|
||||
@@ -83,8 +84,8 @@ const FilePickerModal = ({
|
||||
lastModified: fileItem.lastModified || Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
// If it has data property, reconstruct the File
|
||||
|
||||
// If it has data property, reconstruct the File
|
||||
if (fileItem.data) {
|
||||
const blob = new Blob([fileItem.data], { type: fileItem.type || 'application/pdf' });
|
||||
return new File([blob], fileItem.name, {
|
||||
@@ -92,7 +93,7 @@ const FilePickerModal = ({
|
||||
lastModified: fileItem.lastModified || Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.warn('Could not convert file item:', fileItem);
|
||||
return null;
|
||||
} catch (error) {
|
||||
@@ -101,10 +102,10 @@ const FilePickerModal = ({
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// Filter out any null values and return valid Files
|
||||
const validFiles = convertedFiles.filter((f): f is File => f !== null);
|
||||
|
||||
|
||||
onSelectFiles(validFiles);
|
||||
onClose();
|
||||
};
|
||||
@@ -154,20 +155,20 @@ const FilePickerModal = ({
|
||||
<ScrollArea.Autosize mah={400}>
|
||||
<SimpleGrid cols={2} spacing="md">
|
||||
{storedFiles.map((file) => {
|
||||
const fileId = file.id || file.name;
|
||||
const fileId = file.id;
|
||||
const isSelected = selectedFileIds.includes(fileId);
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={fileId}
|
||||
p="sm"
|
||||
style={{
|
||||
border: isSelected
|
||||
? '2px solid var(--mantine-color-blue-6)'
|
||||
border: isSelected
|
||||
? '2px solid var(--mantine-color-blue-6)'
|
||||
: '1px solid var(--mantine-color-gray-3)',
|
||||
borderRadius: 8,
|
||||
backgroundColor: isSelected
|
||||
? 'var(--mantine-color-blue-0)'
|
||||
backgroundColor: isSelected
|
||||
? 'var(--mantine-color-blue-0)'
|
||||
: 'transparent',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
@@ -180,7 +181,7 @@ const FilePickerModal = ({
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
|
||||
|
||||
{/* Thumbnail */}
|
||||
<Box
|
||||
style={{
|
||||
@@ -246,11 +247,11 @@ const FilePickerModal = ({
|
||||
<Button variant="light" onClick={onClose}>
|
||||
{t("close", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={selectedFileIds.length === 0}
|
||||
>
|
||||
{selectedFileIds.length > 0
|
||||
{selectedFileIds.length > 0
|
||||
? `${t("fileUpload.loadFromStorage", "Load")} ${selectedFileIds.length} ${t("fileUpload.uploadFiles", "Files")}`
|
||||
: t("fileUpload.loadFromStorage", "Load Files")
|
||||
}
|
||||
@@ -261,4 +262,4 @@ const FilePickerModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FilePickerModal;
|
||||
export default FilePickerModal;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import { FileWithUrl } from '../../types/file';
|
||||
import { Box, Center } from '@mantine/core';
|
||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
||||
import { StirlingFileStub } from '../../types/fileContext';
|
||||
import DocumentThumbnail from './filePreview/DocumentThumbnail';
|
||||
import DocumentStack from './filePreview/DocumentStack';
|
||||
import HoverOverlay from './filePreview/HoverOverlay';
|
||||
@@ -8,7 +9,7 @@ import NavigationArrows from './filePreview/NavigationArrows';
|
||||
|
||||
export interface FilePreviewProps {
|
||||
// Core file data
|
||||
file: File | FileWithUrl | null;
|
||||
file: File | StirlingFileStub | null;
|
||||
thumbnail?: string | null;
|
||||
|
||||
// Optional features
|
||||
@@ -21,7 +22,7 @@ export interface FilePreviewProps {
|
||||
isAnimating?: boolean;
|
||||
|
||||
// Event handlers
|
||||
onFileClick?: (file: File | FileWithUrl | null) => void;
|
||||
onFileClick?: (file: File | StirlingFileStub | null) => void;
|
||||
onPrevious?: () => void;
|
||||
onNext?: () => void;
|
||||
}
|
||||
@@ -38,7 +39,21 @@ const FilePreview: React.FC<FilePreviewProps> = ({
|
||||
onPrevious,
|
||||
onNext
|
||||
}) => {
|
||||
if (!file) return null;
|
||||
if (!file) {
|
||||
return (
|
||||
<Box style={{ width: '100%', height: '100%' }}>
|
||||
<Center style={{ width: '100%', height: '100%' }}>
|
||||
<InsertDriveFileIcon
|
||||
style={{
|
||||
fontSize: '4rem',
|
||||
color: 'var(--mantine-color-gray-4)',
|
||||
opacity: 0.6
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const hasMultipleFiles = totalFiles > 1;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user