Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions src/js/logic/jpg-to-pdf-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
compressImageFile,
} from '../utils/image-compress.js';

// @ts-ignore
import Sortable from 'sortablejs';
Comment thread
AB527 marked this conversation as resolved.

const SUPPORTED_FORMATS = '.jpg,.jpeg,.jp2,.jpx';
const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/jp2'];

Expand Down Expand Up @@ -76,6 +79,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) {
Expand Down Expand Up @@ -134,7 +141,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';
'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';
Expand All @@ -149,25 +157,59 @@ 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 = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="5" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="19" r="1"/></svg>`;
Comment on lines +163 to +166
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make drag handle keyboard-accessible.

The reorder control is a non-focusable div (Line 163), so keyboard-only users cannot access reordering. Use a semantic button (or add tabindex, role, and keyboard handlers) plus an accessible label.

🧰 Tools
🪛 ast-grep (0.41.1)

[warning] 165-165: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: dragHandle.innerHTML = <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="5" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="19" r="1"/></svg>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 165-165: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: dragHandle.innerHTML = <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="5" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="19" r="1"/></svg>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/js/logic/jpg-to-pdf-page.ts` around lines 163 - 166, The drag handle is
currently a non-focusable div (dragHandle) which prevents keyboard users from
reordering; replace the div with a semantic focusable control (create a button
element with type="button") or at minimum add tabindex="0", role="button", and
keydown handlers, and give it an accessible label (e.g., aria-label="Move page"
or visually hidden text). Update the element creation where dragHandle is
constructed so it uses button (or adds tabindex/role and implements Enter/Space
keyboard activation that triggers the same drag/reorder logic), keep the same
className/styling, and ensure event listeners that start dragging reference this
button element.


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 = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
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');
}
}

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();
}
},
}
);
}

async function ensurePyMuPDF(): Promise<any> {
if (!pymupdf) {
pymupdf = await loadPyMuPDF();
Expand Down
Loading