(function (app) { 'use strict'; var dom = app.dom; var util = app.util; var filesModule = app.modules.files; // Build ZDDC filename from file row data — delegates to shared zddc.formatFilename() function buildZddcFileName(fileData, droppedExt) { var ext = (fileData.extension || droppedExt || '').toLowerCase().replace(/^\.+/, ''); return zddc.formatFilename({ trackingNumber: fileData.trackingNumber || '', revision: fileData.revision || '', status: fileData.status || '', title: fileData.title || '', extension: ext, }); } function setupRowDropTargets() { if (app.state.mode !== 'edit') { return; } var rows = document.querySelectorAll('table tbody tr:not(.self-entry)'); rows.forEach(function (row) { row.addEventListener('dragover', function (e) { if (app.state.mode !== 'edit') { return; } e.preventDefault(); e.stopPropagation(); row.classList.add('ring-2', 'ring-blue-400', 'bg-blue-50'); }); row.addEventListener('dragleave', function () { row.classList.remove('ring-2', 'ring-blue-400', 'bg-blue-50'); }); row.addEventListener('drop', async function (e) { row.classList.remove('ring-2', 'ring-blue-400', 'bg-blue-50'); if (app.state.mode !== 'edit') { return; } var droppedFile = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]; if (!droppedFile) { return; } e.preventDefault(); e.stopPropagation(); var setStatus = app.modules.data && app.modules.data.setStatus; // Prompt for directory if none selected if (!app.data.selectedDirHandle) { try { app.data.selectedDirHandle = await window.showDirectoryPicker({ mode: 'readwrite' }); app.data.selectedDirName = app.data.selectedDirHandle.name || ''; if (filesModule.updateDirectoryIndicator) { filesModule.updateDirectoryIndicator(); } } catch (pickErr) { if (setStatus) { setStatus('A directory is required to copy files', 'error'); } return; } } var indexCell = row.querySelector('td[data-index]'); if (!indexCell) { return; } var fileIndex = Number(indexCell.dataset.index); var fileData = app.data.files[fileIndex]; if (!fileData) { return; } var droppedExt = zddc.splitExtension(droppedFile.name).extension; var zddcName = buildZddcFileName(fileData, droppedExt); if (!zddcName) { zddcName = droppedFile.name; } var hashCell = row.querySelector('td:last-child'); try { if (hashCell) { hashCell.textContent = 'copying\u2026'; } var dirHandle = app.data.selectedDirHandle; var newHandle = await dirHandle.getFileHandle(zddcName, { create: true }); var writable = await newHandle.createWritable(); await writable.write(droppedFile); await writable.close(); if (hashCell) { hashCell.textContent = 'hashing\u2026'; } var written = await newHandle.getFile(); var hash = await util.hashFile(written); fileData.fileHandle = newHandle; fileData.path = zddcName; fileData.name = zddcName; fileData.size = written.size; fileData.fileSize = written.size; fileData.sha256 = hash; if (!fileData.extension) { fileData.extension = droppedExt; } filesModule.updateFilesInJson(app.data.files); filesModule.render(); app.state.apply(); app.markDirty(); if (setStatus) { setStatus('Copied \u2192 ' + zddcName, 'success'); } } catch (err) { console.error('[transmittal] row file drop failed', err); if (hashCell) { hashCell.textContent = 'error'; } if (setStatus) { setStatus('Drop failed: ' + (err.message || err), 'error'); } } }); }); } filesModule.clearTable = function clearTable() { var tbody = document.querySelector('table tbody'); if (tbody) { tbody.innerHTML = ''; } }; filesModule.renderSingleRow = function renderSingleRow(file, index) { var tbody = document.querySelector('table tbody'); if (!tbody) { return null; } var row = document.createElement('tr'); var numCell = document.createElement('td'); numCell.className = 'px-2 py-1 align-top text-center text-gray-400'; numCell.textContent = String(index + 1); row.appendChild(numCell); var trackingCell = document.createElement('td'); trackingCell.className = 'px-2 py-1 align-top whitespace-nowrap font-mono'; var link = document.createElement('a'); link.textContent = file.trackingNumber || ''; var relativePath = file.path || file.name || ''; if (relativePath) { link.href = encodeURI(relativePath); link.dataset.relativePath = relativePath; } else { link.href = '#'; } link.title = relativePath || (file.name || ''); link.className = 'text-blue-600 hover:underline'; var ext = (file.extension || zddc.splitExtension(relativePath).extension); if (app.constants.viewableExts.indexOf(ext) !== -1) { link.target = '_blank'; link.rel = 'noopener'; } else { var filename = (relativePath.split('/').pop() || file.name || 'download'); link.setAttribute('download', filename); } trackingCell.appendChild(link); trackingCell.contentEditable = 'false'; trackingCell.dataset.index = String(index); trackingCell.dataset.field = 'trackingNumber'; row.appendChild(trackingCell); var titleCell = document.createElement('td'); titleCell.className = 'px-2 py-1 align-top whitespace-normal break-words w-full'; titleCell.textContent = file.title || ''; titleCell.contentEditable = (app.state.mode === 'edit').toString(); titleCell.setAttribute('role', 'textbox'); titleCell.setAttribute('aria-multiline', 'false'); titleCell.setAttribute('tabindex', '0'); titleCell.dataset.index = String(index); titleCell.dataset.field = 'title'; row.appendChild(titleCell); var revisionCell = document.createElement('td'); revisionCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono'; revisionCell.textContent = file.revision || ''; revisionCell.contentEditable = (app.state.mode === 'edit').toString(); revisionCell.setAttribute('role', 'textbox'); revisionCell.setAttribute('aria-multiline', 'false'); revisionCell.setAttribute('tabindex', '0'); revisionCell.dataset.index = String(index); revisionCell.dataset.field = 'revision'; row.appendChild(revisionCell); var statusCell = document.createElement('td'); statusCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono'; statusCell.textContent = file.status || ''; statusCell.contentEditable = (app.state.mode === 'edit').toString(); statusCell.setAttribute('role', 'textbox'); statusCell.setAttribute('aria-multiline', 'false'); statusCell.setAttribute('tabindex', '0'); statusCell.dataset.index = String(index); statusCell.dataset.field = 'status'; row.appendChild(statusCell); var extCell = document.createElement('td'); extCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono'; extCell.textContent = (file.extension || '').toLowerCase(); extCell.contentEditable = 'false'; row.appendChild(extCell); var sizeCell = document.createElement('td'); sizeCell.className = 'px-2 py-1 align-top whitespace-nowrap text-right font-mono'; var fileSizeValue = (file.fileSize != null ? file.fileSize : file.size); sizeCell.textContent = util.formatFileSize(fileSizeValue); sizeCell.contentEditable = 'false'; row.appendChild(sizeCell); var hashCell = document.createElement('td'); hashCell.className = 'px-2 py-1 align-top font-mono text-[9px] whitespace-normal break-all leading-snug'; if (file.sha256) { hashCell.textContent = util.formatShortFileHash(file.sha256); } else { var prog = document.createElement('div'); prog.className = 'hash-progress'; var bar = document.createElement('div'); bar.className = 'hash-progress-bar'; var fill = document.createElement('div'); fill.className = 'hash-progress-fill'; bar.appendChild(fill); prog.appendChild(bar); hashCell.appendChild(prog); } row.appendChild(hashCell); tbody.appendChild(row); return hashCell; }; function renderSelfRow(tbody, rowNum) { var self = filesModule.buildSelfEntry ? filesModule.buildSelfEntry() : null; if (!self) { return; } var row = document.createElement('tr'); row.className = 'self-entry'; var numCell = document.createElement('td'); numCell.className = 'px-2 py-1 align-top text-center text-gray-400'; numCell.textContent = String(rowNum); row.appendChild(numCell); var trackingCell = document.createElement('td'); trackingCell.className = 'px-2 py-1 align-top whitespace-nowrap font-mono text-gray-500'; var selfPath = self.path || self.name || ''; if (selfPath) { var link = document.createElement('a'); link.href = encodeURI(selfPath); link.textContent = self.trackingNumber || ''; link.className = 'text-gray-500 hover:underline'; link.target = '_blank'; link.rel = 'noopener'; trackingCell.appendChild(link); } else { trackingCell.textContent = self.trackingNumber || ''; } row.appendChild(trackingCell); var titleCell = document.createElement('td'); titleCell.className = 'px-2 py-1 align-top whitespace-normal break-words w-full text-gray-500'; titleCell.textContent = self.title || ''; row.appendChild(titleCell); var revisionCell = document.createElement('td'); revisionCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono text-gray-500'; revisionCell.textContent = self.revision || ''; row.appendChild(revisionCell); var statusCell = document.createElement('td'); statusCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono text-gray-500'; statusCell.textContent = self.status || ''; row.appendChild(statusCell); var extCell = document.createElement('td'); extCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono text-gray-500'; extCell.textContent = 'html'; row.appendChild(extCell); var sizeCell = document.createElement('td'); sizeCell.className = 'px-2 py-1 align-top whitespace-nowrap text-right font-mono text-gray-400'; sizeCell.textContent = '\u2014'; row.appendChild(sizeCell); var hashCell = document.createElement('td'); hashCell.className = 'px-2 py-1 align-top font-mono text-[9px] whitespace-nowrap text-gray-400 italic'; hashCell.textContent = 'see above'; row.appendChild(hashCell); tbody.appendChild(row); } filesModule.render = function render() { var tbody = document.querySelector('table tbody'); if (!tbody) { return; } tbody.innerHTML = ''; filesModule.sortFilesInPlace(app.data.files); var filters = app.modules.filters ? app.modules.filters.getActiveFilters() : {}; var filtered = []; (app.data.files || []).forEach(function (file, originalIndex) { if (!app.modules.filters || app.modules.filters.fileMatchesFilters(file, filters)) { filtered.push({ file: file, index: originalIndex }); } }); // Row 0: transmittal self-entry (always pinned first) renderSelfRow(tbody, 0); var rowNum = 0; filtered.forEach(function (entry) { var file = entry.file; var index = entry.index; var row = document.createElement('tr'); rowNum++; var numCell = document.createElement('td'); numCell.className = 'px-2 py-1 align-top text-center text-gray-400 whitespace-nowrap'; if (app.state.mode === 'edit') { var delBtn = document.createElement('button'); delBtn.type = 'button'; delBtn.className = 'row-delete-btn'; delBtn.textContent = '\u00d7'; delBtn.title = 'Remove this file'; delBtn.dataset.fileIndex = String(index); numCell.appendChild(delBtn); var numSpan = document.createElement('span'); numSpan.textContent = String(rowNum); numCell.appendChild(numSpan); } else { numCell.textContent = String(rowNum); } row.appendChild(numCell); var trackingCell = document.createElement('td'); trackingCell.className = 'px-2 py-1 align-top whitespace-nowrap font-mono'; var link = document.createElement('a'); link.textContent = file.trackingNumber || ''; var relativePath = file.path || file.name || ''; if (relativePath) { link.href = encodeURI(relativePath); link.dataset.relativePath = relativePath; } else { link.href = '#'; } link.title = relativePath || (file.name || ''); link.className = 'text-blue-600 hover:underline'; var ext = (file.extension || zddc.splitExtension(relativePath).extension); if (app.constants.viewableExts.indexOf(ext) !== -1) { link.target = '_blank'; link.rel = 'noopener'; } else { var filename = (relativePath.split('/').pop() || file.name || 'download'); link.setAttribute('download', filename); } trackingCell.appendChild(link); trackingCell.contentEditable = 'false'; trackingCell.dataset.index = String(index); trackingCell.dataset.field = 'trackingNumber'; row.appendChild(trackingCell); var titleCell = document.createElement('td'); titleCell.className = 'px-2 py-1 align-top whitespace-normal break-words w-full'; titleCell.textContent = file.title || ''; titleCell.contentEditable = (app.state.mode === 'edit').toString(); titleCell.setAttribute('role', 'textbox'); titleCell.setAttribute('aria-multiline', 'false'); titleCell.setAttribute('tabindex', '0'); titleCell.dataset.index = String(index); titleCell.dataset.field = 'title'; row.appendChild(titleCell); var revisionCell = document.createElement('td'); revisionCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono'; revisionCell.textContent = file.revision || ''; revisionCell.contentEditable = (app.state.mode === 'edit').toString(); revisionCell.setAttribute('role', 'textbox'); revisionCell.setAttribute('aria-multiline', 'false'); revisionCell.setAttribute('tabindex', '0'); revisionCell.dataset.index = String(index); revisionCell.dataset.field = 'revision'; row.appendChild(revisionCell); var statusCell = document.createElement('td'); statusCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono'; statusCell.textContent = file.status || ''; statusCell.contentEditable = (app.state.mode === 'edit').toString(); statusCell.setAttribute('role', 'textbox'); statusCell.setAttribute('aria-multiline', 'false'); statusCell.setAttribute('tabindex', '0'); statusCell.dataset.index = String(index); statusCell.dataset.field = 'status'; row.appendChild(statusCell); var extCell = document.createElement('td'); extCell.className = 'px-2 py-1 align-top whitespace-nowrap text-center font-mono'; extCell.textContent = (file.extension || '').toLowerCase(); extCell.contentEditable = 'false'; row.appendChild(extCell); var sizeCell = document.createElement('td'); sizeCell.className = 'px-2 py-1 align-top whitespace-nowrap text-right font-mono'; var isUnmatched = !file.fileHandle && !file.sha256; if (isUnmatched) { sizeCell.textContent = '\u2014'; sizeCell.classList.add('text-gray-400'); } else { var fileSizeValue = (file.fileSize != null ? file.fileSize : file.size); sizeCell.textContent = util.formatFileSize(fileSizeValue); } sizeCell.contentEditable = 'false'; row.appendChild(sizeCell); var hashCell = document.createElement('td'); hashCell.className = 'px-2 py-1 align-top font-mono text-[9px] whitespace-normal break-all leading-snug'; if (isUnmatched) { hashCell.textContent = 'pending'; hashCell.classList.add('italic', 'text-gray-400'); } else { hashCell.textContent = util.formatShortFileHash(file.sha256 || ''); } row.appendChild(hashCell); if (file._verifyResult) { row.classList.add('verify-' + file._verifyResult); } tbody.appendChild(row); }); if (!filtered.length) { var placeholderRow = document.createElement('tr'); for (var i = 0; i < 8; i += 1) { var cell = document.createElement('td'); cell.className = 'px-2 py-1 align-top'; placeholderRow.appendChild(cell); } tbody.appendChild(placeholderRow); } if (app.modules.filters && typeof app.modules.filters.refreshPlaceholders === 'function') { app.modules.filters.refreshPlaceholders(); } setupRowDropTargets(); }; // Undo state for row deletion var _lastDeleted = null; var _undoTimer = null; function deleteFileRow(fileIndex) { var file = app.data.files[fileIndex]; if (!file) { return; } var setStatus = app.modules.data && app.modules.data.setStatus; var label = (file.trackingNumber || '') + (file.title ? ' - ' + file.title : ''); // Store for undo _lastDeleted = { file: file, index: fileIndex }; if (_undoTimer) { clearTimeout(_undoTimer); } _undoTimer = setTimeout(function () { _lastDeleted = null; }, 10000); app.data.files.splice(fileIndex, 1); filesModule.updateFilesInJson(app.data.files); filesModule.render(); app.state.apply(); app.markDirty(); if (setStatus) { setStatus('Removed ' + (label || 'row') + ' — click here to undo', 'success'); // Attach one-time undo click handler to status bar var statusEl = document.querySelector('#data-status'); if (statusEl) { statusEl.style.cursor = 'pointer'; var cleanup = function () { statusEl.removeEventListener('click', handler); statusEl.style.cursor = ''; }; var handler = function () { cleanup(); if (!_lastDeleted) { return; } var d = _lastDeleted; _lastDeleted = null; if (_undoTimer) { clearTimeout(_undoTimer); _undoTimer = null; } var idx = Math.min(d.index, app.data.files.length); app.data.files.splice(idx, 0, d.file); filesModule.updateFilesInJson(app.data.files); filesModule.render(); app.state.apply(); app.markDirty(); if (setStatus) { setStatus('Restored ' + (label || 'row'), 'success'); } }; // Clear cursor when undo expires if (_undoTimer) { clearTimeout(_undoTimer); } _undoTimer = setTimeout(function () { _lastDeleted = null; cleanup(); }, 10000); statusEl.addEventListener('click', handler); } } } filesModule.setupTableEditing = function setupTableEditing() { var tbody = document.querySelector('table tbody'); if (!tbody) { return; } // Delegated handler for row delete buttons tbody.addEventListener('click', function (event) { var delBtn = event.target.closest('.row-delete-btn'); if (delBtn && delBtn.dataset.fileIndex !== undefined) { event.preventDefault(); event.stopPropagation(); deleteFileRow(Number(delBtn.dataset.fileIndex)); return; } }); // Click delegation for file preview // Edit mode: always preview (relative paths don't work until HTML is saved) // View mode: preview only when checkbox is checked and file source is loaded tbody.addEventListener('click', function (event) { var link = event.target.closest('a'); if (!link) { return; } var cell = link.closest('td'); if (!cell || !cell.dataset.index) { return; } var file = app.data.files[Number(cell.dataset.index)]; if (!file || !filesModule.hasFileSource || !filesModule.hasFileSource(file)) { return; } var usePreview = (app.state.mode === 'edit') || (filesModule.isPreviewEnabled && filesModule.isPreviewEnabled()) || !!(file.fileHandle); if (usePreview) { event.preventDefault(); event.stopPropagation(); filesModule.showFilePreview(file); } }); tbody.addEventListener('input', function (event) { var target = event.target; if (!(target instanceof HTMLElement)) { return; } var index = target.dataset.index; var field = target.dataset.field; if (index === undefined || !field) { return; } var entry = app.data.files[Number(index)]; if (!entry) { return; } entry[field] = (target.textContent || '').replace(/\r?\n/g, ' ').trim(); filesModule.sortFilesInPlace(app.data.files); filesModule.updateFilesInJson(app.data.files); filesModule.render(); app.markDirty(); if (app.modules.liveDigest && app.modules.liveDigest.schedule) { app.modules.liveDigest.schedule(); } }); tbody.addEventListener('keydown', function (event) { var target = event.target; if (!(target instanceof HTMLElement)) { return; } if (target.isContentEditable && event.key === 'Enter') { event.preventDefault(); } }); tbody.addEventListener('paste', function (event) { var target = event.target; if (!(target instanceof HTMLElement)) { return; } if (!target.isContentEditable) { return; } event.preventDefault(); var text = (event.clipboardData || window.clipboardData).getData('text'); var sanitized = (text || '').replace(/\r?\n/g, ' '); try { document.execCommand('insertText', false, sanitized); } catch (err) { target.textContent = (target.textContent || '') + sanitized; } }); }; filesModule.handleClear = function handleClear() { if (app.state.mode !== 'edit') { return; } if (filesModule.cleanupBlobUrls) { filesModule.cleanupBlobUrls(); } app.data.files = []; app.data.selectedDirHandle = null; filesModule.updateDirectoryIndicator(''); filesModule.updateFilesInJson([]); filesModule.render(); app.state.apply(); app.markDirty(); }; })(window.transmittalApp);