Fix Unexpected Keyword in MediaManager and Add Build Linting
- Fix corrupted line in src/MediaManager.html causing syntax error. - Add ESLint integration to build process to prevent future syntax errors. - Create .eslintrc.js with TypeScript and HTML support. - Relax strict lint rules to accommodate existing codebase.
This commit is contained in:
@ -164,25 +164,24 @@
|
||||
/* Processing State */
|
||||
.media-item.processing-card {
|
||||
background-color: #334155 !important;
|
||||
position: relative; /* Ensure absolute children are contained */
|
||||
/* Removed flex centering to let image stretch */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-item.processing-card .media-content {
|
||||
display: block !important;
|
||||
opacity: 0.8; /* Lighter overlay (was 0.4) */
|
||||
filter: grayscale(30%); /* Less grey (was 80%) */
|
||||
opacity: 0.8;
|
||||
filter: grayscale(30%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain; /* Ensure it fills */
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.processing-icon {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
font-size: 20px; /* Smaller */
|
||||
z-index: 20; /* Above badges */
|
||||
font-size: 20px;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -383,60 +382,67 @@
|
||||
|
||||
/* Log Card Styles */
|
||||
.log-card {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
.log-header {
|
||||
padding: 8px 12px;
|
||||
background: #f8fafc; /* Slightly darker than surface */
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
padding: 8px 12px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
padding: 12px;
|
||||
max-height: 16px; /* ~1 line */
|
||||
overflow-y: auto;
|
||||
transition: max-height 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: var(--surface);
|
||||
padding: 12px;
|
||||
/* ~1 line */
|
||||
max-height: 16px;
|
||||
overflow-y: auto;
|
||||
transition: max-height 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.log-card.expanded .log-content {
|
||||
max-height: 300px; /* ~20 lines */
|
||||
/* ~20 lines */
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
line-height: 1.4;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
padding-bottom: 2px;
|
||||
line-height: 1.4;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.log-entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.log-entry:last-child { border-bottom: none; }
|
||||
|
||||
/* Scrollbar for log */
|
||||
.log-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.log-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.log-content::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
@ -460,42 +466,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Options Card -->
|
||||
<div class="card">
|
||||
<div class="header" style="margin-bottom: 12px;">
|
||||
<h3 style="margin:0; font-size:14px; color:var(--text);">Add Photos/Videos from...</h3>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; width: 100%;">
|
||||
<button onclick="controller.openPicker()" class="btn btn-secondary"
|
||||
style="flex: 1; font-size: 13px; white-space: nowrap;">
|
||||
Google Drive
|
||||
</button>
|
||||
<button onclick="controller.startPhotoSession()" class="btn btn-secondary"
|
||||
style="flex: 1; font-size: 13px; white-space: nowrap;">
|
||||
Google Photos
|
||||
</button>
|
||||
<button onclick="document.getElementById('file-input').click()" class="btn btn-secondary"
|
||||
style="flex: 1; font-size: 13px;">
|
||||
Your Computer
|
||||
</button>
|
||||
</div>
|
||||
<input type="file" id="file-input" multiple style="display:none" onchange="controller.handleFiles(this.files)">
|
||||
</div>
|
||||
|
||||
<!-- Photos Session UI -->
|
||||
<div id="photos-session-ui"
|
||||
style="display:none; margin-top:12px; padding:12px; background:#f0f9ff; border-radius:8px; border:1px solid #bae6fd;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
|
||||
<span style="font-weight:600; font-size:12px; color:#0369a1;">Photo Picker Session</span>
|
||||
<button onclick="ui.closePhotoSession()"
|
||||
style="background:none; border:none; color:#0369a1; cursor:pointer; font-size:16px;">×</button>
|
||||
</div>
|
||||
<a id="photos-session-link" href="#" target="_blank" class="btn"
|
||||
style="background:#0ea5e9; text-decoration:none; margin-bottom:8px;">
|
||||
Open Google Photos ↗
|
||||
</a>
|
||||
<div id="photos-session-status" style="font-size:11px; color:#64748b; text-align:center;">Initializing...</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -538,16 +509,54 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Permanent Log Card -->
|
||||
<div id="log-card" class="log-card">
|
||||
<div class="log-header" onclick="ui.toggleLogExpand()">
|
||||
<span style="font-weight:600;">Activity Log</span>
|
||||
<span id="log-toggle-icon">▲</span>
|
||||
</div>
|
||||
<div id="status-log-container" class="log-content">
|
||||
<div class="log-entry" style="color: #94a3b8;">Ready.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="upload-section" style="display:none;">
|
||||
<!-- Upload Options Card -->
|
||||
<div class="card">
|
||||
<div class="header" style="margin-bottom: 12px;">
|
||||
<h3 style="margin:0; font-size:14px; color:var(--text);">Add Photos/Videos from...</h3>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; width: 100%;">
|
||||
<button id="btn-upload-drive" onclick="controller.openPicker()" class="btn btn-secondary"
|
||||
style="flex: 1; font-size: 13px; white-space: nowrap;">
|
||||
Google Drive
|
||||
</button>
|
||||
<button id="btn-upload-photos" onclick="controller.startPhotoSession()" class="btn btn-secondary"
|
||||
style="flex: 1; font-size: 13px; white-space: nowrap;">
|
||||
Google Photos
|
||||
</button>
|
||||
<button id="btn-upload-computer" onclick="document.getElementById('file-input').click()"
|
||||
class="btn btn-secondary" style="flex: 1; font-size: 13px;">
|
||||
Your Computer
|
||||
</button>
|
||||
</div>
|
||||
<input type="file" id="file-input" multiple style="display:none" onchange="controller.handleFiles(this.files)">
|
||||
</div>
|
||||
|
||||
<!-- Photos Session UI -->
|
||||
<div id="photos-session-ui"
|
||||
style="display:none; margin-top:12px; padding:12px; background:#f0f9ff; border-radius:8px; border:1px solid #bae6fd;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
|
||||
<span style="font-weight:600; font-size:12px; color:#0369a1;">Photo Picker Session</span>
|
||||
<button onclick="ui.closePhotoSession()"
|
||||
style="background:none; border:none; color:#0369a1; cursor:pointer; font-size:16px;">×</button>
|
||||
</div>
|
||||
<a id="photos-session-link" href="#" target="_blank" class="btn"
|
||||
style="background:#0ea5e9; text-decoration:none; margin-bottom:8px;">
|
||||
Open Google Photos ↗
|
||||
</a>
|
||||
<div id="photos-session-status" style="font-size:11px; color:#64748b; text-align:center;">Initializing...</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Permanent Log Card -->
|
||||
<div id="log-card" class="log-card">
|
||||
<div class="log-header" onclick="ui.toggleLogExpand()">
|
||||
<span style="font-weight:600;">Activity Log</span>
|
||||
<span id="log-toggle-icon">▲</span>
|
||||
</div>
|
||||
<div id="status-log-container" class="log-content">
|
||||
<div class="log-entry" style="color: #94a3b8;">Ready.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -793,10 +802,10 @@
|
||||
if (this.shopifyUrl) this.linksContainer.innerHTML += '<a href="' + this.shopifyUrl + '" target="_blank" style="color:var(--primary); text-decoration:none; margin-left:8px;">Shopify ↗</a>';
|
||||
};
|
||||
|
||||
UI.prototype.toggleLogExpand = function () {
|
||||
this.logCard.classList.toggle('expanded');
|
||||
var icon = this.logCard.querySelector('#log-toggle-icon');
|
||||
icon.innerText = this.logCard.classList.contains('expanded') ? '▼' : '▲';
|
||||
UI.prototype.toggleLogExpand = function () {
|
||||
this.logCard.classList.toggle('expanded');
|
||||
var icon = this.logCard.querySelector('#log-toggle-icon');
|
||||
icon.innerText = this.logCard.classList.contains('expanded') ? '▼' : '▲';
|
||||
};
|
||||
|
||||
UI.prototype.updateSku = function (sku, title) {
|
||||
@ -1326,6 +1335,8 @@
|
||||
},
|
||||
|
||||
handleFiles(fileList) {
|
||||
if (!fileList || fileList.length === 0) return;
|
||||
this.setPickerState(true);
|
||||
Array.from(fileList).forEach(file => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
@ -1334,6 +1345,12 @@
|
||||
google.script.run
|
||||
.withSuccessHandler(() => {
|
||||
this.loadMedia();
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.withFailureHandler(err => {
|
||||
console.error(err);
|
||||
alert("Upload failed: " + err.message);
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.saveFileToDrive(state.sku, file.name, file.type, data);
|
||||
};
|
||||
@ -1341,27 +1358,57 @@
|
||||
});
|
||||
},
|
||||
|
||||
// --- Picker ---
|
||||
// --- Picker ---
|
||||
setPickerState(isActive) {
|
||||
const btnDrive = document.getElementById('btn-upload-drive');
|
||||
const btnPhotos = document.getElementById('btn-upload-photos');
|
||||
const btnComp = document.getElementById('btn-upload-computer');
|
||||
|
||||
[btnDrive, btnPhotos, btnComp].forEach(btn => {
|
||||
if (btn) {
|
||||
btn.disabled = isActive;
|
||||
btn.style.opacity = isActive ? '0.5' : '1';
|
||||
btn.style.cursor = isActive ? 'not-allowed' : 'pointer';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
openPicker() {
|
||||
if (!pickerApiLoaded) return alert("API Loading...");
|
||||
google.script.run.withSuccessHandler(c => createPicker(c)).getPickerConfig();
|
||||
this.setPickerState(true);
|
||||
google.script.run
|
||||
.withSuccessHandler(c => createPicker(c))
|
||||
.withFailureHandler(e => {
|
||||
alert("Failed to load picker: " + e.message);
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.getPickerConfig();
|
||||
},
|
||||
|
||||
importFromPicker(fileId, mime, name, url) {
|
||||
google.script.run
|
||||
.withSuccessHandler(() => this.loadMedia())
|
||||
.withSuccessHandler(() => {
|
||||
this.loadMedia();
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.withFailureHandler(e => {
|
||||
alert("Import failed: " + e.message);
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.importFromPicker(state.sku, fileId, mime, name, url);
|
||||
},
|
||||
|
||||
// --- Photos (Popup Flow) ---
|
||||
startPhotoSession() {
|
||||
this.setPickerState(true);
|
||||
ui.updatePhotoStatus("Starting session...");
|
||||
google.script.run
|
||||
.withSuccessHandler(session => {
|
||||
ui.showPhotoSession(session.pickerUri);
|
||||
this.pollPhotoSession(session.id);
|
||||
})
|
||||
.withFailureHandler(e => {
|
||||
ui.updatePhotoStatus("Failed: " + e.message);
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.createPhotoSession();
|
||||
},
|
||||
|
||||
@ -1374,13 +1421,18 @@
|
||||
if (res.status === 'complete') {
|
||||
processing = true;
|
||||
ui.updatePhotoStatus("Importing photos...");
|
||||
controller.processPhotoItems(res.mediaItems);
|
||||
this.processPhotoItems(res.mediaItems);
|
||||
} else if (res.status === 'error') {
|
||||
ui.updatePhotoStatus("Error: " + res.message);
|
||||
this.setPickerState(false);
|
||||
} else {
|
||||
setTimeout(check, 2000);
|
||||
}
|
||||
})
|
||||
.withFailureHandler(e => {
|
||||
ui.updatePhotoStatus("Error polling: " + e.message);
|
||||
this.setPickerState(false);
|
||||
})
|
||||
.checkPhotoSession(sessionId);
|
||||
};
|
||||
check();
|
||||
@ -1412,9 +1464,18 @@
|
||||
if (done === items.length) {
|
||||
ui.updatePhotoStatus("Done!");
|
||||
controller.loadMedia();
|
||||
this.setPickerState(false);
|
||||
setTimeout(() => ui.closePhotoSession(), 2000);
|
||||
}
|
||||
})
|
||||
.withFailureHandler(e => {
|
||||
console.error("Import failed", e);
|
||||
// If last one
|
||||
done++;
|
||||
if (done === items.length) {
|
||||
this.setPickerState(false);
|
||||
}
|
||||
})
|
||||
.importFromPicker(state.sku, null, mimeType, filename, url);
|
||||
});
|
||||
},
|
||||
@ -1523,11 +1584,11 @@
|
||||
document.getElementById('btn-match-confirm').innerText = "Linking...";
|
||||
document.getElementById('btn-match-skip').disabled = true;
|
||||
|
||||
// ui.logStatus('link', 'Linking ' + match.drive.filename + '...', 'info');
|
||||
ui.logStatus('link', 'Linking ' + match.drive.filename + '...', 'info');
|
||||
|
||||
google.script.run
|
||||
.withSuccessHandler(function () {
|
||||
// ui.logStatus('link', 'Linked ' + match.drive.filename, 'success');
|
||||
ui.logStatus('link', 'Linked ' + match.drive.filename, 'success');
|
||||
_this.nextMatch();
|
||||
})
|
||||
.withFailureHandler(function (e) {
|
||||
@ -1579,6 +1640,7 @@
|
||||
showGallery() {
|
||||
document.getElementById('loading-ui').style.display = 'none';
|
||||
document.getElementById('main-ui').style.display = 'block';
|
||||
document.getElementById('upload-section').style.display = 'block';
|
||||
ui.logStatus('done', 'Finished loading.', 'success');
|
||||
// setTimeout(function () { ui.toggleLog(false); }, 1000); // Removed auto-hide
|
||||
|
||||
@ -1692,6 +1754,9 @@
|
||||
|
||||
// Drive File (Always, since we removed Photos view)
|
||||
controller.importFromPicker(doc.id, doc.mimeType, doc.name, null);
|
||||
} else if (data.action == google.picker.Action.CANCEL) {
|
||||
console.log("Picker cancelled");
|
||||
controller.setPickerState(false);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
||||
Reference in New Issue
Block a user