diff --git a/src/js/logic/bmp-to-pdf-page.ts b/src/js/logic/bmp-to-pdf-page.ts
index 97ccd46fa..b46fe5df7 100644
--- a/src/js/logic/bmp-to-pdf-page.ts
+++ b/src/js/logic/bmp-to-pdf-page.ts
@@ -1,6 +1,7 @@
import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide';
+import Sortable from 'sortablejs';
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
let files: File[] = [];
@@ -54,7 +55,8 @@ const updateUI = () => {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -69,24 +71,55 @@ const updateUI = () => {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
processBtn.classList.add('hidden');
}
};
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
const resetState = () => {
files = [];
updateUI();
@@ -201,4 +234,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (processBtn) {
processBtn.addEventListener('click', convert);
}
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
});
diff --git a/src/js/logic/csv-to-pdf-page.ts b/src/js/logic/csv-to-pdf-page.ts
index e0ec5560c..5e747f77d 100644
--- a/src/js/logic/csv-to-pdf-page.ts
+++ b/src/js/logic/csv-to-pdf-page.ts
@@ -5,6 +5,7 @@ import {
formatBytes,
} from '../utils/helpers.js';
import { state } from '../state.js';
+import Sortable from 'sortablejs';
import { createIcons, icons } from 'lucide';
document.addEventListener('DOMContentLoaded', () => {
@@ -33,10 +34,12 @@ document.addEventListener('DOMContentLoaded', () => {
if (fileDisplayArea) {
fileDisplayArea.innerHTML = '';
+
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -51,19 +54,28 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
createIcons({ icons });
+ initializeFileListSortable();
}
if (fileControls) fileControls.classList.remove('hidden');
convertOptions.classList.remove('hidden');
@@ -228,4 +240,26 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateUI();
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/excel-to-pdf-page.ts b/src/js/logic/excel-to-pdf-page.ts
index 8d4dbb7d7..78b7ab803 100644
--- a/src/js/logic/excel-to-pdf-page.ts
+++ b/src/js/logic/excel-to-pdf-page.ts
@@ -7,6 +7,7 @@ import {
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
document.addEventListener('DOMContentLoaded', () => {
state.files = [];
@@ -37,7 +38,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -52,15 +54,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -213,4 +223,29 @@ document.addEventListener('DOMContentLoaded', () => {
if (processBtn) {
processBtn.addEventListener('click', convertToPdf);
}
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/heic-to-pdf-page.ts b/src/js/logic/heic-to-pdf-page.ts
index 3f2f6c006..361847ef1 100644
--- a/src/js/logic/heic-to-pdf-page.ts
+++ b/src/js/logic/heic-to-pdf-page.ts
@@ -1,6 +1,7 @@
import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide';
+import Sortable from 'sortablejs';
import heic2any from 'heic2any';
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
@@ -21,7 +22,8 @@ const updateUI = () => {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -36,24 +38,55 @@ const updateUI = () => {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
processBtn.classList.add('hidden');
}
};
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
const resetState = () => {
files = [];
updateUI();
@@ -176,4 +209,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (processBtn) {
processBtn.addEventListener('click', convert);
}
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
});
diff --git a/src/js/logic/image-to-pdf-page.ts b/src/js/logic/image-to-pdf-page.ts
index 3051aca98..6bf91c373 100644
--- a/src/js/logic/image-to-pdf-page.ts
+++ b/src/js/logic/image-to-pdf-page.ts
@@ -3,6 +3,7 @@ import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js';
import { PyMuPDF } from '@bentopdf/pymupdf-wasm';
import { getWasmBaseUrl } from '../config/wasm-cdn-config.js';
+import Sortable from 'sortablejs';
import heic2any from 'heic2any';
const SUPPORTED_FORMATS = '.jpg,.jpeg,.png,.bmp,.gif,.tiff,.tif,.pnm,.pgm,.pbm,.ppm,.pam,.jxr,.jpx,.jp2,.psd,.svg,.heic,.heif,.webp';
@@ -17,6 +18,28 @@ if (document.readyState === 'loading') {
initializePage();
}
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
function initializePage() {
createIcons({ icons });
@@ -80,6 +103,10 @@ function initializePage() {
document.getElementById('back-to-tools')?.addEventListener('click', () => {
window.location.href = import.meta.env.BASE_URL;
});
+
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
}
function handleFileUpload(e: Event) {
@@ -132,7 +159,8 @@ function updateUI() {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -147,18 +175,27 @@ function updateUI() {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
optionsDiv.classList.add('hidden');
diff --git a/src/js/logic/jpg-to-pdf-page.ts b/src/js/logic/jpg-to-pdf-page.ts
index 40264cd69..80b693e98 100644
--- a/src/js/logic/jpg-to-pdf-page.ts
+++ b/src/js/logic/jpg-to-pdf-page.ts
@@ -4,6 +4,8 @@ import { downloadFile, formatBytes } from '../utils/helpers.js';
import { PyMuPDF } from '@bentopdf/pymupdf-wasm';
import { getWasmBaseUrl } from '../config/wasm-cdn-config.js';
+import Sortable from 'sortablejs';
+
const SUPPORTED_FORMATS = '.jpg,.jpeg,.jp2,.jpx';
const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/jp2'];
@@ -16,6 +18,28 @@ if (document.readyState === 'loading') {
initializePage();
}
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
function initializePage() {
createIcons({ icons });
@@ -73,6 +97,10 @@ function initializePage() {
document.getElementById('back-to-tools')?.addEventListener('click', () => {
window.location.href = import.meta.env.BASE_URL;
});
+
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
}
function handleFileUpload(e: Event) {
@@ -125,7 +153,8 @@ function updateUI() {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -140,18 +169,27 @@ function updateUI() {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
optionsDiv.classList.add('hidden');
diff --git a/src/js/logic/ods-to-pdf-page.ts b/src/js/logic/ods-to-pdf-page.ts
index 24343ece9..18b07abbe 100644
--- a/src/js/logic/ods-to-pdf-page.ts
+++ b/src/js/logic/ods-to-pdf-page.ts
@@ -6,6 +6,7 @@ import {
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
const ACCEPTED_EXTENSIONS = ['.ods'];
const FILETYPE_NAME = 'ODS';
@@ -39,7 +40,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -54,15 +56,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -186,4 +196,30 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateUI();
+
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/odt-to-pdf-page.ts b/src/js/logic/odt-to-pdf-page.ts
index 43ff89561..4f4ebb52d 100644
--- a/src/js/logic/odt-to-pdf-page.ts
+++ b/src/js/logic/odt-to-pdf-page.ts
@@ -6,6 +6,7 @@ import {
} from '../utils/helpers.js';
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
+import Sortable from 'sortablejs';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
document.addEventListener('DOMContentLoaded', () => {
@@ -37,7 +38,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -52,15 +54,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -211,5 +221,31 @@ document.addEventListener('DOMContentLoaded', () => {
processBtn.addEventListener('click', convertToPdf);
}
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
+
updateUI();
});
diff --git a/src/js/logic/pages-to-pdf-page.ts b/src/js/logic/pages-to-pdf-page.ts
index 506eced32..a03354946 100644
--- a/src/js/logic/pages-to-pdf-page.ts
+++ b/src/js/logic/pages-to-pdf-page.ts
@@ -6,6 +6,7 @@ import {
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
const ACCEPTED_EXTENSIONS = ['.pages'];
const FILETYPE_NAME = 'Pages';
@@ -39,7 +40,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -54,15 +56,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -185,4 +195,26 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateUI();
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/png-to-pdf-page.ts b/src/js/logic/png-to-pdf-page.ts
index eb9e34a08..92fb41281 100644
--- a/src/js/logic/png-to-pdf-page.ts
+++ b/src/js/logic/png-to-pdf-page.ts
@@ -1,6 +1,7 @@
import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js';
+import Sortable from 'sortablejs';
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
let files: File[] = [];
@@ -11,6 +12,28 @@ if (document.readyState === 'loading') {
initializePage();
}
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
function initializePage() {
createIcons({ icons });
@@ -69,6 +92,9 @@ function initializePage() {
document.getElementById('back-to-tools')?.addEventListener('click', () => {
window.location.href = import.meta.env.BASE_URL;
});
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
}
function handleFileUpload(e: Event) {
@@ -113,7 +139,8 @@ function updateUI() {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -128,18 +155,27 @@ function updateUI() {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
optionsDiv.classList.add('hidden');
diff --git a/src/js/logic/powerpoint-to-pdf-page.ts b/src/js/logic/powerpoint-to-pdf-page.ts
index 130fac52a..e51bb5b97 100644
--- a/src/js/logic/powerpoint-to-pdf-page.ts
+++ b/src/js/logic/powerpoint-to-pdf-page.ts
@@ -7,6 +7,7 @@ import {
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
document.addEventListener('DOMContentLoaded', () => {
state.files = [];
@@ -37,7 +38,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -52,15 +54,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -215,4 +225,26 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateUI();
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/pub-to-pdf-page.ts b/src/js/logic/pub-to-pdf-page.ts
index 1c2e68248..4979dd71e 100644
--- a/src/js/logic/pub-to-pdf-page.ts
+++ b/src/js/logic/pub-to-pdf-page.ts
@@ -2,6 +2,7 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
+import Sortable from 'sortablejs';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
const ACCEPTED_EXTENSIONS = ['.pub'];
@@ -35,7 +36,11 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', String(index));
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-grab mr-3 flex-shrink-0';
+ dragHandle.innerHTML = '';
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
const nameSpan = document.createElement('div');
@@ -52,10 +57,11 @@ document.addEventListener('DOMContentLoaded', () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ fileDiv.append(dragHandle, infoContainer, removeBtn);
fileDisplayArea.appendChild(fileDiv);
}
createIcons({ icons });
+ setTimeout(() => initializeFileListSortable(), 0);
}
if (fileControls) fileControls.classList.remove('hidden');
convertOptions.classList.remove('hidden');
@@ -66,6 +72,25 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
+ let sortable: any = null;
+ const initializeFileListSortable = () => {
+ if (!fileDisplayArea) return;
+ if (sortable) sortable.destroy();
+ sortable = Sortable.create(fileDisplayArea, {
+ animation: 150,
+ handle: '.drag-handle',
+ draggable: '.draggable-file',
+ onEnd: (evt: any) => {
+ const oldIndex = evt.oldIndex;
+ const newIndex = evt.newIndex;
+ if (oldIndex === undefined || newIndex === undefined) return;
+ const moved = state.files.splice(oldIndex, 1)[0];
+ state.files.splice(newIndex, 0, moved);
+ updateUI();
+ }
+ });
+ };
+
const resetState = () => {
state.files = [];
updateUI();
diff --git a/src/js/logic/svg-to-pdf-page.ts b/src/js/logic/svg-to-pdf-page.ts
index e88f7bd4a..f9ff75c24 100644
--- a/src/js/logic/svg-to-pdf-page.ts
+++ b/src/js/logic/svg-to-pdf-page.ts
@@ -2,6 +2,7 @@ import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js';
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
+import Sortable from 'sortablejs';
let files: File[] = [];
@@ -11,6 +12,28 @@ if (document.readyState === 'loading') {
initializePage();
}
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
function initializePage() {
createIcons({ icons });
@@ -69,6 +92,9 @@ function initializePage() {
document.getElementById('back-to-tools')?.addEventListener('click', () => {
window.location.href = import.meta.env.BASE_URL;
});
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
}
function handleFileUpload(e: Event) {
@@ -113,7 +139,8 @@ function updateUI() {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -128,18 +155,27 @@ function updateUI() {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
optionsDiv.classList.add('hidden');
diff --git a/src/js/logic/tiff-to-pdf-page.ts b/src/js/logic/tiff-to-pdf-page.ts
index 436cfcd4b..04d893da5 100644
--- a/src/js/logic/tiff-to-pdf-page.ts
+++ b/src/js/logic/tiff-to-pdf-page.ts
@@ -1,6 +1,7 @@
import { showLoader, hideLoader, showAlert } from '../ui.js';
import { downloadFile, formatBytes, readFileAsArrayBuffer } from '../utils/helpers.js';
import { createIcons, icons } from 'lucide';
+import Sortable from 'sortablejs';
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import { decode } from 'tiff';
@@ -21,7 +22,8 @@ const updateUI = () => {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -36,24 +38,55 @@ const updateUI = () => {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
processBtn.classList.add('hidden');
}
};
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
const resetState = () => {
files = [];
updateUI();
@@ -197,4 +230,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (processBtn) {
processBtn.addEventListener('click', convert);
}
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
});
diff --git a/src/js/logic/vsd-to-pdf-page.ts b/src/js/logic/vsd-to-pdf-page.ts
index 01430d650..31c0998ff 100644
--- a/src/js/logic/vsd-to-pdf-page.ts
+++ b/src/js/logic/vsd-to-pdf-page.ts
@@ -3,6 +3,7 @@ import { downloadFile, formatBytes } from '../utils/helpers.js';
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
const ACCEPTED_EXTENSIONS = ['.vsd', '.vsdx'];
const FILETYPE_NAME = 'VSD';
@@ -35,7 +36,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
const nameSpan = document.createElement('div');
@@ -45,14 +47,20 @@ document.addEventListener('DOMContentLoaded', () => {
metaSpan.className = 'text-xs text-gray-400';
metaSpan.textContent = formatBytes(file.size);
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
createIcons({ icons });
@@ -139,4 +147,26 @@ document.addEventListener('DOMContentLoaded', () => {
if (processBtn) processBtn.addEventListener('click', convert);
updateUI();
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/webp-to-pdf-page.ts b/src/js/logic/webp-to-pdf-page.ts
index 087c8b239..eba365a10 100644
--- a/src/js/logic/webp-to-pdf-page.ts
+++ b/src/js/logic/webp-to-pdf-page.ts
@@ -1,6 +1,7 @@
import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js';
import { downloadFile, readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js';
+import Sortable from 'sortablejs';
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
let files: File[] = [];
@@ -11,6 +12,28 @@ if (document.readyState === 'loading') {
initializePage();
}
+function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = files.splice(evt.oldIndex, 1)[0];
+ files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+}
+
function initializePage() {
createIcons({ icons });
@@ -69,6 +92,9 @@ function initializePage() {
document.getElementById('back-to-tools')?.addEventListener('click', () => {
window.location.href = import.meta.env.BASE_URL;
});
+ setTimeout(() => {
+ initializeFileListSortable();
+ }, 0);
}
function handleFileUpload(e: Event) {
@@ -113,7 +139,8 @@ function updateUI() {
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex items-center gap-2 overflow-hidden';
@@ -128,18 +155,27 @@ function updateUI() {
infoContainer.append(nameSpan, sizeSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
files = files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
});
createIcons({ icons });
+ initializeFileListSortable();
} else {
fileControls.classList.add('hidden');
optionsDiv.classList.add('hidden');
diff --git a/src/js/logic/word-to-pdf-page.ts b/src/js/logic/word-to-pdf-page.ts
index e198e3baa..19732f328 100644
--- a/src/js/logic/word-to-pdf-page.ts
+++ b/src/js/logic/word-to-pdf-page.ts
@@ -7,6 +7,7 @@ import {
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
document.addEventListener('DOMContentLoaded', () => {
state.files = [];
@@ -37,7 +38,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -52,15 +54,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -233,4 +243,26 @@ document.addEventListener('DOMContentLoaded', () => {
// Initialize UI state (ensures button is hidden when no files)
updateUI();
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});
diff --git a/src/js/logic/wps-to-pdf-page.ts b/src/js/logic/wps-to-pdf-page.ts
index 641bae233..a6d063e2d 100644
--- a/src/js/logic/wps-to-pdf-page.ts
+++ b/src/js/logic/wps-to-pdf-page.ts
@@ -6,6 +6,7 @@ import {
import { state } from '../state.js';
import { createIcons, icons } from 'lucide';
import { getLibreOfficeConverter, type LoadProgress } from '../utils/libreoffice-loader.js';
+import Sortable from 'sortablejs';
const ACCEPTED_EXTENSIONS = ['.wps'];
const FILETYPE_NAME = 'WPS';
@@ -39,7 +40,8 @@ document.addEventListener('DOMContentLoaded', () => {
for (let index = 0; index < state.files.length; index++) {
const file = state.files[index];
const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm draggable-file';
+ fileDiv.setAttribute('data-index', index.toString());
const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col overflow-hidden';
@@ -54,15 +56,23 @@ document.addEventListener('DOMContentLoaded', () => {
infoContainer.append(nameSpan, metaSpan);
+ const rightGroup = document.createElement('div');
+ rightGroup.className = 'flex items-center gap-1 ml-4';
+
+ const dragHandle = document.createElement('div');
+ dragHandle.className = 'drag-handle cursor-move text-gray-400 hover:text-white p-1 rounded transition-colors';
+ dragHandle.innerHTML = ``;
+
const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.className = 'text-red-400 hover:text-red-300 flex-shrink-0';
removeBtn.innerHTML = '';
removeBtn.onclick = () => {
state.files = state.files.filter((_, i) => i !== index);
updateUI();
};
- fileDiv.append(infoContainer, removeBtn);
+ rightGroup.append(dragHandle, removeBtn);
+ fileDiv.append(infoContainer, rightGroup);
fileDisplayArea.appendChild(fileDiv);
}
@@ -185,4 +195,26 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateUI();
+
+ function initializeFileListSortable() {
+ const fileDisplayArea = document.getElementById('file-display-area');
+ if (!fileDisplayArea) return;
+ if ((fileDisplayArea as any)._sortableInstance) {
+ (fileDisplayArea as any)._sortableInstance.destroy();
+ }
+ (fileDisplayArea as any)._sortableInstance = Sortable.create(fileDisplayArea, {
+ handle: '.drag-handle',
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onEnd: function (evt: any) {
+ if (evt.oldIndex !== evt.newIndex) {
+ const moved = state.files.splice(evt.oldIndex, 1)[0];
+ state.files.splice(evt.newIndex, 0, moved);
+ updateUI();
+ }
+ },
+ });
+ }
});