feat(media-manager): link media filenames to preview pages in match wizard

- Updates the 'Link Media' wizard and confirmation modal to make filenames clickable.
- Links Drive files to their view page.
- Links Shopify files to the Admin Content > Files page, derived from the product admin URL.
- Applies primary theme color to links for better visibility.
This commit is contained in:
Ben Miller
2026-01-01 05:37:53 -07:00
parent 09995d0d05
commit 8d780d2fcb

View File

@ -525,46 +525,54 @@
/* Combined Card Styles */ /* Combined Card Styles */
.combined-item { .combined-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: #f0f9ff; /* Light blue tint */ background: #f0f9ff;
border: 2px solid var(--primary); /* Light blue tint */
border: 2px solid var(--primary);
} }
.combined-images { .combined-images {
display: flex; display: flex;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }
.combined-part { .combined-part {
flex: 1; flex: 1;
position: relative; position: relative;
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
} }
.combined-part:last-child { .combined-part:last-child {
border-right: none; border-right: none;
} }
.combined-part .media-content { .combined-part .media-content {
aspect-ratio: auto; /* Allow filling height */ aspect-ratio: auto;
height: 100px; /* Allow filling height */
height: 100px;
} }
.unlink-btn { .unlink-btn {
width: 100%; width: 100%;
background: white; background: white;
border: none; border: none;
padding: 6px; padding: 6px;
color: var(--danger); color: var(--danger);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 6px; gap: 6px;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
transition: background 0.1s; transition: background 0.1s;
} }
.unlink-btn:hover { .unlink-btn:hover {
background: #fee2e2; background: #fee2e2;
} }
</style> </style>
</head> </head>
@ -830,54 +838,54 @@
this.checkDirty(); this.checkDirty();
}; };
MediaState.prototype.toggleSelection = function (id) { MediaState.prototype.toggleSelection = function (id) {
var item = this.items.find(function (i) { return i.id === id; }); var item = this.items.find(function (i) { return i.id === id; });
if (!item) return; if (!item) return;
var isSelected = this.selectedIds.has(id); var isSelected = this.selectedIds.has(id);
var affectedIds = [id]; var affectedIds = [id];
if (isSelected) { if (isSelected) {
this.selectedIds.delete(id); this.selectedIds.delete(id);
} else { } else {
// Enforce one-pair rule: Max one Drive, one Shopify // Enforce one-pair rule: Max one Drive, one Shopify
var isDrive = (item.source === 'drive_only'); var isDrive = (item.source === 'drive_only');
var isShopify = (item.source === 'shopify_only'); var isShopify = (item.source === 'shopify_only');
if (isDrive) { if (isDrive) {
// Clear other Drive selections // Clear other Drive selections
var _this = this; var _this = this;
this.items.forEach(function (i) { this.items.forEach(function (i) {
// Simplified clearing logic // Simplified clearing logic
if (i.source === 'drive_only' && _this.selectedIds.has(i.id) && i.id !== id) { if (i.source === 'drive_only' && _this.selectedIds.has(i.id) && i.id !== id) {
_this.selectedIds.delete(i.id); _this.selectedIds.delete(i.id);
affectedIds.push(i.id); affectedIds.push(i.id);
} }
}); });
} else if (isShopify) { } else if (isShopify) {
// Clear other Shopify selections // Clear other Shopify selections
var _this = this; var _this = this;
this.items.forEach(function (i) { this.items.forEach(function (i) {
if (i.source === 'shopify_only' && _this.selectedIds.has(i.id) && i.id !== id) { if (i.source === 'shopify_only' && _this.selectedIds.has(i.id) && i.id !== id) {
_this.selectedIds.delete(i.id); _this.selectedIds.delete(i.id);
affectedIds.push(i.id); affectedIds.push(i.id);
} }
}); });
} }
this.selectedIds.add(id); this.selectedIds.add(id);
} }
// Targeted updates // Targeted updates
affectedIds.forEach(function (aid) { ui.updateCardState(aid); }); affectedIds.forEach(function (aid) { ui.updateCardState(aid); });
ui.updateLinkButtonState(); ui.updateLinkButtonState();
}; };
MediaState.prototype.clearSelection = function () { MediaState.prototype.clearSelection = function () {
var affectedIds = Array.from(this.selectedIds); var affectedIds = Array.from(this.selectedIds);
this.selectedIds.clear(); this.selectedIds.clear();
affectedIds.forEach(function (aid) { ui.updateCardState(aid); }); affectedIds.forEach(function (aid) { ui.updateCardState(aid); });
ui.updateLinkButtonState(); ui.updateLinkButtonState();
}; };
MediaState.prototype.addItem = function (item) { MediaState.prototype.addItem = function (item) {
this.items.push(item); this.items.push(item);
@ -885,12 +893,12 @@
this.checkDirty(); this.checkDirty();
}; };
MediaState.prototype.deleteItem = function (id) { MediaState.prototype.deleteItem = function (id) {
var item = this.items.find(function (i) { return i.id === id; }); var item = this.items.find(function (i) { return i.id === id; });
if (!item) { if (!item) {
console.warn("[MediaState] Item not found for deletion:", id); console.warn("[MediaState] Item not found for deletion:", id);
return; return;
} }
if (item.source === 'new') { if (item.source === 'new') {
var index = this.items.indexOf(item); var index = this.items.indexOf(item);
if (index !== -1) this.items.splice(index, 1); if (index !== -1) this.items.splice(index, 1);
@ -906,13 +914,13 @@
// Handled by Sortable // Handled by Sortable
}; };
MediaState.prototype.unlinkMedia = function (driveId, shopifyId) { MediaState.prototype.unlinkMedia = function (driveId, shopifyId) {
this.tentativeLinks = this.tentativeLinks.filter(function (l) { this.tentativeLinks = this.tentativeLinks.filter(function (l) {
return !(l.driveId === driveId && l.shopifyId === shopifyId); return !(l.driveId === driveId && l.shopifyId === shopifyId);
}); });
ui.render(this.items); ui.render(this.items);
this.checkDirty(); this.checkDirty();
}; };
MediaState.prototype.checkDirty = function () { MediaState.prototype.checkDirty = function () {
var plan = this.calculateDiff(); var plan = this.calculateDiff();
@ -1010,8 +1018,8 @@
}; };
var state = new MediaState(); var state = new MediaState();
state.sku = "<?!= initialSku ?>"; state.sku = "<?!= initialSku ?>";
state.title = "<?!= initialTitle ?>"; state.title = "<?!= initialTitle ?>";
window.state = state; window.state = state;
// --- ES5 Refactor: UI --- // --- ES5 Refactor: UI ---
@ -1131,21 +1139,21 @@
btn.disabled = false; btn.disabled = false;
}; };
UI.prototype.updateLinkButtonState = function () { UI.prototype.updateLinkButtonState = function () {
var btnLink = document.getElementById('btn-link-selected'); var btnLink = document.getElementById('btn-link-selected');
if (btnLink) { if (btnLink) {
var items = state.items || []; var items = state.items || [];
var selectedItems = items.filter(function (i) { return state.selectedIds.has(i.id); }); var selectedItems = items.filter(function (i) { return state.selectedIds.has(i.id); });
var hasDrive = selectedItems.some(function (i) { return i.source === 'drive_only'; }); var hasDrive = selectedItems.some(function (i) { return i.source === 'drive_only'; });
var hasShopify = selectedItems.some(function (i) { return i.source === 'shopify_only'; }); var hasShopify = selectedItems.some(function (i) { return i.source === 'shopify_only'; });
if (hasDrive && hasShopify) { if (hasDrive && hasShopify) {
btnLink.style.display = 'block'; btnLink.style.display = 'block';
} else { } else {
btnLink.style.display = 'none'; btnLink.style.display = 'none';
}
} }
}; }
};
UI.prototype.render = function (items) { UI.prototype.render = function (items) {
this.grid.innerHTML = ''; this.grid.innerHTML = '';
@ -1297,23 +1305,23 @@
} }
}; };
UI.prototype.setSavingState = function (isSaving) { UI.prototype.setSavingState = function (isSaving) {
if (isSaving) { if (isSaving) {
this.grid.classList.add('grid-disabled'); this.grid.classList.add('grid-disabled');
if (this.sortable) this.sortable.option('disabled', true); if (this.sortable) this.sortable.option('disabled', true);
// Disable action buttons explicitly if needed (pointer-events handles most, but keyboard nav?) // Disable action buttons explicitly if needed (pointer-events handles most, but keyboard nav?)
var btns = this.grid.querySelectorAll('button'); var btns = this.grid.querySelectorAll('button');
btns.forEach(function (b) { b.disabled = true; }); btns.forEach(function (b) { b.disabled = true; });
} else { } else {
this.grid.classList.remove('grid-disabled'); this.grid.classList.remove('grid-disabled');
if (this.sortable) this.sortable.option('disabled', false); if (this.sortable) this.sortable.option('disabled', false);
var btns = this.grid.querySelectorAll('button'); var btns = this.grid.querySelectorAll('button');
btns.forEach(function (b) { b.disabled = false; }); btns.forEach(function (b) { b.disabled = false; });
} }
}; };
UI.prototype.logStatus = function (step, message, type) { UI.prototype.logStatus = function (step, message, type) {
if (!type) type = 'info'; if (!type) type = 'info';
@ -1422,7 +1430,7 @@
centerIcon + centerIcon +
'<div class="media-overlay">' + '<div class="media-overlay">' +
'<button class="icon-btn btn-view" onclick="ui.openPreview(\'' + item.id + '\')" title="View">👁️</button>' + '<button class="icon-btn btn-view" onclick="ui.openPreview(\'' + item.id + '\')" title="View">👁️</button>' +
linkSelectionBtn + linkSelectionBtn +
actionBtn + actionBtn +
'</div>'; '</div>';
@ -1477,84 +1485,84 @@
return div; return div;
}; };
UI.prototype.updateCardState = function (id) { UI.prototype.updateCardState = function (id) {
var item = state.items.find(function (i) { return i.id === id; }); var item = state.items.find(function (i) { return i.id === id; });
var el = this.grid.querySelector('[data-id="' + id + '"]'); var el = this.grid.querySelector('[data-id="' + id + '"]');
if (!item || !el) return; if (!item || !el) return;
var isSelected = state.selectedIds.has(item.id); var isSelected = state.selectedIds.has(item.id);
// 1. Update Container Class // 1. Update Container Class
// Combined Items don't use this update path (they are re-rendered if unlinked) // Combined Items don't use this update path (they are re-rendered if unlinked)
el.className = 'media-item ' + (item._deleted ? 'deleted-item' : '') + (isSelected ? ' selected' : ''); el.className = 'media-item ' + (item._deleted ? 'deleted-item' : '') + (isSelected ? ' selected' : '');
if (item.isProcessing) el.className += ' processing-card'; if (item.isProcessing) el.className += ' processing-card';
// 2. Update Badge // 2. Update Badge
var badgeEl = el.querySelector('.badge'); var badgeEl = el.querySelector('.badge');
if (badgeEl) { if (badgeEl) {
if (!item._deleted) { if (!item._deleted) {
if (item.source === 'synced') { if (item.source === 'synced') {
badgeEl.innerText = 'Synced'; badgeEl.innerText = 'Synced';
badgeEl.title = 'Synced'; badgeEl.title = 'Synced';
badgeEl.style.background = '#dcfce7'; badgeEl.style.background = '#dcfce7';
badgeEl.style.color = '#166534'; badgeEl.style.color = '#166534';
} else if (item.source === 'drive_only') { } else if (item.source === 'drive_only') {
badgeEl.innerText = 'Drive'; badgeEl.innerText = 'Drive';
badgeEl.title = 'Drive Only'; badgeEl.title = 'Drive Only';
badgeEl.style.background = '#dbeafe'; badgeEl.style.background = '#dbeafe';
badgeEl.style.color = '#1e40af'; badgeEl.style.color = '#1e40af';
} else if (item.source === 'shopify_only') { } else if (item.source === 'shopify_only') {
badgeEl.innerText = 'Shopify'; badgeEl.innerText = 'Shopify';
badgeEl.title = 'Shopify Only'; badgeEl.title = 'Shopify Only';
badgeEl.style.background = '#fce7f3'; badgeEl.style.background = '#fce7f3';
badgeEl.style.color = '#9d174d'; badgeEl.style.color = '#9d174d';
} }
} else { } else {
badgeEl.innerText = 'Deleted'; badgeEl.innerText = 'Deleted';
badgeEl.title = ''; badgeEl.title = '';
badgeEl.style.background = '#fee2e2'; badgeEl.style.background = '#fee2e2';
badgeEl.style.color = '#991b1b'; badgeEl.style.color = '#991b1b';
} }
} }
// 3. Update Overlay Buttons // 3. Update Overlay Buttons
var overlay = el.querySelector('.media-overlay'); var overlay = el.querySelector('.media-overlay');
if (overlay) { if (overlay) {
// Remove existing link button if it exists // Remove existing link button if it exists
var oldLinkBtn = el.querySelector('[id="link-btn-' + id + '"]'); var oldLinkBtn = el.querySelector('[id="link-btn-' + id + '"]');
if (oldLinkBtn) oldLinkBtn.remove(); if (oldLinkBtn) oldLinkBtn.remove();
// Add link button back if NOT deleted // Add link button back if NOT deleted
if (!item._deleted && (item.source === 'drive_only' || item.source === 'shopify_only')) { if (!item._deleted && (item.source === 'drive_only' || item.source === 'shopify_only')) {
var linkHtml = '<button id="link-btn-' + item.id + '" class="icon-btn' + (isSelected ? ' active' : '') + '" onclick="event.stopPropagation(); state.toggleSelection(\'' + item.id + '\')" title="Select for linking">🔗</button>'; var linkHtml = '<button id="link-btn-' + item.id + '" class="icon-btn' + (isSelected ? ' active' : '') + '" onclick="event.stopPropagation(); state.toggleSelection(\'' + item.id + '\')" title="Select for linking">🔗</button>';
var viewBtn = overlay.querySelector('.btn-view'); var viewBtn = overlay.querySelector('.btn-view');
if (viewBtn) { if (viewBtn) {
viewBtn.insertAdjacentHTML('afterend', linkHtml); viewBtn.insertAdjacentHTML('afterend', linkHtml);
} else { } else {
overlay.insertAdjacentHTML('afterbegin', linkHtml); overlay.insertAdjacentHTML('afterbegin', linkHtml);
} }
} }
// Update Delete/Restore button // Update Delete/Restore button
var actionBtn = overlay.querySelector('.btn-delete') || overlay.querySelector('[title="Restore"]'); var actionBtn = overlay.querySelector('.btn-delete') || overlay.querySelector('[title="Restore"]');
if (actionBtn) { if (actionBtn) {
if (item._deleted) { if (item._deleted) {
actionBtn.className = 'icon-btn'; actionBtn.className = 'icon-btn';
actionBtn.innerHTML = '↩️'; actionBtn.innerHTML = '↩️';
actionBtn.title = 'Restore'; actionBtn.title = 'Restore';
} else { } else {
actionBtn.className = 'icon-btn btn-delete'; actionBtn.className = 'icon-btn btn-delete';
actionBtn.innerHTML = '🗑️'; actionBtn.innerHTML = '🗑️';
actionBtn.title = 'Delete'; actionBtn.title = 'Delete';
} }
} }
} }
// 4. Update global item count // 4. Update global item count
var activeCount = state.items.filter(function (i) { return !i._deleted; }).length; var activeCount = state.items.filter(function (i) { return !i._deleted; }).length;
var countEl = document.getElementById('item-count'); var countEl = document.getElementById('item-count');
if (countEl) countEl.innerText = '(' + activeCount + ')'; if (countEl) countEl.innerText = '(' + activeCount + ')';
}; };
UI.prototype.openPreview = function (id) { UI.prototype.openPreview = function (id) {
var item = state.items.find(function (i) { return i.id === id; }); var item = state.items.find(function (i) { return i.id === id; });
@ -1790,15 +1798,15 @@
if (manualMatches.length > 0) { if (manualMatches.length > 0) {
this.matches = manualMatches; this.matches = manualMatches;
this.currentMatchIndex = 0; this.currentMatchIndex = 0;
this.postMatchAction = 'save'; this.postMatchAction = 'save';
ui.logStatus('info', 'Processing ' + manualMatches.length + ' pending links before saving...', 'info'); ui.logStatus('info', 'Processing ' + manualMatches.length + ' pending links before saving...', 'info');
// Update Modal Text // Update Modal Text
document.getElementById('match-modal-title').innerText = "Confirm Manual Links"; document.getElementById('match-modal-title').innerText = "Confirm Manual Links";
document.getElementById('match-modal-text').innerText = "Please confirm the links you selected."; document.getElementById('match-modal-text').innerText = "Please confirm the links you selected.";
this.startMatching(); this.startMatching();
return; return;
} }
} }
@ -2199,8 +2207,20 @@
dImg.src = blank; dImg.src = blank;
sImg.src = blank; sImg.src = blank;
document.getElementById('match-drive-name').innerText = match.drive.filename; // Link to Drive Preview
document.getElementById('match-shopify-name').innerText = match.shopify.filename; var driveLink = "https://drive.google.com/file/d/" + match.drive.id + "/view";
document.getElementById('match-drive-name').innerHTML = '<a href="' + driveLink + '" target="_blank" style="color:var(--primary); text-decoration:underline;">' + match.drive.filename + '</a>';
// Link to Shopify Admin Media
var shopifyLink = match.shopify.contentUrl || "#";
if (ui.shopifyUrl) {
// Pattern: .../admin/content/files/{id}?selectedView=all
// We derive the base admin URL from the product URL
var adminBase = ui.shopifyUrl.split('/products/')[0];
var mediaId = match.shopify.id.split('/').pop();
shopifyLink = adminBase + "/content/files/" + mediaId + "?selectedView=all";
}
document.getElementById('match-shopify-name').innerHTML = '<a href="' + shopifyLink + '" target="_blank" style="color:var(--primary); text-decoration:underline;">' + match.shopify.filename + '</a>';
document.getElementById('match-index').innerText = this.currentMatchIndex + 1; document.getElementById('match-index').innerText = this.currentMatchIndex + 1;
document.getElementById('match-total').innerText = this.matches.length; document.getElementById('match-total').innerText = this.matches.length;