feat: add extension implementation and docs

Add manifest.json, sidepanel components, and scripts.
Include project assets and documentation files.
Remove placeholder blank file.
This commit is contained in:
Kunthawat Greethong
2026-01-06 08:49:28 +07:00
parent 87dd2931fa
commit f490c63632
23 changed files with 4586 additions and 0 deletions

117
scripts/background.js Normal file
View File

@@ -0,0 +1,117 @@
// Background service worker for Auto Cover Generator
// 1. Native Click Behavior
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
const openTabs = new Set();
// 2. Click Handler (Native behavior handles opening, we just manage content)
// Note: chrome.action.onClicked is ignored when openPanelOnActionClick is true.
// 3. Tab State Management
// Monitor all tab changes to ensure correct panel is shown
chrome.runtime.onInstalled.addListener(() => {
initializeAllTabs();
});
chrome.runtime.onStartup.addListener(() => {
initializeAllTabs();
});
async function initializeAllTabs() {
const tabs = await chrome.tabs.query({});
for (const tab of tabs) {
await checkAndSetSidePanel(tab.id);
}
}
// 4. Tab Activation / Isolation Logic
chrome.tabs.onActivated.addListener(async (activeInfo) => {
const tabId = activeInfo.tabId;
await checkAndSetSidePanel(tabId);
});
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' || changeInfo.url) {
await checkAndSetSidePanel(tabId);
}
});
async function checkAndSetSidePanel(tabId) {
try {
const tab = await chrome.tabs.get(tabId);
if (!tab) return; // Tab doesn't exist
// If we don't have permission to see the URL, it's definitely not Gemini (since we have host_permissions for Gemini).
// So undefined tab.url means "Restricted/Other Site".
const isGemini = tab.url && tab.url.startsWith('https://gemini.google.com/');
if (isGemini) {
// Enable and set path for Gemini
await chrome.sidePanel.setOptions({
tabId: tabId,
path: 'sidepanel/panel.html',
enabled: true
});
} else {
// Disable for all other sites - this CLOSES the panel if open
// and ensures it stays closed when returning to the tab (until clicked manually)
await chrome.sidePanel.setOptions({
tabId: tabId,
enabled: false
});
}
} catch (e) {
// Tab might be closed or we heavily failed access
console.log('Cannot access tab or set panel', e);
}
}
chrome.runtime.onInstalled.addListener(() => {
chrome.sidePanel.setOptions({ enabled: false });
});
chrome.tabs.onRemoved.addListener((tabId) => {
openTabs.delete(tabId);
});
// 4. Message Routing
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Forward messages to content script
if (message.action === 'generateCover' || message.action === 'removeObjectFromImage') {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
chrome.tabs.sendMessage(tabs[0].id, message, (response) => {
sendResponse(response);
});
}
});
return true;
}
if (message.action === 'downloadImage') {
const url = message.url;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `gemini_gen_${timestamp}.png`;
chrome.downloads.download({
url: url,
filename: filename,
conflictAction: 'uniquify'
}, (downloadId) => {
if (chrome.runtime.lastError) {
console.error('Download failed:', chrome.runtime.lastError);
} else {
console.log('Download started, ID:', downloadId);
}
});
return;
}
if (message.action === 'generationError') {
chrome.runtime.sendMessage(message).catch(() => { });
}
});
console.log('Auto Cover Generator: Background Service Ready');