diff --git a/src/manifest.json b/src/manifest.json index 69eaa16..5565687 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -11,8 +11,11 @@ "96": "icons/icon.png", "48": "icons/icon.png" }, - "background": { - "service_worker": "scripts/background.js" + "browser_specific_settings": { + "gecko": { + "id": "scrum-helper@fossasia.org", + "strict_min_version": "109.0" + } }, "content_scripts": [ { @@ -25,7 +28,12 @@ "*://outlook.office.com/*", "*://mail.yahoo.com/*" ], - "js": ["scripts/jquery-3.2.1.min.js", "scripts/emailClientAdapter.js", "scripts/scrumHelper.js"] + "js": [ + "scripts/browser-polyfill.js", + "scripts/jquery-3.2.1.min.js", + "scripts/emailClientAdapter.js", + "scripts/scrumHelper.js" + ] } ], diff --git a/src/popup.html b/src/popup.html index 522ee84..5f8e5cf 100644 --- a/src/popup.html +++ b/src/popup.html @@ -260,6 +260,7 @@

Note:

+ diff --git a/src/scripts/background.js b/src/scripts/background.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/scripts/browser-polyfill.js b/src/scripts/browser-polyfill.js new file mode 100644 index 0000000..688bd56 --- /dev/null +++ b/src/scripts/browser-polyfill.js @@ -0,0 +1,4 @@ +// scripts/browser-polyfill.js +if (typeof browser === "undefined") { + window.browser = typeof chrome !== "undefined" ? chrome : {}; +} \ No newline at end of file diff --git a/src/scripts/main.js b/src/scripts/main.js index 383e6e2..bf23580 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -12,7 +12,7 @@ let userReasonElement = document.getElementById('userReason'); let showCommitsElement = document.getElementById('showCommits'); function handleBodyOnLoad() { - chrome.storage.local.get( + browser.storage.local.get( [ 'githubUsername', 'projectName', @@ -102,7 +102,7 @@ document.getElementById('refreshCache').addEventListener('click', async (e) => { }); // Reload the active tab to re-inject content - chrome.tabs.reload(tabs[0].id); + browser.tabs.reload(tabs[0].id); Materialize.toast({ html: 'Data refreshed successfully!', classes: 'green' }); } catch (err) { @@ -116,16 +116,16 @@ document.getElementById('refreshCache').addEventListener('click', async (e) => { }); function handleEnableChange() { - let value = enableToggleElement.checked; - chrome.storage.local.set({ enableToggle: value }); + var value = enableToggleElement.checked; + browser.storage.local.set({ enableToggle: value }); } function handleStartingDateChange() { - let value = startingDateElement.value; - chrome.storage.local.set({ startingDate: value }); + var value = startingDateElement.value; + browser.storage.local.set({ startingDate: value }); } function handleEndingDateChange() { - let value = endingDateElement.value; - chrome.storage.local.set({ endingDate: value }); + var value = endingDateElement.value; + browser.storage.local.set({ endingDate: value }); } function handleLastWeekContributionChange() { let value = lastWeekContributionElement.checked; @@ -145,8 +145,7 @@ function handleLastWeekContributionChange() { labelElement.classList.add("unselectedLabel"); labelElement.classList.remove("selectedLabel"); } - - chrome.storage.local.set({ lastWeekContribution: value }); + browser.storage.local.set({ lastWeekContribution: value }); } function handleYesterdayContributionChange() { @@ -168,7 +167,7 @@ function handleYesterdayContributionChange() { labelElement.classList.add("unselectedLabel"); labelElement.classList.remove("selectedLabel"); } - chrome.storage.local.set({ yesterdayContribution: value }); + browser.storage.local.set({ yesterdayContribution: value }); } function getLastWeek() { @@ -215,20 +214,20 @@ function getToday() { } function handleGithubUsernameChange() { - let value = githubUsernameElement.value; - chrome.storage.local.set({ githubUsername: value }); + var value = githubUsernameElement.value; + browser.storage.local.set({ githubUsername: value }); } function handleGithubTokenChange() { let value = githubTokenElement.value; - chrome.storage.local.set({ githubToken: value }); + browser.storage.local.set({ githubToken: value }); } function handleProjectNameChange() { - let value = projectNameElement.value; - chrome.storage.local.set({ projectName: value }); + var value = projectNameElement.value; + browser.storage.local.set({ projectName: value }); } function handleCacheInputChange() { let value = cacheInputElement.value; - chrome.storage.local.set({ cacheInput: value }); + browser.storage.local.set({ cacheInput: value }); } function handleOpenLabelChange() { let value = showOpenLabelElement.checked; @@ -242,17 +241,17 @@ function handleOpenLabelChange() { labelElement.classList.remove("selectedLabel"); } - chrome.storage.local.set({ showOpenLabel: value }); + browser.storage.local.set({ showOpenLabel: value }); } function handleUserReasonChange() { - let value = userReasonElement.value; - chrome.storage.local.set({ userReason: value }); + var value = userReasonElement.value; + browser.storage.local.set({ userReason: value }); } function handleShowCommitsChange() { let value = showCommitsElement.checked; - chrome.storage.local.set({ showCommits: value }); + browser.storage.local.set({ showCommits: value }); } enableToggleElement.addEventListener('change', handleEnableChange); @@ -267,4 +266,4 @@ lastWeekContributionElement.addEventListener('change', handleLastWeekContributio yesterdayContributionElement.addEventListener('change', handleYesterdayContributionChange); showOpenLabelElement.addEventListener('change', handleOpenLabelChange); userReasonElement.addEventListener('keyup', handleUserReasonChange); -document.addEventListener('DOMContentLoaded', handleBodyOnLoad); \ No newline at end of file +document.addEventListener('DOMContentLoaded', handleBodyOnLoad); diff --git a/src/scripts/popup.js b/src/scripts/popup.js index 855da2a..70f6085 100644 --- a/src/scripts/popup.js +++ b/src/scripts/popup.js @@ -52,6 +52,8 @@ document.addEventListener('DOMContentLoaded', function () { const settingsToggle = document.getElementById('settingsToggle'); const reportSection = document.getElementById('reportSection'); const settingsSection = document.getElementById('settingsSection'); + const orgInput = document.getElementById('orgInput'); + const setOrgBtn = document.getElementById('setOrgBtn'); let isSettingsVisible = false; const githubTokenInput = document.getElementById('githubToken'); @@ -59,15 +61,15 @@ document.addEventListener('DOMContentLoaded', function () { const tokenEyeIcon = document.getElementById('tokenEyeIcon'); let tokenVisible = false; - const orgInput = document.getElementById('orgInput'); - const setOrgBtn = document.getElementById('setOrgBtn'); - - chrome.storage.local.get(['darkMode'], function (result) { + // Use browser-agnostic storage API + const storage = chrome.storage || browser.storage; + + storage.local.get(['darkMode'], function (result) { if (result.darkMode) { body.classList.add('dark-mode'); darkModeToggle.src = 'icons/light-mode.png'; if (settingsIcon) { - settingsIcon.src = 'icons/settings-night.png'; // Changed from settings-night.png + settingsIcon.src = 'icons/settings-night.png'; } } }); @@ -87,7 +89,7 @@ document.addEventListener('DOMContentLoaded', function () { darkModeToggle.addEventListener('click', function () { body.classList.toggle('dark-mode'); const isDarkMode = body.classList.contains('dark-mode'); - chrome.storage.local.set({ darkMode: isDarkMode }); + storage.local.set({ darkMode: isDarkMode }); this.src = isDarkMode ? 'icons/light-mode.png' : 'icons/night-mode.png'; const settingsIcon = document.getElementById('settingsIcon'); if (settingsIcon) { @@ -129,7 +131,6 @@ document.addEventListener('DOMContentLoaded', function () { 'githubToken', 'projectName', 'settingsToggle', - ]; const radios = document.querySelectorAll('input[name="timeframe"]'); @@ -163,7 +164,6 @@ document.addEventListener('DOMContentLoaded', function () { } }); - if (customDateContainer) { if (!enableToggle) { customDateContainer.style.opacity = '0.5'; @@ -188,7 +188,7 @@ document.addEventListener('DOMContentLoaded', function () { } } - chrome.storage.local.get(['enableToggle'], (items) => { + storage.local.get(['enableToggle'], (items) => { const enableToggle = items.enableToggle !== false; updateContentState(enableToggle); if (!enableToggle) { @@ -198,29 +198,25 @@ document.addEventListener('DOMContentLoaded', function () { initializePopup(); }) - chrome.storage.onChanged.addListener((changes, namespace) => { + storage.onChanged.addListener((changes, namespace) => { if (namespace === 'local' && changes.enableToggle) { updateContentState(changes.enableToggle.newValue); if (changes.enableToggle.newValue) { - // re-initialize if enabled initializePopup(); } } }); function initializePopup() { - - // Button setup const generateBtn = document.getElementById('generateReport'); const copyBtn = document.getElementById('copyReport'); generateBtn.addEventListener('click', function () { - // Check org input value before generating report let org = orgInput.value.trim().toLowerCase(); if (!org) { org = 'fossasia'; } - chrome.storage.local.set({ orgName: org }, () => { + storage.local.set({ orgName: org }, () => { generateBtn.innerHTML = ' Generating...'; generateBtn.disabled = true; window.generateScrumReport(); @@ -267,14 +263,14 @@ document.addEventListener('DOMContentLoaded', function () { startDateInput.readOnly = false; endDateInput.readOnly = false; - chrome.storage.local.set({ + storage.local.set({ lastWeekContribution: false, yesterdayContribution: false, selectedTimeframe: null }); }); - chrome.storage.local.get([ + storage.local.get([ 'selectedTimeframe', 'lastWeekContribution', 'yesterdayContribution', @@ -283,13 +279,11 @@ document.addEventListener('DOMContentLoaded', function () { ], (items) => { console.log('Restoring state:', items); - if (items.startingDate && items.endingDate && !items.lastWeekContribution && !items.yesterdayContribution) { const startDateInput = document.getElementById('startingDate'); const endDateInput = document.getElementById('endingDate'); if (startDateInput && endDateInput) { - startDateInput.value = items.startingDate; endDateInput.value = items.endingDate; startDateInput.readOnly = false; @@ -325,7 +319,7 @@ document.addEventListener('DOMContentLoaded', function () { } startDateInput.readOnly = endDateInput.readOnly = true; - chrome.storage.local.set({ + storage.local.set({ startingDate: startDateInput.value, endingDate: endDateInput.value, lastWeekContribution: items.selectedTimeframe === 'lastWeekContribution', @@ -341,7 +335,6 @@ document.addEventListener('DOMContentLoaded', function () { reportSection.classList.remove('hidden'); settingsSection.classList.add('hidden'); settingsToggle.classList.remove('active'); - console.log('Switched to report view'); } function showSettingsView() { @@ -349,7 +342,6 @@ document.addEventListener('DOMContentLoaded', function () { reportSection.classList.add('hidden'); settingsSection.classList.remove('hidden'); settingsToggle.classList.add('active'); - console.log('Switched to settings view'); } if (settingsToggle) { @@ -372,7 +364,7 @@ document.addEventListener('DOMContentLoaded', function () { showReportView(); // Load org from storage or default - chrome.storage.local.get(['orgName'], function (result) { + storage.local.get(['orgName'], function (result) { orgInput.value = result.orgName || ''; }); @@ -382,8 +374,8 @@ document.addEventListener('DOMContentLoaded', function () { if (!org) { org = 'fossasia'; } - chrome.storage.local.set({ orgName: org }, function () { - chrome.storage.local.remove('githubCache'); // Clear cache on org change + storage.local.set({ orgName: org }, function () { + storage.local.remove('githubCache'); }); }); @@ -425,17 +417,14 @@ document.addEventListener('DOMContentLoaded', function () { } const oldToast = document.getElementById('invalid-org-toast'); if (oldToast) oldToast.parentNode.removeChild(oldToast); - chrome.storage.local.set({ orgName: org }, function () { - // Always clear the scrum report and show org changed message + storage.local.set({ orgName: org }, function () { const scrumReport = document.getElementById('scrumReport'); if (scrumReport) { scrumReport.innerHTML = '

