Skip to content

Commit f34b9f2

Browse files
authored
Merge branch 'main' into date-fix
2 parents ba24fe7 + 762231e commit f34b9f2

8 files changed

Lines changed: 775 additions & 358 deletions

File tree

CHANGELOG.md

Lines changed: 357 additions & 0 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 102 additions & 99 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
"homepage": "https://github.com/fossasia/scrum_helper",
2828
"devDependencies": {
2929
"@biomejs/biome": "2.4.11",
30-
"autoprefixer": "^10.4.27",
31-
"postcss": "^8.5.9",
30+
"autoprefixer": "^10.5.0",
31+
"postcss": "^8.5.10",
3232
"tailwindcss": "^4.2.2"
3333
},
3434
"dependencies": {
35-
"@tailwindcss/cli": "^4.2.2"
35+
"@tailwindcss/cli": "^4.2.4"
3636
}
3737
}

src/_locales/en/messages.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@
7878
"message": "Insert to Email",
7979
"description": "Text for the 'Insert to Email' button."
8080
},
81+
"insertToEmailFailedError": {
82+
"message": "Open an email tab to insert report",
83+
"description": "Error message shown when no active email tab can receive insert action."
84+
},
8185
"settingsOrgNameLabel": {
8286
"message": "Organization Name",
8387
"description": "Label for the organization name input in settings."
@@ -334,7 +338,7 @@
334338
"description": "Error when report generation fails."
335339
},
336340
"githubUserNotFoundError": {
337-
"message": "GitHub user \"$1\" not found (404). Please check the username and try again.",
341+
"message": "GitHub user \"$1\" not found.",
338342
"description": "Error when GitHub user is not found."
339343
},
340344
"githubUserValidationError": {
@@ -386,7 +390,7 @@
386390
"description": "Error when fetching GitLab user fails. $1=status, $2=statusText"
387391
},
388392
"gitlabUserNotFoundError": {
389-
"message": "GitLab user '$1' not found",
393+
"message": "GitLab user '$1' not found.",
390394
"description": "Error when GitLab user is not found. $1=username"
391395
},
392396
"gitlabMembershipError": {
@@ -433,4 +437,4 @@
433437
"message": "Report inserted into email!",
434438
"description": "Notification shown when report is successfully inserted into email."
435439
}
436-
}
440+
}

src/index.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ a {
5858
-webkit-user-modify: read-only;
5959
}
6060

