mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 17:54:19 +01:00
feat: Implement keyboard shortcuts system with UI management, add txt-to-pdf tests, and introduce a generic warning modal.
This commit is contained in:
1 parent
77ee986e2c
commit
5fecc701c6
15 files changed
+947
-23
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { formatBytes, readFileAsArrayBuffer } from '../utils/helpers'
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'
|
||||
|
||||
let selectedFile: File | null = null
|
||||
let viewerIframe: HTMLIFrameElement | null = null
|
||||
@@ -67,7 +68,7 @@ async function loadPdfInViewer(file: File) {
|
||||
enablePermissions: false,
|
||||
}
|
||||
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs))
|
||||
} catch {}
|
||||
} catch { }
|
||||
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.className = 'w-full h-full border-0'
|
||||
@@ -103,7 +104,7 @@ function setupAnnotationViewer(iframe: HTMLIFrameElement) {
|
||||
try {
|
||||
const stampBtn = doc.getElementById('editorStampButton') as HTMLButtonElement | null
|
||||
stampBtn?.click()
|
||||
} catch {}
|
||||
} catch { }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -173,4 +174,6 @@ if (backToToolsBtn) {
|
||||
backToToolsBtn.addEventListener('click', () => {
|
||||
window.location.href = '/'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
initializeGlobalShortcuts()
|
||||
@@ -6,6 +6,7 @@ import * as pdfjsLib from 'pdfjs-dist';
|
||||
import Sortable from 'sortablejs';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import '../../css/bookmark.css';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -453,6 +454,8 @@ function cancelDestinationPicking() {
|
||||
|
||||
// Setup canvas click handler for destination picking
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeGlobalShortcuts();
|
||||
|
||||
const canvas = document.getElementById('pdf-canvas');
|
||||
const canvasWrapper = document.getElementById('pdf-canvas-wrapper');
|
||||
const cancelPickingBtn = document.getElementById('cancel-picking-btn');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import JSZip from 'jszip'
|
||||
import { downloadFile, formatBytes, readFileAsArrayBuffer } from '../utils/helpers';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
|
||||
const worker = new Worker(new URL('/workers/json-to-pdf.worker.js', import.meta.url));
|
||||
|
||||
@@ -17,10 +18,10 @@ function showStatus(
|
||||
) {
|
||||
statusMessage.textContent = message
|
||||
statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${type === 'success'
|
||||
? 'bg-green-900 text-green-200'
|
||||
: type === 'error'
|
||||
? 'bg-red-900 text-red-200'
|
||||
: 'bg-blue-900 text-blue-200'
|
||||
? 'bg-green-900 text-green-200'
|
||||
: type === 'error'
|
||||
? 'bg-red-900 text-red-200'
|
||||
: 'bg-blue-900 text-blue-200'
|
||||
}`
|
||||
statusMessage.classList.remove('hidden')
|
||||
}
|
||||
@@ -155,4 +156,4 @@ convertBtn.addEventListener('click', convertJSONsToPDF)
|
||||
|
||||
// Initialize
|
||||
showStatus('Select JSON files to get started', 'info')
|
||||
|
||||
initializeGlobalShortcuts()
|
||||
@@ -5,6 +5,7 @@ import JSZip from 'jszip';
|
||||
import Sortable from 'sortablejs';
|
||||
import { downloadFile } from '../utils/helpers';
|
||||
import { renderPagesProgressively, cleanupLazyRendering, renderPageToCanvas, createPlaceholder } from '../utils/render-utils';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -147,6 +148,8 @@ function initializeTool() {
|
||||
console.log('PDF Multi Tool: Initializing...');
|
||||
createIcons({ icons });
|
||||
|
||||
initializeGlobalShortcuts();
|
||||
|
||||
document.getElementById('close-tool-btn')?.addEventListener('click', () => {
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import JSZip from 'jszip'
|
||||
import { downloadFile, formatBytes, readFileAsArrayBuffer } from '../utils/helpers';
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
|
||||
|
||||
const worker = new Worker(new URL('/workers/pdf-to-json.worker.js', import.meta.url));
|
||||
|
||||
@@ -17,10 +18,10 @@ function showStatus(
|
||||
) {
|
||||
statusMessage.textContent = message
|
||||
statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${type === 'success'
|
||||
? 'bg-green-900 text-green-200'
|
||||
: type === 'error'
|
||||
? 'bg-red-900 text-red-200'
|
||||
: 'bg-blue-900 text-blue-200'
|
||||
? 'bg-green-900 text-green-200'
|
||||
: type === 'error'
|
||||
? 'bg-red-900 text-red-200'
|
||||
: 'bg-blue-900 text-blue-200'
|
||||
}`
|
||||
statusMessage.classList.remove('hidden')
|
||||
}
|
||||
@@ -149,6 +150,5 @@ if (backToToolsBtn) {
|
||||
|
||||
convertBtn.addEventListener('click', convertPDFsToJSON)
|
||||
|
||||
// Initialize
|
||||
showStatus('Select PDF files to get started', 'info')
|
||||
|
||||
initializeGlobalShortcuts()
|
||||
@@ -0,0 +1,189 @@
|
||||
import { setupToolInterface } from '../handlers/toolSelectionHandler.js';
|
||||
import { categories } from '../config/tools.js';
|
||||
|
||||
export class ShortcutsManager {
|
||||
private static STORAGE_KEY = 'bentopdf_shortcuts';
|
||||
private static shortcuts: Map<string, string> = new Map();
|
||||
private static defaultShortcuts: Map<string, string> = new Map();
|
||||
|
||||
private static getToolId(tool: any): string {
|
||||
if (tool.id) return tool.id;
|
||||
if (tool.href) {
|
||||
// Extract filename without extension from href
|
||||
const match = tool.href.match(/\/([^/]+)\.html$/);
|
||||
return match ? match[1] : tool.href;
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
static init() {
|
||||
this.loadDefaults();
|
||||
this.loadFromStorage();
|
||||
this.setupGlobalListener();
|
||||
}
|
||||
|
||||
private static loadDefaults() {
|
||||
this.defaultShortcuts.set('merge', 'mod+shift+m');
|
||||
this.defaultShortcuts.set('split', 'mod+shift+s');
|
||||
this.defaultShortcuts.set('compress', 'mod+shift+c');
|
||||
}
|
||||
|
||||
private static loadFromStorage() {
|
||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
const parsed = JSON.parse(stored);
|
||||
this.shortcuts = new Map(Object.entries(parsed));
|
||||
|
||||
const allTools = categories.flatMap(c => c.tools);
|
||||
const validToolIds = allTools.map(t => this.getToolId(t));
|
||||
for (const [toolId, _] of this.shortcuts.entries()) {
|
||||
if (!validToolIds.includes(toolId)) {
|
||||
this.shortcuts.delete(toolId);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.shortcuts.size !== Object.keys(parsed).length) {
|
||||
this.save();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse shortcuts from local storage', e);
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
}
|
||||
} else {
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
}
|
||||
}
|
||||
|
||||
static save() {
|
||||
const obj = Object.fromEntries(this.shortcuts);
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(obj));
|
||||
}
|
||||
|
||||
static reset() {
|
||||
this.shortcuts = new Map(this.defaultShortcuts);
|
||||
this.save();
|
||||
// Dispatch event to update UI if needed
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
}
|
||||
|
||||
static getShortcut(toolId: string): string | undefined {
|
||||
return this.shortcuts.get(toolId);
|
||||
}
|
||||
|
||||
static findToolByShortcut(key: string): string | undefined {
|
||||
for (const [id, k] of this.shortcuts.entries()) {
|
||||
if (k === key) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static setShortcut(toolId: string, key: string) {
|
||||
if (key) {
|
||||
this.shortcuts.set(toolId, key);
|
||||
} else {
|
||||
this.shortcuts.delete(toolId);
|
||||
}
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
}
|
||||
|
||||
static getAllShortcuts(): Map<string, string> {
|
||||
return this.shortcuts;
|
||||
}
|
||||
|
||||
static exportSettings() {
|
||||
// Create a map with all tools, defaulting to empty string if not set
|
||||
const exportObj: Record<string, string> = {};
|
||||
|
||||
const allTools = categories.flatMap(c => c.tools);
|
||||
|
||||
allTools.forEach(tool => {
|
||||
const toolId = this.getToolId(tool);
|
||||
exportObj[toolId] = this.shortcuts.get(toolId) || '';
|
||||
});
|
||||
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj, null, 2));
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute("download", "bentopdf_shortcuts.json");
|
||||
document.body.appendChild(downloadAnchorNode);
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}
|
||||
|
||||
static importSettings(jsonString: string): boolean {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
for (const key in parsed) {
|
||||
if (typeof parsed[key] !== 'string') {
|
||||
throw new Error('Invalid shortcut format');
|
||||
}
|
||||
}
|
||||
this.shortcuts = new Map(Object.entries(parsed));
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('shortcuts-updated'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Import failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static setupGlobalListener() {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// Ignore if typing in an input or textarea
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build key string
|
||||
const keys: string[] = [];
|
||||
const isMac = navigator.userAgent.toUpperCase().includes('MAC');
|
||||
|
||||
// On Mac: metaKey = Command, ctrlKey = Control
|
||||
// On Windows/Linux: metaKey is rare, ctrlKey = Ctrl
|
||||
if (isMac) {
|
||||
if (e.metaKey) keys.push('mod'); // Command on Mac
|
||||
if (e.ctrlKey) keys.push('ctrl'); // Control on Mac (separate from Command)
|
||||
} else {
|
||||
if (e.ctrlKey || e.metaKey) keys.push('mod'); // Ctrl on Windows/Linux
|
||||
}
|
||||
if (e.altKey) keys.push('alt');
|
||||
if (e.shiftKey) keys.push('shift');
|
||||
|
||||
const key = e.key.toLowerCase();
|
||||
if (!['control', 'shift', 'alt', 'meta'].includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
const combo = keys.join('+');
|
||||
|
||||
for (const [toolId, shortcut] of this.shortcuts.entries()) {
|
||||
if (shortcut === combo) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Find the tool in categories
|
||||
const allTools = categories.flatMap(c => c.tools);
|
||||
const tool = allTools.find(t => this.getToolId(t) === toolId);
|
||||
|
||||
if (tool && (tool as any).href) {
|
||||
const href = (tool as any).href;
|
||||
window.location.href = href.startsWith('/') ? href : `/${href}`;
|
||||
} else if (tool && (tool as any).id) {
|
||||
if (window.location.pathname === '/' || window.location.pathname === '/index.html') {
|
||||
setupToolInterface(toolId);
|
||||
} else {
|
||||
window.location.href = `/#tool-${toolId}`;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, { capture: true });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { downloadFile, formatBytes } from "../utils/helpers";
|
||||
import { initializeGlobalShortcuts } from "../utils/shortcuts-init.js";
|
||||
|
||||
|
||||
const worker = new Worker('/workers/table-of-contents.worker.js');
|
||||
|
||||
@@ -54,10 +56,10 @@ function showStatus(
|
||||
) {
|
||||
statusMessage.textContent = message;
|
||||
statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${type === 'success'
|
||||
? 'bg-green-900 text-green-200'
|
||||
: type === 'error'
|
||||
? 'bg-red-900 text-red-200'
|
||||
: 'bg-blue-900 text-blue-200'
|
||||
? 'bg-green-900 text-green-200'
|
||||
: type === 'error'
|
||||
? 'bg-red-900 text-red-200'
|
||||
: 'bg-blue-900 text-blue-200'
|
||||
}`;
|
||||
statusMessage.classList.remove('hidden');
|
||||
}
|
||||
@@ -202,3 +204,5 @@ if (backToToolsBtn) {
|
||||
}
|
||||
|
||||
generateBtn.addEventListener('click', generateTableOfContents);
|
||||
|
||||
initializeGlobalShortcuts();
|
||||
@@ -154,6 +154,11 @@ export async function setupTxtToPdfTool() {
|
||||
|
||||
uploadBtn.addEventListener('click', switchToUpload);
|
||||
textBtn.addEventListener('click', switchToText);
|
||||
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
if (processBtn) {
|
||||
processBtn.onclick = txtToPdf;
|
||||
}
|
||||
}
|
||||
|
||||
export async function txtToPdf() {
|
||||
|
||||
Reference in new issue
Block a user