Organization changed. Click Generate button to fetch the GitHub activities.

'; } - // Clear the githubCache for previous org - chrome.storage.local.remove('githubCache'); + storage.local.remove('githubCache'); setOrgBtn.disabled = false; setOrgBtn.innerHTML = originalText; - // Always show green toast: org is set const toastDiv = document.createElement('div'); toastDiv.id = 'invalid-org-toast'; toastDiv.className = 'toast'; @@ -484,7 +473,7 @@ document.addEventListener('DOMContentLoaded', function () { let cacheInput = document.getElementById('cacheInput'); if (cacheInput) { - chrome.storage.local.get(['cacheInput'], function (result) { + storage.local.get(['cacheInput'], function (result) { if (result.cacheInput) { cacheInput.value = result.cacheInput; } else { @@ -506,13 +495,11 @@ document.addEventListener('DOMContentLoaded', function () { this.style.borderColor = '#10b981'; } - chrome.storage.local.set({ cacheInput: ttlValue }, function () { + storage.local.set({ cacheInput: ttlValue }, function () { console.log('Cache TTL saved:', ttlValue, 'minutes'); }); }); - } - }); // Tooltip bubble @@ -567,7 +554,8 @@ document.querySelectorAll('input[name="timeframe"]').forEach(radio => { startDateInput.readOnly = false; endDateInput.readOnly = false; - chrome.storage.local.set({ + const storage = chrome.storage || browser.storage; + storage.local.set({ lastWeekContribution: false, yesterdayContribution: false, selectedTimeframe: null @@ -592,10 +580,7 @@ document.getElementById('refreshCache').addEventListener('click', async function button.disabled = true; try { - // Clear local cache await forceGithubDataRefresh(); - - // Clear the scrum report const scrumReport = document.getElementById('scrumReport'); if (scrumReport) { scrumReport.innerHTML = '

Cache cleared successfully. Click "Generate Report" to fetch fresh data.