61+
.input-error {
62+
outline: 2px solid red;
63+
outline-offset: 1px;
64+
}
65+
66+
.errorMessage {
67+
color: red;
68+
font-size: 12px;
69+
}
70+
71+
.error-message {
72+
color: #dc2626;
73+
font-weight: bold;
74+
padding: 10px;
75+
}
76+
6177
.scrum-actions {
6278
display: flex;
6379
gap: 4px;
@@ -78,6 +94,13 @@ a {
7894
white-space: nowrap;
7995
}
8096

97+
.scrum-actions button:disabled {
98+
background-color: #2564ebc5;
99+
cursor: not-allowed;
100+
opacity: 1;
101+
box-shadow: none;
102+
}
103+
81104
.btn,
82105
.btn-large {
83106
background-color: #3f51b5;

src/popup.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,16 @@ <h3 id="scrumHelperHeading" class="text-3xl font-semibold cursor-pointer">Scrum
169169
<div>
170170

171171
<p id="usernameLabel" class="font-medium text-sm" data-i18n="usernameLabel">Your Username</p>
172-
<input id="platformUsername" type="text"
172+
<input id="platformUsername" type="text" aria-describedby="usernameError"
173173
class="w-full border-2 border-gray-200 bg-gray-200 rounded-xl text-gray-800 p-2 my-2"
174174
data-i18n-placeholder="githubUsernamePlaceholder"
175-
style="margin-top: 0.3rem; margin-bottom: 0.8rem;">
175+
style="margin-top: 0.3rem; margin-bottom: 0.2rem;">
176+
<span id="usernameError" class="" role="alert" aria-live="polite"></span>
177+
176178
</div>
177179

178180
<div>
179-
<p class="text-lg mb-1 font-medium text-sm" data-i18n="contributionsLabel">Fetch your
181+
<p class="text-lg mb-1 font-medium text-sm" data-i18n="contributionsLabel" style="margin-top: 0.6rem;">Fetch your
180182
contributions between:</p>
181183

182184

@@ -323,7 +325,7 @@ <h6 class="text-base font-semibold mt-2" data-i18n="scrumReportLabel">Scrum Repo
323325

324326
<div class="scrum-actions my-2">
325327
<div class="tooltip-container">
326-
<button id="generateReport"
328+
<button id="generateReport" disabled
327329
class="flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 rounded"
328330
style="padding-left: 0.875rem; padding-right: 0.875rem;">
329331
<i class="fa fa-refresh"></i> <span data-i18n="generateReportButton">Generate</span>

src/scripts/popup.js

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -423,31 +423,78 @@ document.addEventListener('DOMContentLoaded', () => {
423423
generateBtn.disabled = true;
424424
}
425425

426-
function showPopupMessage(message) {
427-
if (!message) return;
428-
429-
// Materialize toast if available
430-
if (window.Materialize && typeof window.Materialize.toast === 'function') {
431-
window.Materialize.toast(message, 4000);
426+
function updateGenerateButtonState() {
427+
const generateBtn = document.getElementById('generateReport');
428+
const platformUsername = document.getElementById('platformUsername');
429+
if (!generateBtn || !platformUsername) {
432430
return;
433431
}
434432

433+
const applyGenerateButtonState = () => {
434+
const hasUsername = Boolean(platformUsername.value.trim());
435+
const shouldDisable = !hasUsername;
436+
437+
if (generateBtn.disabled !== shouldDisable) {
438+
generateBtn.disabled = shouldDisable;
439+
}
440+
};
441+
442+
if (!platformUsername.dataset.generateButtonStateBound) {
443+
platformUsername.addEventListener('input', applyGenerateButtonState);
444+
platformUsername.addEventListener('change', applyGenerateButtonState);
445+
platformUsername.dataset.generateButtonStateBound = 'true';
446+
}
447+
448+
if (!generateBtn.dataset.generateButtonStateObserved && typeof MutationObserver !== 'undefined') {
449+
const observer = new MutationObserver(() => {
450+
const shouldDisable = !platformUsername.value.trim();
451+
if (shouldDisable && generateBtn.disabled === false) {
452+
generateBtn.disabled = true;
453+
}
454+
});
455+
456+
observer.observe(generateBtn, {
457+
attributes: true,
458+
attributeFilter: ['disabled']
459+
});
460+
461+
generateBtn.dataset.generateButtonStateObserved = 'true';
462+
}
463+
464+
applyGenerateButtonState();
465+
}
466+
467+
window.updateGenerateButtonState = updateGenerateButtonState;
468+
469+
function showPopupMessage(message) {
470+
if (!message) return;
471+
const isDarkMode = document.body?.classList.contains('dark-mode');
472+
435473
const old = document.getElementById('scrum-cache-toast');
436474
if (old) old.remove();
437475

438476
const toast = document.createElement('div');
439477
toast.id = 'scrum-cache-toast';
440-
toast.className = 'toast';
441-
toast.style.background = '#2563eb';
442-
toast.style.color = '#fff';
478+
toast.className = 'scrum-cache-toast-custom';
479+
toast.style.background = isDarkMode ? '#ffffff' : '#1f2937';
480+
toast.style.color = isDarkMode ? '#1f2937' : '#fff';
481+
toast.style.border = 'none';
443482
toast.style.fontWeight = 'bold';
444-
toast.style.padding = '12px 24px';
483+
toast.style.padding = '12px 16px';
445484
toast.style.borderRadius = '8px';
446485
toast.style.position = 'fixed';
447-
toast.style.top = '24px';
486+
toast.style.top = '12px';
448487
toast.style.left = '50%';
449488
toast.style.transform = 'translateX(-50%)';
450-
toast.style.zIndex = '9999';
489+
toast.style.zIndex = '2147483647';
490+
toast.style.width = 'calc(82% - 16px)';
491+
toast.style.maxWidth = '340px';
492+
toast.style.lineHeight = '1.4';
493+
toast.style.textAlign = 'start';
494+
toast.style.boxSizing = 'border-box';
495+
toast.style.wordBreak = 'break-word';
496+
toast.style.opacity = '1';
497+
toast.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
451498
toast.textContent = message;
452499

453500
document.body.appendChild(toast);
@@ -581,6 +628,7 @@ document.addEventListener('DOMContentLoaded', () => {
581628
const startingDateInput = document.getElementById('startingDate');
582629
const endingDateInput = document.getElementById('endingDate');
583630
const platformUsername = document.getElementById('platformUsername');
631+
const usernameError = document.getElementById("usernameError");
584632

585633
browser.storage.local
586634
.get([
@@ -663,6 +711,7 @@ document.addEventListener('DOMContentLoaded', () => {
663711
const platform = result.platform || 'github';
664712
const platformUsernameKey = `${platform}Username`;
665713
platformUsername.value = result[platformUsernameKey] || '';
714+
window.updateGenerateButtonState && window.updateGenerateButtonState();
666715
checkTokenForShowCommits();
667716
checkTokenForMergedPRs();
668717
},
@@ -684,40 +733,49 @@ document.addEventListener('DOMContentLoaded', () => {
684733
return;
685734
}
686735

687-
browser.tabs
688-
.query({ active: true, currentWindow: true })
736+
// Helper to handle insert-to-email failures consistently
737+
const handleInsertFailure = (errorMsg) => {
738+
console.warn('Insert to Email failed:', errorMsg);
739+
const failureMessage =
740+
browser.i18n.getMessage('insertToEmailFailedError') ||
741+
'open an email tab to insert report';
742+
showPopupMessage(failureMessage);
743+
};
744+
745+
browser.tabs.query({ active: true, currentWindow: true })
689746
.then((tabs) => {
690747
const tabId = tabs?.[0]?.id;
691748
if (!tabId) {
692-
insertBtn._triggeredByShortcut = false;
749+
handleInsertFailure('No active tab found');
693750
return;
694751
}
695752

696-
browser.tabs
697-
.sendMessage(tabId, { action: 'insertReportToEmail', content, subject })
753+
browser.tabs.sendMessage(tabId, { action: 'insertReportToEmail', content, subject })
698754
.then((response) => {
699-
if (response?.success && insertBtn._triggeredByShortcut) {
755+
if (!response?.success) {
756+
handleInsertFailure(response?.error);
757+
} else if (insertBtn._triggeredByShortcut) {
700758
showShortcutNotification('insertedInEmailNotification');
701-
} else if (!response?.success) {
702-
console.warn('Insert to Email failed:', response?.error);
703759
}
704760
})
705761
.catch((error) => {
706-
console.warn('Insert to Email failed:', error?.message || error);
707-
})
708-
.finally(() => {
709-
insertBtn._triggeredByShortcut = false;
762+
handleInsertFailure(error.message);
710763
});
711764
})
712765
.catch((error) => {
713-
console.warn('Unable to get active tab:', error?.message || error);
766+
handleInsertFailure('Failed to query tabs: ' + error.message);
767+
})
768+
.finally(() => {
714769
insertBtn._triggeredByShortcut = false;
715770
});
716771
});
717772
}
718773

719774
generateBtn.addEventListener('click', () => {
720775
browser.storage.local.get(['platform']).then((result) => {
776+
platformUsername.classList.remove("input-error");
777+
usernameError.classList.remove("errorMessage");
778+
usernameError.textContent = "";
721779
const platform = result.platform || 'github';
722780
const platformUsernameKey = `${platform}Username`;
723781

@@ -1032,13 +1090,14 @@ document.addEventListener('DOMContentLoaded', () => {
10321090
startingDateInput.max = today;
10331091
endingDateInput.max = today;
10341092

1035-
1093+
// Save username to storage on input and update button state
10361094
platformUsername.addEventListener('input', () => {
10371095
browser.storage.local.get(['platform']).then((result) => {
10381096
const platform = result.platform || 'github';
10391097
const platformUsernameKey = `${platform}Username`;
10401098
browser.storage.local.set({ [platformUsernameKey]: platformUsername.value });
10411099
});
1100+
window.updateGenerateButtonState && window.updateGenerateButtonState();
10421101
});
10431102
// Bootstrap report on popup open (restore cache / auto-generate / expired-cache toast)
10441103
bootstrapScrumReportOnPopupLoad(generateBtn).catch((err) => {
@@ -1113,7 +1172,7 @@ document.addEventListener('DOMContentLoaded', () => {
11131172
try {
11141173
const items = await browser.storage.local.get(['platform']);
11151174
platform = items.platform || 'github';
1116-
} catch {}
1175+
} catch { }
11171176
if (platform !== 'github') {
11181177
// Do not run repo fetch for non-GitHub platforms
11191178
if (repoStatus) repoStatus.textContent = chrome?.i18n.getMessage('repoFilteringGithubOnly') || 'Repository filtering is only available for GitHub.';
@@ -1201,7 +1260,7 @@ document.addEventListener('DOMContentLoaded', () => {
12011260
try {
12021261
const items = await browser.storage.local.get(['platform']);
12031262
platform = items.platform || 'github';
1204-
} catch {}
1263+
} catch { }
12051264
if (platform !== 'github') {
12061265
repoFilterContainer.classList.add('hidden');
12071266
useRepoFilter.checked = false;
@@ -1382,7 +1441,7 @@ document.addEventListener('DOMContentLoaded', () => {
13821441
try {
13831442
const items = await browser.storage.local.get(['platform']);
13841443
platform = items.platform || 'github';
1385-
} catch {}
1444+
} catch { }
13861445
if (platform !== 'github') {
13871446
if (repoStatus) repoStatus.textContent = chrome?.i18n.getMessage('repoLoadingGithubOnly') || 'Repository loading is only available for GitHub.';
13881447
return;
@@ -1422,7 +1481,7 @@ document.addEventListener('DOMContentLoaded', () => {
14221481
try {
14231482
const items = await browser.storage.local.get(['platform']);
14241483
platform = items.platform || 'github';
1425-
} catch (e) {}
1484+
} catch (e) { }
14261485
if (platform !== 'github') {
14271486
if (repoStatus) repoStatus.textContent = chrome?.i18n.getMessage('repoFetchingGithubOnly') || 'Repository fetching is only available for GitHub.';
14281487
return;
@@ -1747,6 +1806,7 @@ platformSelect.addEventListener('change', () => {
17471806
browser.storage.local.get([`${platform}Username`]).then((result) => {
17481807
if (platformUsername) {
17491808
platformUsername.value = result[`${platform}Username`] || '';
1809+
window.updateGenerateButtonState && window.updateGenerateButtonState();
17501810
}
17511811
});
17521812

@@ -1798,6 +1858,7 @@ function setPlatformDropdown(value) {
17981858
browser.storage.local.get([`${value}Username`]).then((result) => {
17991859
if (platformUsername) {
18001860
platformUsername.value = result[`${value}Username`] || '';
1861+
window.updateGenerateButtonState && window.updateGenerateButtonState();
18011862
}
18021863
});
18031864

@@ -1814,6 +1875,11 @@ dropdownList.querySelectorAll('li').forEach((item) => {
18141875
item.addEventListener('click', function (e) {
18151876
const newPlatform = this.getAttribute('data-value');
18161877
const currentPlatform = platformSelectHidden.value;
1878+
const platformUsername = document.getElementById('platformUsername');
1879+
const usernameError = document.getElementById("usernameError");
1880+
platformUsername.classList.remove("input-error");
1881+
usernameError.classList.remove("errorMessage");
1882+
usernameError.textContent = "";
18171883

18181884
if (newPlatform !== currentPlatform) {
18191885
const platformUsername = document.getElementById('platformUsername');
@@ -1993,7 +2059,7 @@ document.getElementById('refreshCache').addEventListener('click', async function
19932059
try {
19942060
const items = await browser.storage.local.get(['platform']);
19952061
platform = items.platform || 'github';
1996-
} catch (e) {}
2062+
} catch (e) { }
19972063

19982064
// Clear all caches
19992065
const keysToRemove = ['githubCache', 'repoCache', 'gitlabCache'];

0 commit comments

Comments
 (0)