'; @@ -608,7 +593,6 @@ document.getElementById('refreshCache').addEventListener('click', async function button.innerHTML = originalText; button.disabled = false; }, 2000); - } catch (error) { console.error('Cache clear failed:', error); button.innerHTML = 'Failed to clear cache'; @@ -624,8 +608,7 @@ document.getElementById('refreshCache').addEventListener('click', async function function toggleRadio(radio) { const startDateInput = document.getElementById('startingDate'); const endDateInput = document.getElementById('endingDate'); - - console.log('Toggling radio:', radio.id); + const storage = chrome.storage || browser.storage; if (radio.id === 'lastWeekContribution') { startDateInput.value = getLastWeek(); @@ -637,19 +620,12 @@ function toggleRadio(radio) { startDateInput.readOnly = endDateInput.readOnly = true; - chrome.storage.local.set({ + storage.local.set({ startingDate: startDateInput.value, endingDate: endDateInput.value, lastWeekContribution: radio.id === 'lastWeekContribution', yesterdayContribution: radio.id === 'yesterdayContribution', selectedTimeframe: radio.id, - githubCache: null // Clear cache to force new fetch - }, () => { - console.log('State saved, dates:', { - start: startDateInput.value, - end: endDateInput.value, - isLastWeek: radio.id === 'lastWeekContribution' - }); + githubCache: null }); -} - +} \ No newline at end of file diff --git a/src/scripts/scrumHelper.js b/src/scripts/scrumHelper.js index 3aebad4..f9f4a99 100644 --- a/src/scripts/scrumHelper.js +++ b/src/scripts/scrumHelper.js @@ -9,20 +9,27 @@ function logError(...args) { console.error('[SCRUM-HELPER]:', ...args); } } + console.log('Script loaded, adapter exists:', !!window.emailClientAdapter); + let refreshButton_Placed = false; let enableToggle = true; let hasInjectedContent = false; let scrumGenerationInProgress = false; -let orgName = 'fossasia'; // default +let orgName = 'fossasia'; + +// Unified storage API for Chrome/Firefox compatibility +const storage = chrome.storage || browser.storage; + function allIncluded(outputTarget = 'email') { if (scrumGenerationInProgress) { - console.warn('[SCRUM-HELPER]: Scrum generation already in progress, aborting new call.'); + log('Scrum generation already in progress, aborting new call'); return; } scrumGenerationInProgress = true; - console.log('allIncluded called with outputTarget:', outputTarget); - console.log('Current window context:', window.location.href); + log('allIncluded called with outputTarget:', outputTarget); + log('Current window context:', window.location.href); + let scrumBody = null; let scrumSubject = null; let startingDate = ''; @@ -45,25 +52,17 @@ function allIncluded(outputTarget = 'email') { let showCommits = false; let userReason = ''; - let pr_open_button = - '
open
'; - let pr_closed_button = - '
closed
'; - let pr_merged_button = - '
merged
'; - let pr_draft_button = - '
draft
'; - - let issue_closed_button = - '
closed
'; - let issue_opened_button = - '
open
'; - - - // let linkStyle = ''; - function getChromeData() { - console.log("Getting Chrome data for context:", outputTarget); - chrome.storage.local.get( + // Button styles + let pr_open_button = '
open
'; + let pr_closed_button = '
closed
'; + let pr_merged_button = '
merged
'; + let pr_draft_button = '
draft
'; + let issue_closed_button = '
closed
'; + let issue_opened_button = '
open
'; + + function getBrowserData() { + log("Getting browser data for context:", outputTarget); + storage.local.get( [ 'githubUsername', 'githubToken', @@ -82,9 +81,9 @@ function allIncluded(outputTarget = 'email') { 'orgName' ], (items) => { - console.log("Storage items received:", items); - + log("Storage items received:", items); + // Handle popup UI elements if (outputTarget === 'popup') { const usernameFromDOM = document.getElementById('githubUsername')?.value; const projectFromDOM = document.getElementById('projectName')?.value; @@ -96,7 +95,7 @@ function allIncluded(outputTarget = 'email') { items.userReason = reasonFromDOM || items.userReason; items.githubToken = tokenFromDOM || items.githubToken; - chrome.storage.local.set({ + storage.local.set({ githubUsername: items.githubUsername, projectName: items.projectName, userReason: items.userReason, @@ -104,6 +103,7 @@ function allIncluded(outputTarget = 'email') { }); } + // Set retrieved values githubUsername = items.githubUsername; projectName = items.projectName; userReason = items.userReason || 'No Blocker at the moment'; @@ -111,10 +111,18 @@ function allIncluded(outputTarget = 'email') { lastWeekContribution = items.lastWeekContribution; yesterdayContribution = items.yesterdayContribution; - if (!items.enableToggle) { - enableToggle = items.enableToggle; + // Handle feature toggles + if (items.enableToggle !== undefined) enableToggle = items.enableToggle; + if (items.cacheInput) cacheInput = items.cacheInput; + if (items.showCommits !== undefined) showCommits = items.showCommits; + if (items.orgName) orgName = items.orgName; + if (items.showOpenLabel === false) { + showOpenLabel = false; + pr_open_button = ''; + issue_opened_button = ''; } + // Handle date ranges if (items.lastWeekContribution) { handleLastWeekContributionChange(); } else if (items.yesterdayContribution) { @@ -123,115 +131,75 @@ function allIncluded(outputTarget = 'email') { startingDate = items.startingDate; endingDate = items.endingDate; } else { - handleLastWeekContributionChange(); //on fresh unpack - default to last week. + handleLastWeekContributionChange(); // Default to last week if (outputTarget === 'popup') { - chrome.storage.local.set({ lastWeekContribution: true, yesterdayContribution: false }); + storage.local.set({ lastWeekContribution: true, yesterdayContribution: false }); } } + + // Fetch data if username exists if (githubUsername) { - console.log("About to fetch GitHub data for:", githubUsername); + log("About to fetch GitHub data for:", githubUsername); fetchGithubData(); } else { - if (outputTarget === 'popup') { - console.log("No username found - popup context"); - // Show error in popup - const scrumReport = document.getElementById('scrumReport'); - const generateBtn = document.getElementById('generateReport'); - if (scrumReport) { - scrumReport.innerHTML = '
Please enter your GitHub username to generate a report.
'; - } - if (generateBtn) { - generateBtn.innerHTML = ' Generate Report'; - generateBtn.disabled = false; - } - scrumGenerationInProgress = false; - } else { - console.warn('No GitHub username found in storage'); - scrumGenerationInProgress = false; - } - return; - } - if (items.cacheInput) { - cacheInput = items.cacheInput; - } - if (items.showCommits !== undefined) { - showCommits = items.showCommits; - } else { - showCommits = false; // Default value + handleMissingUsername(); } - - - if (!items.showOpenLabel) { - showOpenLabel = false; - pr_unmerged_button = ''; - issue_opened_button = ''; - pr_merged_button = ''; - issue_closed_button = ''; - } - if (items.githubCache) { - githubCache.data = items.githubCache.data; - githubCache.cacheKey = items.githubCache.cacheKey; - githubCache.timestamp = items.githubCache.timestamp; - log('Restored cache from storage'); - } - - if (items.orgName) { - orgName = items.orgName; - } - }, + } ); } - getChromeData(); + + function handleMissingUsername() { + if (outputTarget === 'popup') { + log("No username found - popup context"); + const scrumReport = document.getElementById('scrumReport'); + const generateBtn = document.getElementById('generateReport'); + if (scrumReport) { + scrumReport.innerHTML = '
Please enter your GitHub username to generate a report.
'; + } + if (generateBtn) { + generateBtn.innerHTML = ' Generate Report'; + generateBtn.disabled = false; + } + scrumGenerationInProgress = false; + } else { + log('No GitHub username found in storage'); + scrumGenerationInProgress = false; + } + } function handleLastWeekContributionChange() { endingDate = getToday(); startingDate = getLastWeek(); } + function handleYesterdayContributionChange() { endingDate = getToday(); startingDate = getYesterday(); } + function getLastWeek() { - let today = new Date(); - let lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7); - let lastWeekMonth = lastWeek.getMonth() + 1; - let lastWeekDay = lastWeek.getDate(); - let lastWeekYear = lastWeek.getFullYear(); - let lastWeekDisplayPadded = - ('0000' + lastWeekYear.toString()).slice(-4) + - '-' + - ('00' + lastWeekMonth.toString()).slice(-2) + - '-' + - ('00' + lastWeekDay.toString()).slice(-2); - return lastWeekDisplayPadded; + const today = new Date(); + const lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7); + const lastWeekMonth = lastWeek.getMonth() + 1; + const lastWeekDay = lastWeek.getDate(); + return `${lastWeek.getFullYear()}-${pad(lastWeekMonth)}-${pad(lastWeekDay)}`; } + function getYesterday() { - let today = new Date(); - let yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1); - let yesterdayMonth = yesterday.getMonth() + 1; - let yesterdayDay = yesterday.getDate(); - let yesterdayYear = yesterday.getFullYear(); - let yesterdayPadded = - ('0000' + yesterdayYear.toString()).slice(-4) + - '-' + - ('00' + yesterdayMonth.toString()).slice(-2) + - '-' + - ('00' + yesterdayDay.toString()).slice(-2); - return yesterdayPadded; + const today = new Date(); + const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1); + const yesterdayMonth = yesterday.getMonth() + 1; + const yesterdayDay = yesterday.getDate(); + return `${yesterday.getFullYear()}-${pad(yesterdayMonth)}-${pad(yesterdayDay)}`; } + function getToday() { - let today = new Date(); - let Week = new Date(today.getFullYear(), today.getMonth(), today.getDate()); - let WeekMonth = Week.getMonth() + 1; - let WeekDay = Week.getDate(); - let WeekYear = Week.getFullYear(); - let WeekDisplayPadded = - ('0000' + WeekYear.toString()).slice(-4) + - '-' + - ('00' + WeekMonth.toString()).slice(-2) + - '-' + - ('00' + WeekDay.toString()).slice(-2); - return WeekDisplayPadded; + const today = new Date(); + return `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`; + } + + function pad(num) { + return num.toString().padStart(2, '0'); } // Global cache object @@ -242,21 +210,18 @@ function allIncluded(outputTarget = 'email') { ttl: 10 * 60 * 1000, // cache valid for 10 mins fetching: false, queue: [], - errors: {}, - errorTTL: 60 * 1000, // 1 min error cache subject: null, }; async function getCacheTTL() { return new Promise((resolve) => { - chrome.storage.local.get(['cacheInput'], function (result) { + storage.local.get(['cacheInput'], (result) => { const ttlMinutes = result.cacheInput || 10; resolve(ttlMinutes * 60 * 1000); }); }); } - function saveToStorage(data, subject = null) { const cacheData = { data: data, @@ -264,32 +229,19 @@ function allIncluded(outputTarget = 'email') { timestamp: githubCache.timestamp, subject: subject, } - log(`Saving data to storage:`, { - cacheKey: githubCache.cacheKey, - timestamp: githubCache.timestamp, - hasSubject: !!subject, - }); - return new Promise((resolve) => { - chrome.storage.local.set({ githubCache: cacheData }, () => { - if (chrome.runtime.lastError) { - logError('Storage save failed: ', chrome.runtime.lastError); - resolve(false); - } else { - log('Cache saved successfuly'); - githubCache.data = data; - githubCache.subject = subject; - resolve(true); - } + storage.local.set({ githubCache: cacheData }, () => { + githubCache.data = data; + githubCache.subject = subject; + resolve(true); }); }); } function loadFromStorage() { - log('Loading cache from storage'); return new Promise(async (resolve) => { const currentTTL = await getCacheTTL(); - chrome.storage.local.get('githubCache', (result) => { + storage.local.get('githubCache', (result) => { const cache = result.githubCache; if (!cache) { log('No cache found in storage'); @@ -302,22 +254,10 @@ function allIncluded(outputTarget = 'email') { resolve(false); return; } - log('Found valid cache:', { - cacheKey: cache.cacheKey, - age: `${((Date.now() - cache.timestamp) / 1000 / 60).toFixed(1)} minutes`, - }); - githubCache.data = cache.data; githubCache.cacheKey = cache.cacheKey; githubCache.timestamp = cache.timestamp; githubCache.subject = cache.subject; - githubCache.usedToken = cache.usedToken || false; - - - if (cache.subject && scrumSubject) { - scrumSubject.value = cache.subject; - scrumSubject.dispatchEvent(new Event('input', { bubbles: true })); - } resolve(true); }); }); @@ -325,83 +265,45 @@ function allIncluded(outputTarget = 'email') { async function fetchGithubData() { const cacheKey = `${githubUsername}-${orgName}-${startingDate}-${endingDate}`; - if (githubCache.fetching || (githubCache.cacheKey === cacheKey && githubCache.data)) { - log('Fetch already in progress or data already fetched. Skipping fetch.'); + log('Using existing data or ongoing fetch'); return; } - log('Fetching Github data:', { - username: githubUsername, - startDate: startingDate, - endDate: endingDate, - }); - - log('CacheKey in cache:', githubCache.cacheKey); - log('Incoming cacheKey:', cacheKey); - log('Has data:', !!githubCache.data); - - // Check if we need to load from storage + // Check cache status if (!githubCache.data && !githubCache.fetching) { await loadFromStorage(); }; const currentTTL = await getCacheTTL(); githubCache.ttl = currentTTL; - log(`Caching for ${currentTTL / (60 * 1000)} minutes`); + githubCache.cacheKey = cacheKey; const now = Date.now(); const isCacheFresh = (now - githubCache.timestamp) < githubCache.ttl; const isCacheKeyMatch = githubCache.cacheKey === cacheKey; - const needsToken = !!githubToken; - const cacheUsedToken = !!githubCache.usedToken; - if (githubCache.data && isCacheFresh & isCacheKeyMatch) { //should be && check after rebase - if (needsToken & !cacheUsedToken) { - log('Cache was fetched without token, but user now has a token. Invalidating cache.'); - githubCache.data = null; - } else { - log('Using cached data - cache is fresh and key matches'); - processGithubData(githubCache.data); - return Promise.resolve(); - } - } - // if cache key does not match our cache is stale, fetch new data - if (!isCacheKeyMatch) { - log('Cache key mismatch - fetching new Data'); - githubCache.data = null; - } else if (!isCacheFresh) { - log('Cache is stale - fetching new data'); + + if (githubCache.data && isCacheFresh && isCacheKeyMatch) { + log('Using cached data'); + processGithubData(githubCache.data); + return; } - // if fetching is in progress, queue the calls and return a promise resolved when done - if (githubCache.fetching) { - log('Fetch in progress, queuing requests'); - return new Promise((resolve, reject) => { - githubCache.queue.push({ resolve, reject }); - }); - } + if (!isCacheKeyMatch) githubCache.data = null; + if (githubCache.fetching) return; githubCache.fetching = true; - githubCache.cacheKey = cacheKey; - const headers = { 'Accept': 'application/vnd.github.v3+json', + ...(githubToken && { 'Authorization': `token ${githubToken}` }) }; - if (githubToken) { - log('Making authenticated requests.'); - headers['Authorization'] = `token ${githubToken}`; - - } else { - log('Making public requests'); - } - - let issueUrl = `https://api.github.com/search/issues?q=author%3A${githubUsername}+org%3A${orgName}+updated%3A${startingDate}..${endingDate}&per_page=100`; - let prUrl = `https://api.github.com/search/issues?q=commenter%3A${githubUsername}+org%3A${orgName}+updated%3A${startingDate}..${endingDate}&per_page=100`; - let userUrl = `https://api.github.com/users/${githubUsername}`; - try { - // throttling 500ms to avoid burst + const issueUrl = `https://api.github.com/search/issues?q=author%3A${githubUsername}+org%3A${orgName}+updated%3A${startingDate}..${endingDate}&per_page=100`; + const prUrl = `https://api.github.com/search/issues?q=commenter%3A${githubUsername}+org%3A${orgName}+updated%3A${startingDate}..${endingDate}&per_page=100`; + const userUrl = `https://api.github.com/users/${githubUsername}`; + + // Throttle requests await new Promise(res => setTimeout(res, 500)); const [issuesRes, prRes, userRes] = await Promise.all([ @@ -410,183 +312,58 @@ function allIncluded(outputTarget = 'email') { fetch(userUrl, { headers }), ]); - if (issuesRes.status === 401 || prRes.status === 401 || userRes.status === 401 || - issuesRes.status === 403 || prRes.status === 403 || userRes.status === 403) { + // Handle API errors + if (issuesRes.status === 401 || prRes.status === 401 || userRes.status === 401) { showInvalidTokenMessage(); return; } - if (issuesRes.status === 404 || prRes.status === 404) { - if (outputTarget === 'popup') { - Materialize.toast && Materialize.toast('Organization not found on GitHub', 3000); - } - throw new Error('Organization not found'); - } - - if (!issuesRes.ok) throw new Error(`Error fetching Github issues: ${issuesRes.status} ${issuesRes.statusText}`); - if (!prRes.ok) throw new Error(`Error fetching Github PR review data: ${prRes.status} ${prRes.statusText}`); - if (!userRes.ok) throw new Error(`Error fetching Github userdata: ${userRes.status} ${userRes.statusText}`); + if (!issuesRes.ok) throw new Error(`Error fetching issues: ${issuesRes.status}`); + if (!prRes.ok) throw new Error(`Error fetching PRs: ${prRes.status}`); + if (!userRes.ok) throw new Error(`Error fetching user: ${userRes.status}`); githubIssuesData = await issuesRes.json(); githubPrsReviewData = await prRes.json(); githubUserData = await userRes.json(); - if (githubIssuesData && githubIssuesData.items) { - log('Fetched githubIssuesData:', githubIssuesData.items.length, 'items'); - // Collect open PRs - const openPRs = githubIssuesData.items.filter( - item => item.pull_request && item.state === 'open' - ); - log('Open PRs for commit fetching:', openPRs.map(pr => pr.number)); - // Fetch commits for open PRs (batch) - if (openPRs.length && githubToken) { - const commitMap = await fetchCommitsForOpenPRs(openPRs, githubToken, startingDate, endingDate); - log('Commit map returned from fetchCommitsForOpenPRs:', commitMap); - // Attach commits to PR objects - openPRs.forEach(pr => { - pr._allCommits = commitMap[pr.number] || []; - log(`Attached ${pr._allCommits.length} commits to PR #${pr.number}`); - }); - } - } - // Cache the data githubCache.data = { githubIssuesData, githubPrsReviewData, githubUserData }; githubCache.timestamp = Date.now(); - await saveToStorage(githubCache.data); - processGithubData(githubCache.data); - // Resolve queued calls - githubCache.queue.forEach(({ resolve }) => resolve()); - githubCache.queue = []; + processGithubData(githubCache.data); } catch (err) { logError('Fetch Failed:', err); - // Reject queued calls on error - githubCache.queue.forEach(({ reject }) => reject(err)); - githubCache.queue = []; + showErrorMessage(err.message || 'An error occurred'); + } finally { githubCache.fetching = false; + } + } - if (outputTarget === 'popup') { + function showInvalidTokenMessage() { + if (outputTarget === 'popup') { + const reportDiv = document.getElementById('scrumReport'); + if (reportDiv) { + reportDiv.innerHTML = '
Invalid or expired GitHub token. Please check your token in the settings and try again.
'; const generateBtn = document.getElementById('generateReport'); - if (scrumReport) { - let errorMsg = 'An error occurred while generating the report.'; - if (err) { - if (typeof err === 'string') errorMsg = err; - else if (err.message) errorMsg = err.message; - else errorMsg = JSON.stringify(err) - } - scrumReport.innerHTML = `
${err.message || 'An error occurred while generating the report.'}
`; - generateBtn.innerHTML = ' Generate Report'; - generateBtn.disabled = false; - } if (generateBtn) { generateBtn.innerHTML = ' Generate Report'; generateBtn.disabled = false; } } - scrumGenerationInProgress = false; - throw err; - } finally { - githubCache.fetching = false; } } - async function fetchCommitsForOpenPRs(prs, githubToken, startDate, endDate) { - log('fetchCommitsForOpenPRs called with PRs:', prs.map(pr => pr.number), 'startDate:', startDate, 'endDate:', endDate); - if (!prs.length) return {}; - const since = new Date(startDate).toISOString(); - const until = new Date(endDate + 'T23:59:59').toISOString(); - let queries = prs.map((pr, idx) => { - const repoParts = pr.repository_url.split('/'); - const owner = repoParts[repoParts.length - 2]; - const repo = repoParts[repoParts.length - 1]; - return ` - pr${idx}: repository(owner: "${owner}", name: "${repo}") { - pullRequest(number: ${pr.number}) { - commits(first: 100) { - nodes { - commit { - messageHeadline - committedDate - url - author { - name - user { login } - } - } - } - } - } - - }`; - }).join('\n'); - const query = `query { ${queries} }`; - log('GraphQL query for commits:', query); - const res = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(githubToken ? { Authorization: `bearer ${githubToken}` } : {}) - }, - body: JSON.stringify({ query }) - }); - log('fetchCommitsForOpenPRs response status:', res.status); - const data = await res.json(); - log('fetchCommitsForOpenPRs response data:', data); - let commitMap = {}; - prs.forEach((pr, idx) => { - const prData = data.data && data.data[`pr${idx}`] && data.data[`pr${idx}`].pullRequest; - if (prData && prData.commits && prData.commits.nodes) { - const allCommits = prData.commits.nodes.map(n => n.commit); - log(`PR #${pr.number} allCommits:`, allCommits); - const filteredCommits = allCommits.filter(commit => { - const commitDate = new Date(commit.committedDate); - const sinceDate = new Date(since); - const untilDate = new Date(until); - return commitDate >= sinceDate && commitDate <= untilDate; - }); - log(`PR #${pr.number} filteredCommits:`, filteredCommits); - commitMap[pr.number] = filteredCommits; - } else { - log(`No commits found for PR #${pr.number}`); - } - }); - return commitMap; - } - async function verifyCacheStatus() { - log('Cache Status: ', { - hasCachedData: !!githubCache.data, - cacheAge: githubCache.timestamp ? `${((Date.now() - githubCache.timestamp) / 1000 / 60).toFixed(1)} minutes` : `no cache`, - cacheKey: githubCache.cacheKey, - isFetching: githubCache.fetching, - queueLength: githubCache.queue.length - }); - const storageData = await new Promise(resolve => { - chrome.storage.local.get('githubCache', resolve); - }); - log('Storage Status:', { - hasStoredData: !!storageData.githubCache, - storedCacheKey: storageData.githubCache?.cacheKey, - storageAge: storageData.githubCache?.timestamp ? - `${((Date.now() - storageData.githubCache.timestamp) / 1000 / 60).toFixed(1)} minutes` : - 'no data' - }); - } - verifyCacheStatus(); - - function showInvalidTokenMessage() { + function showErrorMessage(message) { if (outputTarget === 'popup') { const reportDiv = document.getElementById('scrumReport'); if (reportDiv) { - reportDiv.innerHTML = '
Invalid or expired GitHub token. Please check your token in the settings and try again.
'; + reportDiv.innerHTML = `
${message}
`; const generateBtn = document.getElementById('generateReport'); if (generateBtn) { generateBtn.innerHTML = ' Generate Report'; generateBtn.disabled = false; } - } else { - alert('Invalid or expired GitHub token. Please check your token in the extension popup and try again.'); } } } @@ -597,13 +374,6 @@ function allIncluded(outputTarget = 'email') { githubPrsReviewData = data.githubPrsReviewData; githubUserData = data.githubUserData; - log('GitHub data set:', { - issues: githubIssuesData?.items?.length || 0, - prs: githubPrsReviewData?.items?.length || 0, - user: githubUserData?.login - }); - - lastWeekArray = []; nextWeekArray = []; reviewedPrsArray = []; @@ -616,11 +386,13 @@ function allIncluded(outputTarget = 'email') { if (!githubCache.subject && scrumSubject) { scrumSubjectLoaded(); } + await Promise.all([ writeGithubIssuesPrs(), writeGithubPrsReviews(), ]) - log('Both data processing functions completed, generating scrum body'); + + log('Generating scrum body'); writeScrumBody(); } @@ -630,31 +402,29 @@ function allIncluded(outputTarget = 'email') { return date.toLocaleDateString('en-US', options); } - //load initial text in scrum body function writeScrumBody() { if (!enableToggle || (outputTarget === 'email' && hasInjectedContent)) return; if (outputTarget === 'email') { if (!window.emailClientAdapter) { - console.error('Email client adapter not found'); + logError('Email client adapter not found'); return; } if (!window.emailClientAdapter.isNewConversation()) { - console.log('Not a new conversation, skipping scrum helper'); + log('Not a new conversation, skipping scrum helper'); return; } } setTimeout(() => { - // Generate content first + // Generate content let lastWeekUl = ''; let nextWeekUl = ''; let weekOrDay = lastWeekContribution ? 'last week' : (yesterdayContribution ? 'yesterday' : 'the period'); @@ -662,7 +432,7 @@ function allIncluded(outputTarget = 'email') { // Create the complete content let content; - if (lastWeekContribution == true || yesterdayContribution == true) { + if (lastWeekContribution || yesterdayContribution) { content = `1. What did I do ${weekOrDay}?
${lastWeekUl}
2. What do I plan to do ${weekOrDay2}?
@@ -678,59 +448,43 @@ ${nextWeekUl}
${userReason}`; } - + // Inject into UI if (outputTarget === 'popup') { const scrumReport = document.getElementById('scrumReport'); if (scrumReport) { - log("found div, updating content"); scrumReport.innerHTML = content; - - // Reset generate button const generateBtn = document.getElementById('generateReport'); if (generateBtn) { generateBtn.innerHTML = ' Generate Report'; generateBtn.disabled = false; } - scrumGenerationInProgress = false; - } else { - logError('Scrum report div not found'); - scrumGenerationInProgress = false; } } else { const elements = window.emailClientAdapter.getEditorElements(); - if (!elements || !elements.body) { - console.error('Email client editor not found'); - return; + if (elements && elements.body) { + window.emailClientAdapter.injectContent(elements.body, content, elements.eventTypes.contentChange); + hasInjectedContent = true; } - window.emailClientAdapter.injectContent(elements.body, content, elements.eventTypes.contentChange); - hasInjectedContent = true; - scrumGenerationInProgress = false; } + + scrumGenerationInProgress = false; }, 500); } - //load initial scrum subject function scrumSubjectLoaded() { try { if (!enableToggle || hasInjectedContent) return; if (!scrumSubject) { - console.error('Subject element not found'); + logError('Subject element not found'); return; } setTimeout(() => { let name = githubUserData.name || githubUsername; let project = projectName || ''; let curDate = new Date(); - let year = curDate.getFullYear().toString(); - let date = curDate.getDate(); - let month = curDate.getMonth(); - month++; - if (month < 10) month = '0' + month; - if (date < 10) date = '0' + date; - let dateCode = year.toString() + month.toString() + date.toString(); + let dateCode = `${curDate.getFullYear()}${pad(curDate.getMonth() + 1)}${pad(curDate.getDate())}`; const subject = `[Scrum] ${name} - ${project} - ${dateCode}`; - log('Generated subject:', subject); githubCache.subject = subject; saveToStorage(githubCache.data, subject); @@ -740,83 +494,61 @@ ${userReason}`; } }); } catch (err) { - console.err('Error while setting subject: ', err); + logError('Error setting subject:', err); } } function writeGithubPrsReviews() { let items = githubPrsReviewData.items; - log('Processing PR reviews:', { - hasItems: !!items, - itemCount: items?.length, - firstItem: items?.[0] - }); if (!items) { logError('No Github PR review data available'); return; } reviewedPrsArray = []; githubPrsReviewDataProcessed = {}; - let i; - for (i = 0; i < items.length; i++) { + + for (let i = 0; i < items.length; i++) { let item = items[i]; - if (item.user.login == githubUsername || !item.pull_request) continue; + if (item.user.login === githubUsername || !item.pull_request) continue; let repository_url = item.repository_url; let project = repository_url.substr(repository_url.lastIndexOf('/') + 1); let title = item.title; let number = item.number; let html_url = item.html_url; + if (!githubPrsReviewDataProcessed[project]) { - // first pr in this repo githubPrsReviewDataProcessed[project] = []; } - let obj = { + + githubPrsReviewDataProcessed[project].push({ number: number, html_url: html_url, title: title, state: item.state, - }; - githubPrsReviewDataProcessed[project].push(obj); + }); } + for (let repo in githubPrsReviewDataProcessed) { - let repoLi = - '
  • \ - (' + - repo + - ') - Reviewed '; + let repoLi = `
  • (${repo}) - Reviewed `; if (githubPrsReviewDataProcessed[repo].length > 1) repoLi += 'PRs - '; - else { - repoLi += 'PR - '; - } + else repoLi += 'PR - '; + if (githubPrsReviewDataProcessed[repo].length <= 1) { for (let pr in githubPrsReviewDataProcessed[repo]) { let pr_arr = githubPrsReviewDataProcessed[repo][pr]; - let prText = ''; - prText += - "#" + pr_arr.number + ' (' + pr_arr.title + ') '; + let prText = `#${pr_arr.number} (${pr_arr.title}) `; if (pr_arr.state === 'open') prText += issue_opened_button; else prText += issue_closed_button; - - prText += '  '; repoLi += prText; } } else { repoLi += ''; @@ -826,55 +558,12 @@ ${userReason}`; } prsReviewDataProcessed = true; - if (outputTarget === 'email') { - triggerScrumGeneration(); - } - - } - - function triggerScrumGeneration() { - if (issuesDataProcessed && prsReviewDataProcessed) { - log('Both data sets processed, generating scrum body.'); + if (issuesDataProcessed && outputTarget === 'email') { writeScrumBody(); - } else { - log('Waiting for all data to be processed before generating scrum.', { - issues: issuesDataProcessed, - reviews: prsReviewDataProcessed, - }); - } - } - - // Helper: calculate days between two yyyy-mm-dd strings - function getDaysBetween(start, end) { - const d1 = new Date(start); - const d2 = new Date(end); - return Math.ceil((d2 - d1) / (1000 * 60 * 60 * 24)); - } - - // Session cache object - let sessionMergedStatusCache = {}; - - // Helper to fetch PR details for merged status (REST, single PR) - async function fetchPrMergedStatusREST(owner, repo, number, headers) { - const cacheKey = `${owner}/${repo}#${number}`; - if (sessionMergedStatusCache[cacheKey] !== undefined) { - return sessionMergedStatusCache[cacheKey]; - } - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${number}`; - try { - const res = await fetch(url, { headers }); - if (!res.ok) return null; - const data = await res.json(); - const merged = !!data.merged_at; - sessionMergedStatusCache[cacheKey] = merged; - return merged; - } catch (e) { - return null; } } - async function writeGithubIssuesPrs() { - log('writeGithubIssuesPrs called'); + function writeGithubIssuesPrs() { let items = githubIssuesData.items; lastWeekArray = []; nextWeekArray = []; @@ -882,53 +571,7 @@ ${userReason}`; logError('No Github issues data available'); return; } - - const headers = { 'Accept': 'application/vnd.github.v3+json' }; - if (githubToken) headers['Authorization'] = `token ${githubToken}`; - let useMergedStatus = false; - let fallbackToSimple = false; - let daysRange = getDaysBetween(startingDate, endingDate); - // For token users, always enable useMergedStatus (no 7-day limit) - if (githubToken) { - useMergedStatus = true; - } else if (daysRange <= 7) { - useMergedStatus = true; - } - // Collect PRs to batch fetch merged status - let prsToCheck = []; - for (let i = 0; i < items.length; i++) { - let item = items[i]; - if (item.pull_request && item.state === 'closed' && useMergedStatus && !fallbackToSimple) { - let repository_url = item.repository_url; - let repoParts = repository_url.split('/'); - let owner = repoParts[repoParts.length - 2]; - let repo = repoParts[repoParts.length - 1]; - prsToCheck.push({ owner, repo, number: item.number, idx: i }); - } - } - - let mergedStatusResults = {}; - if (githubToken) { - // Use GraphQL batching for all cases - if (prsToCheck.length > 0) { - mergedStatusResults = await fetchPrsMergedStatusBatch(prsToCheck, headers); - log('Merged status results (GraphQL):', mergedStatusResults); - } - } else if (useMergedStatus) { - if (prsToCheck.length > 30) { - fallbackToSimple = true; - if (typeof Materialize !== 'undefined' && Materialize.toast) { - Materialize.toast('API limit exceeded. Please use a GitHub token for full status. Showing only open/closed PRs.', 5000); - } - } else { - // Use REST API for each PR, cache results - for (let pr of prsToCheck) { - let merged = await fetchPrMergedStatusREST(pr.owner, pr.repo, pr.number, headers); - mergedStatusResults[`${pr.owner}/${pr.repo}#${pr.number}`] = merged; - } - log('Merged status results (REST):', mergedStatusResults); - } - } + for (let i = 0; i < items.length; i++) { let item = items[i]; let html_url = item.html_url; @@ -938,262 +581,122 @@ ${userReason}`; let number = item.number; let li = ''; let isDraft = false; + if (item.pull_request && typeof item.draft !== 'undefined') { isDraft = item.draft; } + if (item.pull_request) { - - const prCreatedDate = new Date(item.created_at); - const startDate = new Date(startingDate); - const endDate = new Date(endingDate + 'T23:59:59'); - const isNewPR = prCreatedDate >= startDate && prCreatedDate <= endDate; - - if (!isNewPR) { - const hasCommitsInRange = showCommits && item._allCommits && item._allCommits.length > 0; - - if (!hasCommitsInRange) { - - continue; //skip these prs - created outside daterange with no commits - } else { - - } - } else { - - } - const prAction = isNewPR ? 'Made PR' : 'Existing PR'; if (isDraft) { - li = `
  • (${project}) - ${prAction} (#${number}) - ${title} ${pr_draft_button}
  • `; + li = `
  • (${project}) - Made PR (#${number}) - ${title} ${pr_draft_button}
  • `; } else if (item.state === 'open') { - li = `
  • (${project}) - ${prAction} (#${number}) - ${title} ${pr_open_button}`; - if (showCommits && item._allCommits && item._allCommits.length && !isNewPR) { - log(`[PR DEBUG] Rendering commits for existing PR #${number}:`, item._allCommits); - item._allCommits.forEach(commit => { - li += `
  • ${commit.messageHeadline} (${new Date(commit.committedDate).toLocaleString()})
  • `; - }); - } - li += ``; + li = `
  • (${project}) - Made PR (#${number}) - ${title} ${pr_open_button}
  • `; } else if (item.state === 'closed') { - let merged = null; - if ((githubToken || (useMergedStatus && !fallbackToSimple)) && mergedStatusResults) { - let repoParts = repository_url.split('/'); - let owner = repoParts[repoParts.length - 2]; - let repo = repoParts[repoParts.length - 1]; - merged = mergedStatusResults[`${owner}/${repo}#${number}`]; - } - if (merged === true) { - li = `
  • (${project}) - Made PR (#${number}) - ${title} ${pr_merged_button}
  • `; - } else { - // Always show closed label for merged === false or merged === null/undefined - li = `
  • (${project}) - Made PR (#${number}) - ${title} ${pr_closed_button}
  • `; - } + li = `
  • (${project}) - Made PR (#${number}) - ${title} ${pr_closed_button}
  • `; } lastWeekArray.push(li); - continue; } else { - // is a issue + // Handle issues if (item.state === 'open' && item.body?.toUpperCase().indexOf('YES') > 0) { - let li2 = - '
  • (' + - project + - ') - Work on Issue(#' + - number + - ") - " + - title + - ' ' + - issue_opened_button + - '  
  • '; + let li2 = `
  • (${project}) - Work on Issue(#${number}) - ${title} ${issue_opened_button}
  • `; nextWeekArray.push(li2); } + if (item.state === 'open') { li = `
  • (${project}) - Opened Issue(#${number}) - ${title} ${issue_opened_button}
  • `; } else if (item.state === 'closed') { li = `
  • (${project}) - Opened Issue(#${number}) - ${title} ${issue_closed_button}
  • `; } else { - li = - '
  • (' + - project + - ') - Opened Issue(#' + - number + - ") - " + - title + - '
  • '; + li = `
  • (${project}) - Opened Issue(#${number}) - ${title}
  • `; } + lastWeekArray.push(li); } - lastWeekArray.push(li); } + issuesDataProcessed = true; - if (outputTarget === 'email') { - triggerScrumGeneration(); + if (prsReviewDataProcessed && outputTarget === 'email') { + writeScrumBody(); } } + // Initialize email UI elements let intervalBody = setInterval(() => { if (!window.emailClientAdapter) return; - const elements = window.emailClientAdapter.getEditorElements(); if (!elements || !elements.body) return; - clearInterval(intervalBody); scrumBody = elements.body; - // writeScrumBody(); // This call is premature and causes the issue. }, 500); let intervalSubject = setInterval(() => { if (!githubUserData || !window.emailClientAdapter) return; - const elements = window.emailClientAdapter.getEditorElements(); if (!elements || !elements.subject) return; if (outputTarget === 'email' && !window.emailClientAdapter.isNewConversation()) { - console.log('Not a new conversation, skipping subject interval'); clearInterval(intervalSubject); return; } clearInterval(intervalSubject); scrumSubject = elements.subject; - - setTimeout(() => { - scrumSubjectLoaded(); - }, 500); + setTimeout(scrumSubjectLoaded, 500); }, 500); - // check for github safe writing - let intervalWriteGithubIssues = setInterval(() => { - if (outputTarget === 'popup') { - return; - } else { - if (scrumBody && githubUsername && githubIssuesData && githubPrsReviewData) { - clearInterval(intervalWriteGithubIssues); - clearInterval(intervalWriteGithubPrs); - writeGithubIssuesPrs(); - } - } - }, 500); - let intervalWriteGithubPrs = setInterval(() => { - if (outputTarget === 'popup') { - return; - } else { - if (scrumBody && githubUsername && githubPrsReviewData && githubIssuesData) { - clearInterval(intervalWriteGithubPrs); - clearInterval(intervalWriteGithubIssues); - writeGithubPrsReviews(); - } - } - }, 500); - if (!refreshButton_Placed) { - let intervalWriteButton = setInterval(() => { - if (document.getElementsByClassName('F0XO1GC-x-b').length == 3 && scrumBody && enableToggle) { - refreshButton_Placed = true; - clearInterval(intervalWriteButton); - let td = document.createElement('td'); - let button = document.createElement('button'); - button.style = 'background-image:none;background-color:#3F51B5;'; - button.setAttribute('class', 'F0XO1GC-n-a F0XO1GC-G-a'); - button.title = 'Rewrite your SCRUM using updated settings!'; - button.id = 'refreshButton'; - let elemText = document.createTextNode('↻ Rewrite SCRUM!'); - button.appendChild(elemText); - td.appendChild(button); - document.getElementsByClassName('F0XO1GC-x-b')[0].children[0].children[0].appendChild(td); - document.getElementById('refreshButton').addEventListener('click', handleRefresh); - } - }, 1000); - } - function handleRefresh() { - hasInjectedContent = false; // Reset the flag before refresh - allIncluded(); - } + // Initialize + getBrowserData(); } +// Cache refresh function async function forceGithubDataRefresh() { let showCommits = false; - await new Promise(resolve => { - chrome.storage.local.get('showCommits', (result) => { - if (result.showCommits !== undefined) { - showCommits = result.showCommits; - } + storage.local.get('showCommits', (result) => { + if (result.showCommits !== undefined) showCommits = result.showCommits; resolve(); }); }); + // Reset cache if (typeof githubCache !== 'undefined') { githubCache.data = null; githubCache.cacheKey = null; githubCache.timestamp = 0; githubCache.subject = null; - githubCache.fetching = false; - githubCache.queue = []; } - await new Promise(resolve => { - chrome.storage.local.remove('githubCache', resolve); - }); - - chrome.storage.local.set({ showCommits: showCommits }); - + await new Promise(resolve => storage.local.remove('githubCache', resolve)); + storage.local.set({ showCommits }); hasInjectedContent = false; - return { success: true }; } - -// allIncluded('email'); - +// Initialize based on context if (window.location.protocol.startsWith('http')) { allIncluded('email'); - $('button>span:contains(New conversation)').parent('button').click(() => { - allIncluded(); - }); + // Gmail-specific handler + const newConvBtn = document.querySelector('button>span:contains(New conversation)'); + if (newConvBtn) newConvBtn.parentElement.addEventListener('click', () => allIncluded()); } window.generateScrumReport = function () { allIncluded('popup'); } +// Message handling for cache refresh chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'forceRefresh') { forceGithubDataRefresh() - .then(result => sendResponse(result)).catch(err => { - console.error('Force refresh failed:', err); - sendResponse({ success: false, error: err.message }); - }); - return true; + .then(result => sendResponse(result)) + .catch(err => sendResponse({ success: false, error: err.message })); + return true; // Indicates async response } }); -async function fetchPrsMergedStatusBatch(prs, headers) { - // prs: Array of {owner, repo, number} - const results = {}; - if (prs.length === 0) return results; - // Use GitHub GraphQL API for batching - const query = `query { -${prs.map((pr, i) => ` repo${i}: repository(owner: \"${pr.owner}\", name: \"${pr.repo}\") { - pr${i}: pullRequest(number: ${pr.number}) { merged } - }`).join('\n')} -}`; - try { - const res = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query }), - }); - if (!res.ok) return results; - const data = await res.json(); - prs.forEach((pr, i) => { - const merged = data.data[`repo${i}`]?.[`pr${i}`]?.merged; - results[`${pr.owner}/${pr.repo}#${pr.number}`] = merged; - }); - return results; - } catch (e) { - return results; - } +// Initialize for email clients +if (window.emailClientAdapter) { + window.emailClientAdapter.onNewCompose(() => { + hasInjectedContent = false; + allIncluded('email'); + }); } \ No newline at end of file