diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json
index c354282..411741d 100644
--- a/src/_locales/de/messages.json
+++ b/src/_locales/de/messages.json
@@ -75,6 +75,15 @@
"githubTokenPlaceholder": {
"message": "Erforderlich für authentifizierte Anfragen"
},
+ "gitlabTokenLabel": {
+ "message": "Ihr GitLab-Token"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Für authentifizierte Anfragen erforderlich"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Warum wird empfohlen, einen GitLab Token hinzuzufügen?
Scrum Helper funktioniert ohne GitLab Token, aber das Hinzufügen eines persönlichen Zugriffstokens wird für eine bessere Erfahrung empfohlen. Es erhöht Ihre API-Limits, ermöglicht den Zugriff auf private Repositories (falls erlaubt) und verbessert Genauigkeit und Geschwindigkeit. Tokens werden lokal gespeichert und niemals an uns gesendet und nur verwendet, um Ihre Git-Daten abzurufen. Sie können jederzeit einen in den Erweiterungseinstellungen hinzufügen.
Wie man erhält:
1. Gehen Sie zu GitLab Personal Access Tokens.
2. Klicken Sie auf \"Persönlichen Zugriffstoken erstellen\"
3. Geben Sie ihm einen Namen und wählen Sie entsprechende Bereiche (read_api, read_user, read_repository)
4. Fügen Sie ihn hier ein.
Halten Sie Ihren Token geheim!"
+ },
"showCommitsLabel": {
"message": "Commits in offenen PRs/ Entwurfs-PRs anzeigen"
},
diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index 188e9cd..37a9974 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -1,4 +1,5 @@
{
+
"appName": {
"message": "Scrum Helper",
"description": "The name of the extension."
@@ -256,3 +257,4 @@
"description": "Label for the username input header."
}
}
+
diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json
index 22b286b..e2ae911 100644
--- a/src/_locales/es/messages.json
+++ b/src/_locales/es/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "Requerido para realizar solicitudes autenticadas"
},
+ "gitlabTokenLabel": {
+ "message": "Su token de GitLab"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Requerido para realizar solicitudes autenticadas"
+ },
+ "gitlabTokenTooltip": {
+ "message": "¿Por qué se recomienda agregar un token de GitLab?
Scrum Helper funciona sin un token de GitLab, pero agregar un token de acceso personal es recomendado para una mejor experiencia. Aumenta tus límites de API, permite acceso a repositorios privados (si está permitido) y mejora la precisión y velocidad. Los tokens se almacenan localmente y nunca se envían a nosotros y solo se usan para obtener tus datos de git. Puedes agregar uno en cualquier momento en la configuración de la extensión.
Cómo obtener:
1. Ve a GitLab Personal Access Tokens.
2. Haz clic en \"Crear token de acceso personal\"
3. Dale un nombre y selecciona los ámbitos apropiados (read_api, read_user, read_repository)
4. Pégalo aquí.
¡Mantén tu token en secreto!"
+ },
"showCommitsLabel": {
"message": "Mostrar commits en PRs abiertos/ borradores de PRs"
},
diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json
index b8c7017..b1186dd 100644
--- a/src/_locales/fr/messages.json
+++ b/src/_locales/fr/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "Requis pour les requêtes authentifiées"
},
+ "gitlabTokenLabel": {
+ "message": "Votre jeton GitLab"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Requis pour les requêtes authentifiées"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Pourquoi est-il recommandé d'ajouter un jeton GitLab ?
Scrum Helper fonctionne sans jeton GitLab, mais ajouter un jeton d'accès personnel est recommandé pour une meilleure expérience. Il augmente vos limites d'API, permet l'accès aux dépôts privés (si autorisé) et améliore la précision et la vitesse. Les jetons sont stockés localement et ne sont jamais envoyés à nous et utilisés uniquement pour récupérer vos données git. Vous pouvez en ajouter un à tout moment dans les paramètres de l'extension.
Comment obtenir :
1. Allez sur GitLab Personal Access Tokens.
2. Cliquez sur \"Créer un jeton d'accès personnel\"
3. Donnez-lui un nom et sélectionnez les portées appropriées (read_api, read_user, read_repository)
4. Collez-le ici.
Gardez votre jeton secret !"
+ },
"showCommitsLabel": {
"message": "Afficher les commits sur les PRs ouverts/ brouillons de PRs"
},
diff --git a/src/_locales/hi/messages.json b/src/_locales/hi/messages.json
index 668b593..33b1a9a 100644
--- a/src/_locales/hi/messages.json
+++ b/src/_locales/hi/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "प्रमाणित अनुरोधों के लिए आवश्यक"
},
+ "gitlabTokenLabel": {
+ "message": "आपका GitLab टोकन"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "प्रमाणित अनुरोधों के लिए आवश्यक"
+ },
+ "gitlabTokenTooltip": {
+ "message": "GitLab टोकन जोड़ने की सिफारिश क्यों की जाती है?
Scrum Helper GitLab टोकन के बिना काम करता है, लेकिन बेहतर अनुभव के लिए व्यक्तिगत एक्सेस टोकन जोड़ने की सिफारिश की जाती है। यह आपकी API सीमाओं को बढ़ाता है, निजी रिपॉजिटरी तक पहुंच की अनुमति देता है (यदि अनुमति दी गई है) और सटीकता और गति में सुधार करता है। टोकन स्थानीय रूप से संग्रहीत किए जाते हैं और कभी भी हमें नहीं भेजे जाते और केवल आपके git डेटा को प्राप्त करने के लिए उपयोग किए जाते हैं। आप एक्सटेंशन सेटिंग्स में किसी भी समय एक जोड़ सकते हैं।
कैसे प्राप्त करें:
1. GitLab Personal Access Tokens पर जाएं।
2. \"Create personal access token\" पर क्लिक करें
3. इसे एक नाम दें और उचित स्कोप चुनें (read_api, read_user, read_repository)
4. इसे यहां पेस्ट करें।
अपना टोकन गुप्त रखें!"
+ },
"showCommitsLabel": {
"message": "खुले पीआर/ ड्राफ्ट पीआर पर कमिट दिखाएं"
},
diff --git a/src/_locales/id/messages.json b/src/_locales/id/messages.json
index 53718c7..796e3a2 100644
--- a/src/_locales/id/messages.json
+++ b/src/_locales/id/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "Diperlukan untuk permintaan terotentikasi"
},
+ "gitlabTokenLabel": {
+ "message": "Token GitLab Anda"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Diperlukan untuk permintaan terotentikasi"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Mengapa disarankan untuk menambahkan token GitLab?
Scrum Helper bekerja tanpa token GitLab, tetapi menambahkan token akses pribadi disarankan untuk pengalaman yang lebih baik. Ini meningkatkan batas API Anda, memungkinkan akses ke repositori pribadi (jika diizinkan), dan meningkatkan akurasi dan kecepatan. Token disimpan secara lokal dan tidak pernah dikirim kepada kami dan hanya digunakan untuk mengambil data git Anda. Anda dapat menambahkan satu kapan saja di pengaturan ekstensi.
Cara mendapatkan:
1. Kunjungi GitLab Personal Access Tokens.
2. Klik \"Create personal access token\"
3. Beri nama dan pilih cakupan yang sesuai (read_api, read_user, read_repository)
4. Tempel di sini.
Jaga kerahasiaan token Anda!"
+ },
"showCommitsLabel": {
"message": "Tampilkan commit pada PR terbuka/ draf PR"
},
diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json
index 80afc75..e587c4b 100644
--- a/src/_locales/ja/messages.json
+++ b/src/_locales/ja/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "認証済みリクエストに必要です"
},
+ "gitlabTokenLabel": {
+ "message": "GitLabトークン"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "認証済みリクエストに必要です"
+ },
+ "gitlabTokenTooltip": {
+ "message": "GitLabトークンを追加することが推奨される理由は?
Scrum HelperはGitLabトークンなしでも動作しますが、より良い体験のために個人アクセストークンを追加することが推奨されます。API制限を引き上げ、プライベートリポジトリへのアクセスを許可し(許可されている場合)、精度と速度を向上させます。トークンはローカルに保存され、私たちに送信されることはなく、あなたのgitデータを取得するためにのみ使用されます。拡張機能の設定でいつでも追加できます。
取得方法:
1. GitLab Personal Access Tokensに移動します。
2. \"Create personal access token\"をクリックします
3.名前を付けて適切なスコープを選択します(read_api, read_user, read_repository)
4.ここに貼り付けます。
トークンを秘密に保ってください!"
+ },
"showCommitsLabel": {
"message": "オープンなPR/ドラフトPRのコミットを表示"
},
diff --git a/src/_locales/pt/messages.json b/src/_locales/pt/messages.json
index 0a2e611..a1bee81 100644
--- a/src/_locales/pt/messages.json
+++ b/src/_locales/pt/messages.json
@@ -75,6 +75,15 @@
"githubTokenPlaceholder": {
"message": "Necessário para fazer solicitações autenticadas"
},
+ "gitlabTokenLabel": {
+ "message": "Seu Token GitLab"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Necessário para fazer solicitações autenticadas"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Por que é recomendado adicionar um token GitLab?
O Scrum Helper funciona sem um token GitLab, mas adicionar um token de acesso pessoal é recomendado para uma melhor experiência. Ele aumenta seus limites de API, permite acesso a repositórios privados (se permitido) e melhora a precisão e velocidade. Os tokens são armazenados localmente e nunca são enviados para nós e usados apenas para buscar seus dados git. Você pode adicionar um a qualquer momento nas configurações da extensão.
Como obter:
1. Vá para GitLab Personal Access Tokens.
2. Clique em \"Create personal access token\"
3. Dê um nome e selecione os escopos apropriados (read_api, read_user, read_repository)
4. Cole aqui.
Mantenha seu token secreto!"
+ },
"showCommitsLabel": {
"message": "Mostrar commits em PRs abertos/ rascunhos de PRs"
},
diff --git a/src/_locales/pt_BR/messages.json b/src/_locales/pt_BR/messages.json
index 554aa84..b9c7794 100644
--- a/src/_locales/pt_BR/messages.json
+++ b/src/_locales/pt_BR/messages.json
@@ -75,6 +75,15 @@
"githubTokenPlaceholder": {
"message": "Necessário para solicitações autenticadas"
},
+ "gitlabTokenLabel": {
+ "message": "Seu Token do GitLab"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Necessário para solicitações autenticadas"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Por que é recomendado adicionar um token do GitLab?
O Scrum Helper funciona sem um token do GitLab, mas adicionar um token de acesso pessoal é recomendado para uma melhor experiência. Ele aumenta seus limites de API, permite acesso a repositórios privados (se permitido) e melhora a precisão e velocidade. Os tokens são armazenados localmente e nunca são enviados para nós e usados apenas para buscar seus dados git. Você pode adicionar um a qualquer momento nas configurações da extensão.
Como obter:
1. Vá para GitLab Personal Access Tokens.
2. Clique em \"Create personal access token\"
3. Dê um nome e selecione os escopos apropriados (read_api, read_user, read_repository)
4. Cole aqui.
Mantenha seu token secreto!"
+ },
"showCommitsLabel": {
"message": "Mostrar commits em PRs abertos/ rascunhos de PRs"
},
diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json
index 47e8743..bc08373 100644
--- a/src/_locales/ru/messages.json
+++ b/src/_locales/ru/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "Требуется для аутентифицированных запросов"
},
+ "gitlabTokenLabel": {
+ "message": "Ваш токен GitLab"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Требуется для аутентифицированных запросов"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Почему рекомендуется добавить GitLab токен?
Scrum Helper работает без GitLab токена, но добавление персонального токена доступа рекомендуется для лучшего опыта. Это повышает ваши лимиты API, позволяет доступ к приватным репозиториям (если разрешено) и улучшает точность и скорость. Токены хранятся локально и никогда не отправляются нам и используются только для получения ваших git данных. Вы можете добавить один в любое время в настройках расширения.
Как получить:
1. Перейдите на GitLab Personal Access Tokens.
2. Нажмите \"Create personal access token\"
3. Дайте ему имя и выберите соответствующие области (read_api, read_user, read_repository)
4. Вставьте его сюда.
Держите ваш токен в секрете!"
+ },
"showCommitsLabel": {
"message": "Показывать коммиты в открытых PR/ черновиках PR"
},
diff --git a/src/_locales/vi/messages.json b/src/_locales/vi/messages.json
index 94b44f8..17221ea 100644
--- a/src/_locales/vi/messages.json
+++ b/src/_locales/vi/messages.json
@@ -74,6 +74,15 @@
"githubTokenPlaceholder": {
"message": "Bắt buộc cho các yêu cầu đã xác thực"
},
+ "gitlabTokenLabel": {
+ "message": "Mã token GitLab của bạn"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "Bắt buộc cho các yêu cầu đã xác thực"
+ },
+ "gitlabTokenTooltip": {
+ "message": "Tại sao nên thêm token GitLab?
Scrum Helper hoạt động mà không cần token GitLab, nhưng việc thêm token truy cập cá nhân được khuyến nghị để có trải nghiệm tốt hơn. Nó tăng giới hạn API của bạn, cho phép truy cập vào các repository riêng tư (nếu được phép) và cải thiện độ chính xác và tốc độ. Token được lưu trữ cục bộ và không bao giờ được gửi đến chúng tôi và chỉ được sử dụng để lấy dữ liệu git của bạn. Bạn có thể thêm một token bất cứ lúc nào trong cài đặt tiện ích mở rộng.
Cách lấy:
1. Truy cập GitLab Personal Access Tokens.
2. Nhấp vào \"Create personal access token\"
3. Đặt tên và chọn phạm vi thích hợp (read_api, read_user, read_repository)
4. Dán vào đây.
Giữ token của bạn bí mật!"
+ },
"showCommitsLabel": {
"message": "Hiển thị các commit trên PR đang mở/ PR nháp"
},
diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json
index 452e6c1..7f84d5a 100644
--- a/src/_locales/zh_CN/messages.json
+++ b/src/_locales/zh_CN/messages.json
@@ -75,6 +75,15 @@
"githubTokenPlaceholder": {
"message": "进行身份验证请求所必需"
},
+ "gitlabTokenLabel": {
+ "message": "您的 GitLab 令牌"
+ },
+ "gitlabTokenPlaceholder": {
+ "message": "进行身份验证请求所必需"
+ },
+ "gitlabTokenTooltip": {
+ "message": "为什么建议添加 GitLab 令牌?
Scrum Helper 无需 GitLab 令牌即可工作,但建议添加个人访问令牌以获得更好的体验。它会提高您的 API 限制,允许访问私有仓库(如果允许),并提高准确性和速度。令牌存储在本地,永远不会发送给我们,仅用于获取您的 git 数据。您可以随时在扩展设置中添加一个。
如何获取:
1. 访问 GitLab Personal Access Tokens。
2. 点击 \"Create personal access token\"
3. 给它一个名称并选择适当的范围(read_api, read_user, read_repository)
4. 粘贴到这里。
保持您的令牌秘密!"
+ },
"showCommitsLabel": {
"message": "在打开的 PR/草稿 PR 上显示提交"
},
diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json
index 608a922..3d26a3e 100644
--- a/src/_locales/zh_TW/messages.json
+++ b/src/_locales/zh_TW/messages.json
@@ -1,4 +1,5 @@
{
+
"appName": {
"message": "Scrum Helper"
},
@@ -195,3 +196,4 @@
"message": "您的用戶名"
}
}
+
diff --git a/src/popup.html b/src/popup.html
index 04c2900..cd6e7f9 100644
--- a/src/popup.html
+++ b/src/popup.html
@@ -223,7 +223,8 @@
Your GitLab Token
+
+
+ Why is it recommended to add a GitLab token?
+ Scrum Helper works without a GitLab token, but adding a personal access
+ token is recommended for a
+ better experience. It raises your API limits, allows access to private
+ repos
+ (if permitted), and
+ improves accuracy and speed. Tokens are stored locally and never sent to
+ us
+ and used only to fetch
+ your git data. You can add one anytime in the extension
+ settings.
+ How to obtain:
+ 1. Go to GitLab Personal Access
+ Tokens.
+ 2. Click "Create personal access token"
+ 3. Give it a name and select appropriate scopes (read_api, read_user, read_repository)
+ 4. Paste it here.
+ Keep your token secret!
+
+
Your Organization Name diff --git a/src/scripts/emailClientAdapter.js b/src/scripts/emailClientAdapter.js index 392b4c5..964768b 100644 --- a/src/scripts/emailClientAdapter.js +++ b/src/scripts/emailClientAdapter.js @@ -1,38 +1,37 @@ - class EmailClientAdapter { isNewConversation() { - const clientType = this.detectClient(); - if (!clientType) return false; - const elements = this.getEditorElements(); - if (!elements || !elements.subject) return false; - const currentSubject = elements.subject.value || ''; - const isReplySubject = currentSubject.startsWith('Re:') || currentSubject.startsWith('Fwd:'); - let isReplyContext = false; - - switch (clientType) { - case 'gmail': { - const editor = document.querySelector('.Am.Al.editable.LW-avf'); - const isNewWindow = editor ? !!editor.closest('div[role="dialog"]') : false; - isReplyContext = !isNewWindow; - break; - } - - case 'outlook': { - isReplyContext = !!document.querySelector('[aria-label="Reply"]'); - break; - } - - case 'yahoo': { - const header = document.querySelector('[data-test-id="compose-header-title"]'); - if (header) { - const title = header.innerText.trim().toLowerCase(); - isReplyContext = title.includes('reply') || title.includes('forward'); - } - break; - } - } - return !(isReplySubject || isReplyContext); -} + const clientType = this.detectClient(); + if (!clientType) return false; + const elements = this.getEditorElements(); + if (!elements || !elements.subject) return false; + const currentSubject = elements.subject.value || ''; + const isReplySubject = currentSubject.startsWith('Re:') || currentSubject.startsWith('Fwd:'); + let isReplyContext = false; + + switch (clientType) { + case 'gmail': { + const editor = document.querySelector('.Am.Al.editable.LW-avf'); + const isNewWindow = editor ? !!editor.closest('div[role="dialog"]') : false; + isReplyContext = !isNewWindow; + break; + } + + case 'outlook': { + isReplyContext = !!document.querySelector('[aria-label="Reply"]'); + break; + } + + case 'yahoo': { + const header = document.querySelector('[data-test-id="compose-header-title"]'); + if (header) { + const title = header.innerText.trim().toLowerCase(); + isReplyContext = title.includes('reply') || title.includes('forward'); + } + break; + } + } + return !(isReplySubject || isReplyContext); + } constructor() { this.clientConfigs = { 'google-groups': { @@ -45,7 +44,7 @@ class EmailClientAdapter { subjectChange: 'input', }, }, - 'gmail': { + gmail: { selectors: { body: 'div.editable.LW-avf[contenteditable="true"][role="textbox"]', subject: 'input[name="subjectbox"][tabindex="1"]', @@ -55,7 +54,7 @@ class EmailClientAdapter { subjectChange: 'input', }, }, - 'outlook': { + outlook: { selectors: { body: 'div[role="textbox"][contenteditable="true"][aria-multiline="true"]', subject: [ @@ -69,12 +68,12 @@ class EmailClientAdapter { }, injectMethod: 'focusAndPaste', // Custom injection method }, - 'yahoo': { + yahoo: { selectors: { body: [ // Desktop selectors '#editor-container [contenteditable="true"][role="textbox"]', - '[aria-multiline="true"][aria-label="Message body"][contenteditable="true"][role="textbox"]', + '[aria-multiline="true"][aria-label="Message body"][contenteditable="true"][role="textbox"]', '[aria-label="Message body"][contenteditable="true"]', '[role="textbox"][contenteditable="true"]', '[data-test-id*="compose"][contenteditable="true"]', @@ -85,12 +84,12 @@ class EmailClientAdapter { subject: [ // Desktop selectors '#compose-subject-input, input[placeholder="Subject"][id="compose-subject-input"]', - '#compose-subject-input', - 'input[placeholder="Subject"]', - 'input[aria-label*="subject" i]', - 'input[data-test-id*="subject" i]', + '#compose-subject-input', + 'input[placeholder="Subject"]', + 'input[aria-label*="subject" i]', + 'input[data-test-id*="subject" i]', // Mobile selectors - '#compose-subject-input-mobile, input[placeholder="Subject"][id="compose-subject-input-mobile"]' + '#compose-subject-input-mobile, input[placeholder="Subject"][id="compose-subject-input-mobile"]', ].join(', '), }, eventTypes: { @@ -165,7 +164,7 @@ class EmailClientAdapter { this.dispatchElementEvents(element, ['input', 'change'], true); break; - case 'setContent': + case 'setContent': { // Special handling for Yahoo element.innerHTML = content; element.focus(); @@ -177,6 +176,7 @@ class EmailClientAdapter { selection.addRange(range); this.dispatchElementEvents(element, ['input', 'change']); break; + } default: // Default handling for Google clients @@ -213,4 +213,4 @@ class EmailClientAdapter { // Create global instance window.emailClientAdapter = new EmailClientAdapter(); -console.log('Email client adapter initialized'); \ No newline at end of file +console.log('Email client adapter initialized'); diff --git a/src/scripts/gitlabHelper.js b/src/scripts/gitlabHelper.js index 5ff9698..17600db 100644 --- a/src/scripts/gitlabHelper.js +++ b/src/scripts/gitlabHelper.js @@ -1,275 +1,692 @@ // GitLab API Helper for Scrum Helper Extension class GitLabHelper { - constructor() { - this.baseUrl = 'https://gitlab.com/api/v4'; - this.cache = { - data: null, - cacheKey: null, - timestamp: 0, - ttl: 10 * 60 * 1000, // 10 minutes - fetching: false, - queue: [] - }; - } - - async getCacheTTL() { - return new Promise((resolve) => { - chrome.storage.local.get(['cacheInput'], (items) => { - const ttl = items.cacheInput ? parseInt(items.cacheInput) * 60 * 1000 : 10 * 60 * 1000; - resolve(ttl); - }); - }); - } - - async saveToStorage(data) { - return new Promise((resolve) => { - chrome.storage.local.set({ - gitlabCache: { - data: data, - cacheKey: this.cache.cacheKey, - timestamp: this.cache.timestamp - } - }, resolve); - }); - } - - async loadFromStorage() { - return new Promise((resolve) => { - chrome.storage.local.get(['gitlabCache'], (items) => { - if (items.gitlabCache) { - this.cache.data = items.gitlabCache.data; - this.cache.cacheKey = items.gitlabCache.cacheKey; - this.cache.timestamp = items.gitlabCache.timestamp; - console.log('Restored GitLab cache from storage'); - } - resolve(); - }); - }); - } - - async fetchGitLabData(username, startDate, endDate) { - const cacheKey = `${username}-${startDate}-${endDate}`; - - if (this.cache.fetching || (this.cache.cacheKey === cacheKey && this.cache.data)) { - console.log('GitLab fetch already in progress or data already fetched. Skipping fetch.'); - return this.cache.data; - } - - console.log('Fetching GitLab data:', { - username: username, - startDate: startDate, - endDate: endDate, - }); - - // Check if we need to load from storage - if (!this.cache.data && !this.cache.fetching) { - await this.loadFromStorage(); - } - - const currentTTL = await this.getCacheTTL(); - this.cache.ttl = currentTTL; - console.log(`GitLab caching for ${currentTTL / (60 * 1000)} minutes`); - - const now = Date.now(); - const isCacheFresh = (now - this.cache.timestamp) < this.cache.ttl; - const isCacheKeyMatch = this.cache.cacheKey === cacheKey; - - if (this.cache.data && isCacheFresh && isCacheKeyMatch) { - console.log('Using cached GitLab data - cache is fresh and key matches'); - return this.cache.data; - } - - if (!isCacheKeyMatch) { - console.log('GitLab cache key mismatch - fetching new data'); - this.cache.data = null; - } else if (!isCacheFresh) { - console.log('GitLab cache is stale - fetching new data'); - } - - if (this.cache.fetching) { - console.log('GitLab fetch in progress, queuing requests'); - return new Promise((resolve, reject) => { - this.cache.queue.push({ resolve, reject }); - }); - } - - this.cache.fetching = true; - this.cache.cacheKey = cacheKey; - - try { - // Throttling 500ms to avoid burst - await new Promise(res => setTimeout(res, 500)); - - // Get user info first - const userUrl = `${this.baseUrl}/users?username=${username}`; - const userRes = await fetch(userUrl); - if (!userRes.ok) { - throw new Error(`Error fetching GitLab user: ${userRes.status} ${userRes.statusText}`); - } - const users = await userRes.json(); - if (users.length === 0) { - throw new Error(`GitLab user '${username}' not found`); - } - const userId = users[0].id; - - // Fetch all projects the user is a member of (including group projects) - const membershipProjectsUrl = `${this.baseUrl}/users/${userId}/projects?membership=true&per_page=100&order_by=updated_at&sort=desc`; - const membershipProjectsRes = await fetch(membershipProjectsUrl); - if (!membershipProjectsRes.ok) { - throw new Error(`Error fetching GitLab membership projects: ${membershipProjectsRes.status} ${membershipProjectsRes.statusText}`); - } - const membershipProjects = await membershipProjectsRes.json(); - - // Fetch all projects the user has contributed to (public, group, etc.) - const contributedProjectsUrl = `${this.baseUrl}/users/${userId}/contributed_projects?per_page=100&order_by=updated_at&sort=desc`; - const contributedProjectsRes = await fetch(contributedProjectsUrl); - if (!contributedProjectsRes.ok) { - throw new Error(`Error fetching GitLab contributed projects: ${contributedProjectsRes.status} ${contributedProjectsRes.statusText}`); - } - const contributedProjects = await contributedProjectsRes.json(); - - // Merge and deduplicate projects by project id - const allProjectsMap = new Map(); - for (const p of [...membershipProjects, ...contributedProjects]) { - allProjectsMap.set(p.id, p); - } - const allProjects = Array.from(allProjectsMap.values()); - - // Fetch merge requests from each project (works without auth for public projects) - let allMergeRequests = []; - for (const project of allProjects) { - try { - const projectMRsUrl = `${this.baseUrl}/projects/${project.id}/merge_requests?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; - const projectMRsRes = await fetch(projectMRsUrl); - if (projectMRsRes.ok) { - const projectMRs = await projectMRsRes.json(); - allMergeRequests = allMergeRequests.concat(projectMRs); - } - // Add small delay to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - console.error(`Error fetching MRs for project ${project.name}:`, error); - // Continue with other projects - } - } - - // Fetch issues from each project (works without auth for public projects) - let allIssues = []; - for (const project of allProjects) { - try { - const projectIssuesUrl = `${this.baseUrl}/projects/${project.id}/issues?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; - const projectIssuesRes = await fetch(projectIssuesUrl); - if (projectIssuesRes.ok) { - const projectIssues = await projectIssuesRes.json(); - allIssues = allIssues.concat(projectIssues); - } - // Add small delay to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - console.error(`Error fetching issues for project ${project.name}:`, error); - // Continue with other projects - } - } - - const gitlabData = { - user: users[0], - projects: allProjects, - mergeRequests: allMergeRequests, // use project-by-project response - issues: allIssues, // use project-by-project response - comments: [] // Empty array since we're not fetching comments - }; - // Cache the data - this.cache.data = gitlabData; - this.cache.timestamp = Date.now(); - - await this.saveToStorage(gitlabData); - - // Resolve queued calls - this.cache.queue.forEach(({ resolve }) => resolve(gitlabData)); - this.cache.queue = []; - - return gitlabData; - - } catch (err) { - console.error('GitLab Fetch Failed:', err); - // Reject queued calls on error - this.cache.queue.forEach(({ reject }) => reject(err)); - this.cache.queue = []; - throw err; - } finally { - this.cache.fetching = false; - } - } - - async getDetailedMergeRequests(mergeRequests) { - const detailed = []; - for (const mr of mergeRequests) { - try { - const url = `${this.baseUrl}/projects/${mr.project_id}/merge_requests/${mr.iid}`; - const res = await fetch(url); - if (res.ok) { - const detailedMr = await res.json(); - detailed.push(detailedMr); - } - // Add small delay to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - console.error(`[GITLAB-DEBUG] Error fetching detailed MR ${mr.iid}:`, error); - detailed.push(mr); // Use basic data if detailed fetch fails - } - } - return detailed; - } - - async getDetailedIssues(issues) { - const detailed = []; - for (const issue of issues) { - try { - const url = `${this.baseUrl}/projects/${issue.project_id}/issues/${issue.iid}`; - const res = await fetch(url); - if (res.ok) { - const detailedIssue = await res.json(); - detailed.push(detailedIssue); - } - // Add small delay to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - console.error(`[GITLAB-DEBUG] Error fetching detailed issue ${issue.iid}:`, error); - detailed.push(issue); // Use basic data if detailed fetch fails - } - } - return detailed; - } - - formatDate(dateString) { - const date = new Date(dateString); - const options = { day: '2-digit', month: 'short', year: 'numeric' }; - return date.toLocaleDateString('en-US', options); - } - - processGitLabData(data) { - const processed = { - mergeRequests: data.mergeRequests || [], - issues: data.issues || [], - comments: data.comments || [], - user: data.user - }; - console.log('[GITLAB-DEBUG] processGitLabData input:', data); - console.log('[GITLAB-DEBUG] processGitLabData output:', processed); - console.log('GitLab data processed:', { - mergeRequests: processed.mergeRequests.length, - issues: processed.issues.length, - comments: processed.comments.length, - user: processed.user?.username - }); - - return processed; - } + constructor() { + this.baseUrl = 'https://gitlab.com/api/v4'; + this.graphqlUrl = 'https://gitlab.com/api/graphql'; + this.cache = { + data: null, + cacheKey: null, + timestamp: 0, + ttl: 10 * 60 * 1000, // 10 minutes + fetching: false, + queue: [], + }; + } + + async getCacheTTL() { + return new Promise((resolve) => { + chrome.storage.local.get(['cacheInput'], (items) => { + const ttl = items.cacheInput ? Number.parseInt(items.cacheInput) * 60 * 1000 : 10 * 60 * 1000; + resolve(ttl); + }); + }); + } + + async saveToStorage(data) { + return new Promise((resolve) => { + chrome.storage.local.set( + { + gitlabCache: { + data: data, + cacheKey: this.cache.cacheKey, + timestamp: this.cache.timestamp, + }, + }, + resolve, + ); + }); + } + + async loadFromStorage() { + return new Promise((resolve) => { + chrome.storage.local.get(['gitlabCache'], (items) => { + if (items.gitlabCache) { + this.cache.data = items.gitlabCache.data; + this.cache.cacheKey = items.gitlabCache.cacheKey; + this.cache.timestamp = items.gitlabCache.timestamp; + console.log('Restored GitLab cache from storage'); + } + resolve(); + }); + }); + } + + // Optimized GraphQL method for GitLab data fetching + async fetchGitLabDataGraphQL(username, startDate, endDate, token = null) { + const cacheKey = `${username}-${startDate}-${endDate}-${token ? 'auth-graphql' : 'public-graphql'}`; + + if (this.cache.fetching || (this.cache.cacheKey === cacheKey && this.cache.data)) { + console.log('GitLab GraphQL fetch already in progress or data already fetched. Skipping fetch.'); + return this.cache.data; + } + + console.log('Fetching GitLab data via GraphQL:', { + username: username, + startDate: startDate, + endDate: endDate, + hasToken: !!token, + }); + + // Check if we need to load from storage + if (!this.cache.data && !this.cache.fetching) { + await this.loadFromStorage(); + } + + const currentTTL = await this.getCacheTTL(); + this.cache.ttl = currentTTL; + console.log(`GitLab GraphQL caching for ${currentTTL / (60 * 1000)} minutes`); + + const now = Date.now(); + const isCacheFresh = now - this.cache.timestamp < this.cache.ttl; + const isCacheKeyMatch = this.cache.cacheKey === cacheKey; + + if (this.cache.data && isCacheFresh && isCacheKeyMatch) { + console.log('Using cached GitLab GraphQL data - cache is fresh and key matches'); + return this.cache.data; + } + + if (!isCacheKeyMatch) { + console.log('GitLab GraphQL cache key mismatch - fetching new data'); + this.cache.data = null; + } else if (!isCacheFresh) { + console.log('GitLab GraphQL cache is stale - fetching new data'); + } + + if (this.cache.fetching) { + console.log('GitLab GraphQL fetch in progress, queuing requests'); + return new Promise((resolve, reject) => { + this.cache.queue.push({ resolve, reject }); + }); + } + + this.cache.fetching = true; + this.cache.cacheKey = cacheKey; + + try { + // Throttling 500ms to avoid burst + await new Promise((res) => setTimeout(res, 500)); + + // Comprehensive GraphQL query for GitLab data + const graphqlQuery = ` + query($username: String!, $startDate: Time!, $endDate: Time!) { + user(username: $username) { + id + name + username + projects { + nodes { + id + name + path + webUrl + mergeRequests(authorUsernames: [$username], createdAfter: $startDate, createdBefore: $endDate) { + nodes { + id + iid + title + state + createdAt + updatedAt + webUrl + project { + id + name + path + } + } + } + issues(authorUsernames: [$username], createdAfter: $startDate, createdBefore: $endDate) { + nodes { + id + iid + title + state + createdAt + updatedAt + webUrl + project { + id + name + path + } + } + } + } + } + } + } + `; + + const variables = { + username: username, + startDate: `${startDate}T00:00:00Z`, + endDate: `${endDate}T23:59:59Z`, + }; + + const headers = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + const graphqlRes = await fetch(this.graphqlUrl, { + method: 'POST', + headers: headers, + body: JSON.stringify({ + query: graphqlQuery, + variables: variables, + }), + }); + + if (!graphqlRes.ok) { + throw new Error(`GraphQL request failed: ${graphqlRes.status} ${graphqlRes.statusText}`); + } + + const graphqlData = await graphqlRes.json(); + console.log('GraphQL response:', graphqlData); + + if (graphqlData.errors) { + console.error('GraphQL errors:', graphqlData.errors); + // Fallback to REST API if GraphQL fails + console.log('GraphQL failed, falling back to REST API...'); + this.cache.fetching = false; + return this.fetchGitLabDataREST(username, startDate, endDate, token); + } + + // Process GraphQL response + const user = graphqlData.data?.user; + if (!user) { + throw new Error(`GitLab user '${username}' not found`); + } + + // Extract data from GraphQL response + const projects = user.projects?.nodes || []; + const allMergeRequests = []; + const allIssues = []; + + for (const project of projects) { + // Add merge requests + if (project.mergeRequests?.nodes) { + for (const mr of project.mergeRequests.nodes) { + allMergeRequests.push({ + ...mr, + project_id: project.id, + web_url: mr.webUrl, + }); + } + } + + // Add issues + if (project.issues?.nodes) { + for (const issue of project.issues.nodes) { + allIssues.push({ + ...issue, + project_id: project.id, + web_url: issue.webUrl, + }); + } + } + } + + const gitlabData = { + user: { + id: user.id, + name: user.name, + username: user.username, + }, + projects: projects, + mergeRequests: allMergeRequests, + issues: allIssues, + comments: [], // GraphQL doesn't fetch comments by default + }; + + console.log('GitLab GraphQL data fetched:', { + user: gitlabData.user?.username, + projectsCount: gitlabData.projects?.length || 0, + mergeRequestsCount: gitlabData.mergeRequests?.length || 0, + issuesCount: gitlabData.issues?.length || 0, + }); + + // Cache the data + this.cache.data = gitlabData; + this.cache.timestamp = Date.now(); + + await this.saveToStorage(gitlabData); + + // Resolve queued calls + this.cache.queue.forEach(({ resolve }) => resolve(gitlabData)); + this.cache.queue = []; + + return gitlabData; + } catch (err) { + console.error('GitLab GraphQL Fetch Failed:', err); + // Fallback to REST API + console.log('Falling back to REST API...'); + this.cache.fetching = false; + return this.fetchGitLabDataREST(username, startDate, endDate, token); + } finally { + this.cache.fetching = false; + } + } + + // Optimized REST method with batch operations + async fetchGitLabDataREST(username, startDate, endDate, token = null) { + const cacheKey = `${username}-${startDate}-${endDate}-${token ? 'auth-rest' : 'public-rest'}`; + + // Reset cache state when falling back from GraphQL + if (this.cache.cacheKey?.includes('graphql')) { + console.log('Resetting cache state for REST fallback'); + this.cache.data = null; + this.cache.cacheKey = null; + this.cache.timestamp = 0; + this.cache.fetching = false; + } + + if (this.cache.fetching || (this.cache.cacheKey === cacheKey && this.cache.data)) { + console.log('GitLab REST fetch already in progress or data already fetched. Skipping fetch.'); + return this.cache.data; + } + + console.log('Fetching GitLab data via REST:', { + username: username, + startDate: startDate, + endDate: endDate, + hasToken: !!token, + }); + + // Check if we need to load from storage + if (!this.cache.data && !this.cache.fetching) { + await this.loadFromStorage(); + } + + const currentTTL = await this.getCacheTTL(); + this.cache.ttl = currentTTL; + console.log(`GitLab REST caching for ${currentTTL / (60 * 1000)} minutes`); + + const now = Date.now(); + const isCacheFresh = now - this.cache.timestamp < this.cache.ttl; + const isCacheKeyMatch = this.cache.cacheKey === cacheKey; + + if (this.cache.data && isCacheFresh && isCacheKeyMatch) { + console.log('Using cached GitLab REST data - cache is fresh and key matches'); + return this.cache.data; + } + + if (!isCacheKeyMatch) { + console.log('GitLab REST cache key mismatch - fetching new data'); + this.cache.data = null; + } else if (!isCacheFresh) { + console.log('GitLab REST cache is stale - fetching new data'); + } + + if (this.cache.fetching) { + console.log('GitLab REST fetch in progress, queuing requests'); + return new Promise((resolve, reject) => { + this.cache.queue.push({ resolve, reject }); + }); + } + + this.cache.fetching = true; + this.cache.cacheKey = cacheKey; + + try { + // Throttling 500ms to avoid burst + await new Promise((res) => setTimeout(res, 500)); + + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + + // Get user info first + const userUrl = `${this.baseUrl}/users?username=${username}`; + console.log('GitLab API call with headers:', { + url: userUrl, + hasToken: !!token, + headers: headers, + }); + const userRes = await fetch(userUrl, { headers: headers }); + console.log('GitLab user API response:', { + status: userRes.status, + statusText: userRes.statusText, + headers: Object.fromEntries(userRes.headers.entries()), + }); + if (!userRes.ok) { + throw new Error(`Error fetching GitLab user: ${userRes.status} ${userRes.statusText}`); + } + const users = await userRes.json(); + if (users.length === 0) { + throw new Error(`GitLab user '${username}' not found`); + } + const userId = users[0].id; + + // If we have a token, use optimized endpoints + if (token) { + console.log('Using authenticated endpoints for optimized data fetching'); + return await this.fetchGitLabDataAuthenticated(userId, username, startDate, endDate, token); + } else { + console.log('Using public endpoints for data fetching'); + return await this.fetchGitLabDataPublic(userId, username, startDate, endDate); + } + } catch (err) { + console.error('GitLab REST Fetch Failed:', err); + // Reject queued calls on error + this.cache.queue.forEach(({ reject }) => reject(err)); + this.cache.queue = []; + throw err; + } finally { + this.cache.fetching = false; + } + } + + // Optimized method for authenticated requests + async fetchGitLabDataAuthenticated(userId, username, startDate, endDate, token) { + const headers = { Authorization: `Bearer ${token}` }; + + // Get user's projects first (optimized with token) + const projectsUrl = `${this.baseUrl}/users/${userId}/projects?membership=true&per_page=100&order_by=updated_at&sort=desc`; + console.log('GitLab authenticated projects API call:', { + url: projectsUrl, + hasToken: !!token, + }); + + const projectsRes = await fetch(projectsUrl, { headers }); + if (!projectsRes.ok) { + throw new Error(`Error fetching GitLab projects: ${projectsRes.status} ${projectsRes.statusText}`); + } + + const projects = await projectsRes.json(); + console.log(`Fetched ${projects.length} GitLab projects`); + + // Use optimized endpoints to get merge requests and issues across all projects + const mergeRequests = []; + const issues = []; + + // Fetch merge requests using optimized endpoint + const mrsUrl = `${this.baseUrl}/merge_requests?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + console.log('GitLab authenticated MRs API call:', { + url: mrsUrl, + hasToken: !!token, + }); + + const mrsRes = await fetch(mrsUrl, { headers }); + if (mrsRes.ok) { + const mrs = await mrsRes.json(); + console.log(`Fetched ${mrs.length} merge requests`); + mergeRequests.push(...mrs); + } else { + console.warn('Failed to fetch merge requests via optimized endpoint, falling back to project-by-project'); + // Fallback to project-by-project for merge requests + for (const project of projects) { + try { + const projectMRsUrl = `${this.baseUrl}/projects/${project.id}/merge_requests?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + const projectMRsRes = await fetch(projectMRsUrl, { headers }); + if (projectMRsRes.ok) { + const projectMRs = await projectMRsRes.json(); + mergeRequests.push(...projectMRs); + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Error fetching MRs for project ${project.name}:`, error); + } + } + } + + // Fetch issues using optimized endpoint + const issuesUrl = `${this.baseUrl}/issues?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + console.log('GitLab authenticated Issues API call:', { + url: issuesUrl, + hasToken: !!token, + }); + + const issuesRes = await fetch(issuesUrl, { headers }); + if (issuesRes.ok) { + const fetchedIssues = await issuesRes.json(); + console.log(`Fetched ${fetchedIssues.length} issues`); + issues.push(...fetchedIssues); + } else { + console.warn('Failed to fetch issues via optimized endpoint, falling back to project-by-project'); + // Fallback to project-by-project for issues + for (const project of projects) { + try { + const projectIssuesUrl = `${this.baseUrl}/projects/${project.id}/issues?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + const projectIssuesRes = await fetch(projectIssuesUrl, { headers }); + if (projectIssuesRes.ok) { + const projectIssues = await projectIssuesRes.json(); + issues.push(...projectIssues); + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Error fetching issues for project ${project.name}:`, error); + } + } + } + + const gitlabData = { + user: { id: userId, username: username }, + projects: projects, + mergeRequests: mergeRequests, + issues: issues, + comments: [], + }; + + console.log('GitLab authenticated data fetched:', { + user: gitlabData.user?.username, + projectsCount: gitlabData.projects?.length || 0, + mergeRequestsCount: gitlabData.mergeRequests?.length || 0, + issuesCount: gitlabData.issues?.length || 0, + }); + + // Cache the data + this.cache.data = gitlabData; + this.cache.timestamp = Date.now(); + + await this.saveToStorage(gitlabData); + + // Resolve queued calls + this.cache.queue.forEach(({ resolve }) => resolve(gitlabData)); + this.cache.queue = []; + + return gitlabData; + } + + // Method for public API requests (fallback) + async fetchGitLabDataPublic(userId, username, startDate, endDate) { + // Fetch all projects the user is a member of (including group projects) + const membershipProjectsUrl = `${this.baseUrl}/users/${userId}/projects?membership=true&per_page=100&order_by=updated_at&sort=desc`; + console.log('GitLab membership projects API call:', { + url: membershipProjectsUrl, + }); + const membershipProjectsRes = await fetch(membershipProjectsUrl); + console.log('GitLab membership projects response:', { + status: membershipProjectsRes.status, + statusText: membershipProjectsRes.statusText, + rateLimitRemaining: membershipProjectsRes.headers.get('X-RateLimit-Remaining'), + rateLimitLimit: membershipProjectsRes.headers.get('X-RateLimit-Limit'), + }); + if (!membershipProjectsRes.ok) { + throw new Error( + `Error fetching GitLab membership projects: ${membershipProjectsRes.status} ${membershipProjectsRes.statusText}`, + ); + } + const membershipProjects = await membershipProjectsRes.json(); + + // Fetch all projects the user has contributed to (public, group, etc.) + const contributedProjectsUrl = `${this.baseUrl}/users/${userId}/contributed_projects?per_page=100&order_by=updated_at&sort=desc`; + const contributedProjectsRes = await fetch(contributedProjectsUrl); + if (!contributedProjectsRes.ok) { + throw new Error( + `Error fetching GitLab contributed projects: ${contributedProjectsRes.status} ${contributedProjectsRes.statusText}`, + ); + } + const contributedProjects = await contributedProjectsRes.json(); + + // Merge and deduplicate projects by project id + const allProjectsMap = new Map(); + for (const p of [...membershipProjects, ...contributedProjects]) { + allProjectsMap.set(p.id, p); + } + const allProjects = Array.from(allProjectsMap.values()); + + // Fetch merge requests from each project (works without auth for public projects) + let allMergeRequests = []; + for (const project of allProjects) { + try { + const projectMRsUrl = `${this.baseUrl}/projects/${project.id}/merge_requests?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + console.log('GitLab MR API call:', { + url: projectMRsUrl, + project: project.name, + }); + const projectMRsRes = await fetch(projectMRsUrl); + if (projectMRsRes.ok) { + const projectMRs = await projectMRsRes.json(); + allMergeRequests = allMergeRequests.concat(projectMRs); + } + // Add small delay to avoid rate limiting + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Error fetching MRs for project ${project.name}:`, error); + // Continue with other projects + } + } + + // Fetch issues from each project (works without auth for public projects) + let allIssues = []; + for (const project of allProjects) { + try { + const projectIssuesUrl = `${this.baseUrl}/projects/${project.id}/issues?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + console.log('GitLab Issues API call:', { + url: projectIssuesUrl, + project: project.name, + }); + const projectIssuesRes = await fetch(projectIssuesUrl); + if (projectIssuesRes.ok) { + const projectIssues = await projectIssuesRes.json(); + allIssues = allIssues.concat(projectIssues); + } + // Add small delay to avoid rate limiting + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Error fetching issues for project ${project.name}:`, error); + // Continue with other projects + } + } + + const gitlabData = { + user: { id: userId, username: username }, + projects: allProjects, + mergeRequests: allMergeRequests, + issues: allIssues, + comments: [], + }; + + console.log('GitLab public data fetched:', { + user: gitlabData.user?.username, + projectsCount: gitlabData.projects?.length || 0, + mergeRequestsCount: gitlabData.mergeRequests?.length || 0, + issuesCount: gitlabData.issues?.length || 0, + }); + + // Cache the data + this.cache.data = gitlabData; + this.cache.timestamp = Date.now(); + + await this.saveToStorage(gitlabData); + + // Resolve queued calls + this.cache.queue.forEach(({ resolve }) => resolve(gitlabData)); + this.cache.queue = []; + + return gitlabData; + } + + // Main method that chooses between GraphQL and REST + async fetchGitLabData(username, startDate, endDate, token = null) { + // Only try GraphQL if we have a token (GraphQL works better with authentication) + if (token) { + console.log('Token provided, attempting GraphQL for GitLab data fetch...'); + try { + return await this.fetchGitLabDataGraphQL(username, startDate, endDate, token); + } catch (error) { + console.log('GraphQL failed, falling back to REST API...'); + return await this.fetchGitLabDataREST(username, startDate, endDate, token); + } + } else { + console.log('No token provided, using REST API directly for GitLab data fetch...'); + return await this.fetchGitLabDataREST(username, startDate, endDate, token); + } + } + + async getDetailedMergeRequests(mergeRequests, token = null) { + const detailed = []; + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + for (const mr of mergeRequests) { + try { + const url = `${this.baseUrl}/projects/${mr.project_id}/merge_requests/${mr.iid}`; + const res = await fetch(url, { headers }); + if (res.ok) { + const detailedMr = await res.json(); + detailed.push(detailedMr); + } + // Add small delay to avoid rate limiting + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (error) { + console.error(`[GITLAB-DEBUG] Error fetching detailed MR ${mr.iid}:`, error); + detailed.push(mr); // Use basic data if detailed fetch fails + } + } + return detailed; + } + + async getDetailedIssues(issues, token = null) { + const detailed = []; + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + for (const issue of issues) { + try { + const url = `${this.baseUrl}/projects/${issue.project_id}/issues/${issue.iid}`; + const res = await fetch(url, { headers }); + if (res.ok) { + const detailedIssue = await res.json(); + detailed.push(detailedIssue); + } + // Add small delay to avoid rate limiting + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (error) { + console.error(`[GITLAB-DEBUG] Error fetching detailed issue ${issue.iid}:`, error); + detailed.push(issue); // Use basic data if detailed fetch fails + } + } + return detailed; + } + + formatDate(dateString) { + const date = new Date(dateString); + const options = { day: '2-digit', month: 'short', year: 'numeric' }; + return date.toLocaleDateString('en-US', options); + } + + processGitLabData(data) { + const processed = { + mergeRequests: data.mergeRequests || [], + issues: data.issues || [], + comments: data.comments || [], + user: data.user, + }; + console.log('[GITLAB-DEBUG] processGitLabData input:', data); + console.log('[GITLAB-DEBUG] processGitLabData output:', processed); + console.log('GitLab data processed:', { + mergeRequests: processed.mergeRequests.length, + issues: processed.issues.length, + comments: processed.comments.length, + user: processed.user?.username, + }); + + return processed; + } } // Export for use in other scripts if (typeof module !== 'undefined' && module.exports) { - module.exports = GitLabHelper; + module.exports = GitLabHelper; } else { - window.GitLabHelper = GitLabHelper; -} \ No newline at end of file + window.GitLabHelper = GitLabHelper; +} diff --git a/src/scripts/main.js b/src/scripts/main.js index 8c78571..02c195e 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,6 +1,7 @@ let enableToggleElement = document.getElementById('enable'); let platformUsernameElement = document.getElementById('platformUsername'); let githubTokenElement = document.getElementById('githubToken'); +let gitlabTokenElement = document.getElementById('gitlabToken'); let cacheInputElement = document.getElementById('cacheInput'); let projectNameElement = document.getElementById('projectName'); let yesterdayContributionElement = document.getElementById('yesterdayContribution'); @@ -41,6 +42,7 @@ function handleBodyOnLoad() { 'yesterdayContribution', 'cacheInput', 'githubToken', + 'gitlabToken', 'showCommits', ], (items) => { @@ -54,6 +56,9 @@ function handleBodyOnLoad() { if (items.githubToken) { githubTokenElement.value = items.githubToken; } + if (items.gitlabToken && gitlabTokenElement) { + gitlabTokenElement.value = items.gitlabToken; + } if (items.projectName) { projectNameElement.value = items.projectName; } @@ -84,8 +89,7 @@ function handleBodyOnLoad() { if (items.yesterdayContribution) { yesterdayContributionElement.checked = items.yesterdayContribution; handleYesterdayContributionChange(); - } - else if (items.yesterdayContribution !== false) { + } else if (items.yesterdayContribution !== false) { yesterdayContributionElement.checked = true; handleYesterdayContributionChange(); } @@ -106,7 +110,7 @@ document.getElementById('refreshCache').addEventListener('click', async (e) => { try { } catch (err) { - console.log('Refresh successful',); + console.log('Refresh successful'); } finally { setTimeout(() => { button.classList.remove('loading'); @@ -139,13 +143,13 @@ function handleYesterdayContributionChange() { startingDateElement.value = getYesterday(); handleEndingDateChange(); handleStartingDateChange(); - labelElement.classList.add("selectedLabel"); - labelElement.classList.remove("unselectedLabel"); + labelElement.classList.add('selectedLabel'); + labelElement.classList.remove('unselectedLabel'); } else { startingDateElement.readOnly = false; endingDateElement.readOnly = false; - labelElement.classList.add("unselectedLabel"); - labelElement.classList.remove("selectedLabel"); + labelElement.classList.add('unselectedLabel'); + labelElement.classList.remove('selectedLabel'); } chrome.storage.local.set({ yesterdayContribution: value }); } @@ -173,6 +177,10 @@ function handleGithubTokenChange() { let value = githubTokenElement.value; chrome.storage.local.set({ githubToken: value }); } +function handleGitlabTokenChange() { + let {value} = gitlabTokenElement; + chrome.storage.local.set({ gitlabToken: value }); +} function handleProjectNameChange() { let value = projectNameElement.value; chrome.storage.local.set({ projectName: value }); @@ -186,18 +194,16 @@ function handleOpenLabelChange() { let labelElement = document.querySelector("label[for='showOpenLabel']"); if (value) { - labelElement.classList.add("selectedLabel"); - labelElement.classList.remove("unselectedLabel"); + labelElement.classList.add('selectedLabel'); + labelElement.classList.remove('unselectedLabel'); } else { - labelElement.classList.add("unselectedLabel"); - labelElement.classList.remove("selectedLabel"); + labelElement.classList.add('unselectedLabel'); + labelElement.classList.remove('selectedLabel'); } chrome.storage.local.set({ showOpenLabel: value }); } - - function handleShowCommitsChange() { let value = showCommitsElement.checked; chrome.storage.local.set({ showCommits: value }); @@ -208,6 +214,9 @@ platformUsernameElement.addEventListener('keyup', handlePlatformUsernameChange); if (githubTokenElement) { githubTokenElement.addEventListener('keyup', handleGithubTokenChange); } +if (gitlabTokenElement) { + gitlabTokenElement.addEventListener('keyup', handleGitlabTokenChange); +} cacheInputElement.addEventListener('keyup', handleCacheInputChange); projectNameElement.addEventListener('keyup', handleProjectNameChange); startingDateElement.addEventListener('change', handleStartingDateChange); @@ -216,4 +225,4 @@ endingDateElement.addEventListener('change', handleEndingDateChange); yesterdayContributionElement.addEventListener('change', handleYesterdayContributionChange); showOpenLabelElement.addEventListener('change', handleOpenLabelChange); -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 abe31ba..a3025c0 100644 --- a/src/scripts/popup.js +++ b/src/scripts/popup.js @@ -1,12 +1,13 @@ function debounce(func, wait) { - let timeout; - return function (...args) { - clearTimeout(timeout); - timeout = setTimeout(() => func.apply(this, args), wait); - } + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; } function getToday() { + let today = new Date(); return today.toISOString().split('T')[0]; } @@ -16,1027 +17,1046 @@ function getYesterday() { let yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); return yesterday.toISOString().split('T')[0]; + } function applyI18n() { - document.querySelectorAll('[data-i18n]').forEach(el => { - const key = el.getAttribute('data-i18n'); - const message = chrome.i18n.getMessage(key); - if (message) { - // Use innerHTML to support simple formatting like in tooltips - if (el.classList.contains('tooltip-bubble') || el.classList.contains('cache-info')) { - el.innerHTML = message; - } else { - el.textContent = message; - } - } - }); - - document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { - const key = el.getAttribute('data-i18n-placeholder'); - const message = chrome.i18n.getMessage(key); - if (message) { - el.placeholder = message; - } - }); - - document.querySelectorAll('[data-i18n-title]').forEach(el => { - const key = el.getAttribute('data-i18n-title'); - const message = chrome.i18n.getMessage(key); - if (message) { - el.title = message; - } - }); + document.querySelectorAll('[data-i18n]').forEach((el) => { + const key = el.getAttribute('data-i18n'); + const message = chrome.i18n.getMessage(key); + if (message) { + // Use innerHTML to support simple formatting like in tooltips + if (el.classList.contains('tooltip-bubble') || el.classList.contains('cache-info')) { + el.innerHTML = message; + } else { + el.textContent = message; + } + } + }); + + document.querySelectorAll('[data-i18n-placeholder]').forEach((el) => { + const key = el.getAttribute('data-i18n-placeholder'); + const message = chrome.i18n.getMessage(key); + if (message) { + el.placeholder = message; + } + }); + + document.querySelectorAll('[data-i18n-title]').forEach((el) => { + const key = el.getAttribute('data-i18n-title'); + const message = chrome.i18n.getMessage(key); + if (message) { + el.title = message; + } + }); } - document.addEventListener('DOMContentLoaded', function () { - // Apply translations as soon as the DOM is ready - applyI18n(); - - // Dark mode setup - const darkModeToggle = document.querySelector('img[alt="Night Mode"]'); - const settingsIcon = document.getElementById('settingsIcon'); - const body = document.body; - const homeButton = document.getElementById('homeButton'); - const scrumHelperHeading = document.getElementById('scrumHelperHeading'); - const settingsToggle = document.getElementById('settingsToggle'); - const reportSection = document.getElementById('reportSection'); - const settingsSection = document.getElementById('settingsSection'); - - let isSettingsVisible = false; - const githubTokenInput = document.getElementById('githubToken'); - const toggleTokenBtn = document.getElementById('toggleTokenVisibility'); - const tokenEyeIcon = document.getElementById('tokenEyeIcon'); - let tokenVisible = false; - - const orgInput = document.getElementById('orgInput'); - const setOrgBtn = document.getElementById('setOrgBtn'); - - - const platformSelect = document.getElementById('platformSelect'); - const usernameLabel = document.getElementById('usernameLabel'); - const platformUsername = document.getElementById('platformUsername'); - - function checkTokenForFilter() { - const useRepoFilter = document.getElementById('useRepoFilter'); - const githubTokenInput = document.getElementById('githubToken'); - const tokenWarning = document.getElementById('tokenWarningForFilter'); - const repoFilterContainer = document.getElementById('repoFilterContainer'); - - - if (!useRepoFilter || !githubTokenInput || !tokenWarning || !repoFilterContainer) { - - return; - } - const isFilterEnabled = useRepoFilter.checked; - const hasToken = githubTokenInput.value.trim() != ''; - - if (isFilterEnabled && !hasToken) { - useRepoFilter.checked = false; - repoFilterContainer.classList.add('hidden'); - if (typeof hideDropdown === 'function') { - hideDropdown(); - } - chrome.storage.local.set({ useRepoFilter: false }); - } - tokenWarning.classList.toggle('hidden', !isFilterEnabled || hasToken); - setTimeout(() => { - tokenWarning.classList.add('hidden'); - }, 4000) - - } - - - - chrome.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'; - } - } - }); - - toggleTokenBtn.addEventListener('click', function () { - tokenVisible = !tokenVisible; - githubTokenInput.type = tokenVisible ? 'text' : 'password'; - - tokenEyeIcon.classList.add('eye-animating'); - setTimeout(() => tokenEyeIcon.classList.remove('eye-animating'), 400); - tokenEyeIcon.className = tokenVisible ? 'fa fa-eye-slash text-gray-600' : 'fa fa-eye text-gray-600'; - - githubTokenInput.classList.add('token-animating'); - setTimeout(() => githubTokenInput.classList.remove('token-animating'), 300); - }); - - githubTokenInput.addEventListener('input', checkTokenForFilter); - - darkModeToggle.addEventListener('click', function () { - body.classList.toggle('dark-mode'); - const isDarkMode = body.classList.contains('dark-mode'); - chrome.storage.local.set({ darkMode: isDarkMode }); - this.src = isDarkMode ? 'icons/light-mode.png' : 'icons/night-mode.png'; - const settingsIcon = document.getElementById('settingsIcon'); - if (settingsIcon) { - settingsIcon.src = isDarkMode ? 'icons/settings-night.png' : 'icons/settings-light.png'; - } - renderTokenPreview(); - }); - - function renderTokenPreview() { - tokenPreview.innerHTML = ''; - const value = githubTokenInput.value; - const isDark = document.body.classList.contains('dark-mode'); - for (let i = 0; i < value.length; i++) { - const charBox = document.createElement('span'); - charBox.className = 'token-preview-char' + (isDark ? ' dark-mode' : ''); - if (tokenVisible) { - charBox.textContent = value[i]; - } else { - const dot = document.createElement('span'); - dot.className = 'token-preview-dot' + (isDark ? ' dark-mode' : ''); - charBox.appendChild(dot); - } - tokenPreview.appendChild(charBox); - setTimeout(() => charBox.classList.add('flip'), 10 + i * 30); - } - } - - function updateContentState(enableToggle) { - console.log('[DEBUG] updateContentState called with:', enableToggle); - const elementsToToggle = [ - 'startingDate', - 'endingDate', - 'generateReport', - 'copyReport', - 'refreshCache', - 'showOpenLabel', - 'showCommits', - 'scrumReport', - 'githubUsername', - 'githubToken', - 'projectName', - 'platformUsername', - 'orgInput', - 'cacheInput', - 'settingsToggle', - ]; - - const radios = document.querySelectorAll('input[name="timeframe"]'); - const customDateContainer = document.getElementById('customDateContainer'); - - elementsToToggle.forEach(id => { - const element = document.getElementById(id); - if (element) { - element.disabled = !enableToggle; - if (!enableToggle) { - element.style.opacity = '0.5'; - element.style.pointerEvents = 'none'; - } else { - element.style.opacity = '1'; - element.style.pointerEvents = 'auto'; - } - } - }); - - radios.forEach(radio => { - radio.disabled = !enableToggle; - const label = document.querySelector(`label[for="${radio.id}"]`); - if (label) { - if (!enableToggle) { - label.style.opacity = '0.5'; - label.style.pointerEvents = 'none'; - } else { - label.style.opacity = '1'; - label.style.pointerEvents = 'auto'; - } - } - }); - - - if (customDateContainer) { - if (!enableToggle) { - customDateContainer.style.opacity = '0.5'; - customDateContainer.style.pointerEvents = 'none'; - } else { - customDateContainer.style.opacity = '1'; - customDateContainer.style.pointerEvents = 'auto'; - } - } - - const scrumReport = document.getElementById('scrumReport'); - if (scrumReport) { - scrumReport.contentEditable = enableToggle; - if (!enableToggle) { - scrumReport.innerHTML = `
${chrome.i18n.getMessage('extensionDisabledMessage')}
`; - } else { - const disabledMessage = `${chrome.i18n.getMessage('extensionDisabledMessage')}
`; - if (scrumReport.innerHTML === disabledMessage) { - scrumReport.innerHTML = ''; - } - } - } - } - - chrome.storage.local.get(['enableToggle'], (items) => { - console.log('[DEBUG] Storage items received:', items); - const enableToggle = items.enableToggle !== false; - console.log('[DEBUG] enableToggle calculated:', enableToggle); - - // If enableToggle is undefined (first install), set it to true by default - if (typeof items.enableToggle === 'undefined') { - console.log('[DEBUG] Setting default enableToggle to true'); - chrome.storage.local.set({ enableToggle: true }); - } - - console.log('[DEBUG] Calling updateContentState with:', enableToggle); - updateContentState(enableToggle); - if (!enableToggle) { - console.log('[DEBUG] Extension disabled, returning early'); - return; - } - - console.log('[DEBUG] Extension enabled, initializing popup'); - initializePopup(); - checkTokenForFilter(); - }) - - chrome.storage.onChanged.addListener((changes, namespace) => { - console.log('[DEBUG] Storage changed:', changes, namespace); - if (namespace === 'local' && changes.enableToggle) { - console.log('[DEBUG] enableToggle changed to:', changes.enableToggle.newValue); - updateContentState(changes.enableToggle.newValue); - if (changes.enableToggle.newValue) { - // re-initialize if enabled - console.log('[DEBUG] Re-initializing popup due to enable toggle change'); - initializePopup(); - } - } - if (changes.startingDate || changes.endingDate) { - console.log('[POPUP-DEBUG] Date changed in storage, triggering repo fetch.', { - startingDate: changes.startingDate?.newValue, - endingDate: changes.endingDate?.newValue - }); - if (window.triggerRepoFetchIfEnabled) { - window.triggerRepoFetchIfEnabled(); - } - } - }); - - function initializePopup() { - // Migration: Handle existing users with old platformUsername storage - chrome.storage.local.get(['platform', 'platformUsername'], function (result) { - if (result.platformUsername && result.platform) { - // Migrate old platformUsername to platform-specific storage - const platformUsernameKey = `${result.platform}Username`; - chrome.storage.local.set({ [platformUsernameKey]: result.platformUsername }); - // Remove the old key - chrome.storage.local.remove(['platformUsername']); - console.log(`[MIGRATION] Migrated platformUsername to ${platformUsernameKey}`); - } - }); - - // Restore all persistent fields immediately on DOMContentLoaded - const projectNameInput = document.getElementById('projectName'); - const orgInput = document.getElementById('orgInput'); - const userReasonInput = document.getElementById('userReason'); - const showOpenLabelCheckbox = document.getElementById('showOpenLabel'); - const showCommitsCheckbox = document.getElementById('showCommits'); - const githubTokenInput = document.getElementById('githubToken'); - const cacheInput = document.getElementById('cacheInput'); - const enableToggleSwitch = document.getElementById('enable'); - const yesterdayRadio = document.getElementById('yesterdayContribution'); - const startingDateInput = document.getElementById('startingDate'); - const endingDateInput = document.getElementById('endingDate'); - const platformUsername = document.getElementById('platformUsername'); - - chrome.storage.local.get([ - 'projectName', 'orgName', 'userReason', 'showOpenLabel', 'showCommits', 'githubToken', 'cacheInput', - 'enableToggle', 'yesterdayContribution', 'startingDate', 'endingDate', 'selectedTimeframe', 'platform', 'githubUsername', 'gitlabUsername' - ], function (result) { - if (result.projectName) projectNameInput.value = result.projectName; - if (result.orgName) orgInput.value = result.orgName; - if (result.userReason) userReasonInput.value = result.userReason; - if (typeof result.showOpenLabel !== 'undefined') { - showOpenLabelCheckbox.checked = result.showOpenLabel; - } else { - showOpenLabelCheckbox.checked = true; // Default to true for new users - } - if (typeof result.showCommits !== 'undefined') showCommitsCheckbox.checked = result.showCommits; - if (result.githubToken) githubTokenInput.value = result.githubToken; - if (result.cacheInput) cacheInput.value = result.cacheInput; - if (enableToggleSwitch) { - if (typeof result.enableToggle !== 'undefined') { - enableToggleSwitch.checked = result.enableToggle; - } else { - enableToggleSwitch.checked = true; // Default to enabled - } - } - if (typeof result.yesterdayContribution !== 'undefined') yesterdayRadio.checked = result.yesterdayContribution; - if (result.startingDate) startingDateInput.value = result.startingDate; - if (result.endingDate) endingDateInput.value = result.endingDate; - - // Load platform-specific username - const platform = result.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - platformUsername.value = result[platformUsernameKey] || ''; - }); - - // Button setup - const generateBtn = document.getElementById('generateReport'); - const copyBtn = document.getElementById('copyReport'); - - generateBtn.addEventListener('click', function () { - - chrome.storage.local.get(['platform'], function (result) { - const platform = result.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - - chrome.storage.local.set({ - platform: platformSelect.value, - [platformUsernameKey]: platformUsername.value - }, () => { - let org = orgInput.value.trim().toLowerCase(); - chrome.storage.local.set({ orgName: org }, () => { - // Reload platform from storage before generating report - chrome.storage.local.get(['platform'], function (res) { - platformSelect.value = res.platform || 'github'; - updatePlatformUI(platformSelect.value); - generateBtn.innerHTML = ' Generating...'; - generateBtn.disabled = true; - window.generateScrumReport && window.generateScrumReport(); - }); - }); - }); - - }); - }); - - copyBtn.addEventListener('click', function () { - const scrumReport = document.getElementById('scrumReport'); - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = scrumReport.innerHTML; - document.body.appendChild(tempDiv); - tempDiv.style.position = 'absolute'; - tempDiv.style.left = '-9999px'; - - const range = document.createRange(); - range.selectNode(tempDiv); - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - - try { - document.execCommand('copy'); - this.innerHTML = ` ${chrome.i18n.getMessage('copiedButton')}`; - setTimeout(() => { - this.innerHTML = ` ${chrome.i18n.getMessage('copyReportButton')}`; - }, 2000); - } catch (err) { - console.error('Failed to copy: ', err); - } finally { - selection.removeAllRanges(); - document.body.removeChild(tempDiv); - } - }); - - // Custom date container click handler - document.getElementById('customDateContainer').addEventListener('click', () => { - document.querySelectorAll('input[name="timeframe"]').forEach(radio => { - radio.checked = false - radio.dataset.wasChecked = 'false' - }); - - const startDateInput = document.getElementById('startingDate'); - const endDateInput = document.getElementById('endingDate'); - startDateInput.readOnly = false; - endDateInput.readOnly = false; - - chrome.storage.local.set({ - yesterdayContribution: false, - selectedTimeframe: null - }); - }); - - chrome.storage.local.get([ - 'selectedTimeframe', - 'yesterdayContribution', - 'startingDate', - 'endingDate', - ], (items) => { - console.log('Restoring state:', items); - - - if (items.startingDate && items.endingDate && !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; - endDateInput.readOnly = false; - } - document.querySelectorAll('input[name="timeframe"]').forEach(radio => { - radio.checked = false; - radio.dataset.wasChecked = 'false'; - }) - return; - } - - if (!items.selectedTimeframe) { - items.selectedTimeframe = 'yesterdayContribution'; - items.yesterdayContribution = true; - } - - const radio = document.getElementById(items.selectedTimeframe); - if (radio) { - radio.checked = true; - radio.dataset.wasChecked = 'true'; - - const startDateInput = document.getElementById('startingDate'); - const endDateInput = document.getElementById('endingDate'); - - if (items.selectedTimeframe === 'yesterdayContribution') { - startDateInput.value = getYesterday(); - endDateInput.value = getToday(); - } - startDateInput.readOnly = endDateInput.readOnly = true; - - chrome.storage.local.set({ - startingDate: startDateInput.value, - endingDate: endDateInput.value, - yesterdayContribution: items.selectedTimeframe === 'yesterdayContribution', - selectedTimeframe: items.selectedTimeframe - }); - } - }); - - // Save all fields to storage on input/change - projectNameInput.addEventListener('input', function () { - chrome.storage.local.set({ projectName: projectNameInput.value }); - }); - orgInput.addEventListener('input', function () { - chrome.storage.local.set({ orgName: orgInput.value.trim().toLowerCase() }); - }); - userReasonInput.addEventListener('input', function () { - chrome.storage.local.set({ userReason: userReasonInput.value }); - }); - showOpenLabelCheckbox.addEventListener('change', function () { - chrome.storage.local.set({ showOpenLabel: showOpenLabelCheckbox.checked }); - }); - showCommitsCheckbox.addEventListener('change', function () { - chrome.storage.local.set({ showCommits: showCommitsCheckbox.checked }); - }); - githubTokenInput.addEventListener('input', function () { - chrome.storage.local.set({ githubToken: githubTokenInput.value }); - }); - cacheInput.addEventListener('input', function () { - chrome.storage.local.set({ cacheInput: cacheInput.value }); - }); - if (enableToggleSwitch) { - console.log('[DEBUG] Setting up enable toggle switch event listener'); - enableToggleSwitch.addEventListener('change', function () { - console.log('[DEBUG] Enable toggle changed to:', enableToggleSwitch.checked); - chrome.storage.local.set({ enableToggle: enableToggleSwitch.checked }); - }); - } - yesterdayRadio.addEventListener('change', function () { - chrome.storage.local.set({ yesterdayContribution: yesterdayRadio.checked }); - }); - startingDateInput.addEventListener('input', function () { - chrome.storage.local.set({ startingDate: startingDateInput.value }); - }); - endingDateInput.addEventListener('input', function () { - chrome.storage.local.set({ endingDate: endingDateInput.value }); - }); - - // Save username to storage on input - platformUsername.addEventListener('input', function () { - chrome.storage.local.get(['platform'], function (result) { - const platform = result.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - chrome.storage.local.set({ [platformUsernameKey]: platformUsername.value }); - }); - }); - - - } - - function showReportView() { - isSettingsVisible = false; - reportSection.classList.remove('hidden'); - settingsSection.classList.add('hidden'); - settingsToggle.classList.remove('active'); - - } - - function showSettingsView() { - isSettingsVisible = true; - reportSection.classList.add('hidden'); - settingsSection.classList.remove('hidden'); - settingsToggle.classList.add('active'); - - } - - if (settingsToggle) { - settingsToggle.addEventListener('click', function () { - if (isSettingsVisible) { - showReportView(); - } else { - showSettingsView(); - } - }); - } - - if (homeButton) { - homeButton.addEventListener('click', showReportView); - } - if (scrumHelperHeading) { - scrumHelperHeading.addEventListener('click', showReportView); - } - - showReportView(); - - chrome.storage.local.get(['orgName'], function (result) { - orgInput.value = result.orgName || ''; - }); - - - // Debug function to test storage - window.testStorage = function () { - chrome.storage.local.get(['enableToggle'], function (result) { - console.log('[TEST] Current enableToggle value:', result.enableToggle); - }); - }; - - - - //report filter - const repoSearch = document.getElementById('repoSearch'); - const repoDropdown = document.getElementById('repoDropdown'); - const selectedReposDiv = document.getElementById('selectedRepos'); - const repoTags = document.getElementById('repoTags'); - const repoPlaceholder = document.getElementById('repoPlaceholder'); - const repoCount = document.getElementById('repoCount'); - const repoStatus = document.getElementById('repoStatus'); - const useRepoFilter = document.getElementById('useRepoFilter'); - const repoFilterContainer = document.getElementById('repoFilterContainer'); - - if (repoSearch && useRepoFilter && repoFilterContainer) { - repoSearch.addEventListener('click', function () { - if (!useRepoFilter.checked) { - useRepoFilter.checked = true; - repoFilterContainer.classList.remove('hidden'); - chrome.storage.local.set({ useRepoFilter: true }); - } - }) - } - - if (!repoSearch || !useRepoFilter) { - console.log('Repository, filter elements not found in DOM'); - } - else { - let availableRepos = []; - let selectedRepos = []; - let highlightedIndex = -1; - - async function triggerRepoFetchIfEnabled() { - // --- PLATFORM CHECK: Only run for GitHub --- - let platform = 'github'; - try { - const items = await new Promise(resolve => { - chrome.storage.local.get(['platform'], resolve); - }); - platform = items.platform || 'github'; - } catch (e) { } - if (platform !== 'github') { - // Do not run repo fetch for non-GitHub platforms - if (repoStatus) repoStatus.textContent = 'Repository filtering is only available for GitHub.'; - return; - } - if (!useRepoFilter.checked) { - return; - } - - if (repoStatus) { - repoStatus.textContent = chrome.i18n.getMessage('repoRefetching'); - } - - try { - const cacheData = await new Promise(resolve => { - chrome.storage.local.get(['repoCache'], resolve); - }); - const items = await new Promise(resolve => { - chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], resolve); - }); - - const platform = items.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - const username = items[platformUsernameKey]; - - if (!username) { - if (repoStatus) { - - repoStatus.textContent = 'Username required'; - - } - return; - } - - if (window.fetchUserRepositories) { - const repos = await window.fetchUserRepositories( - username, - items.githubToken, - items.orgName || '' - ); - - availableRepos = repos; - - if (repoStatus) { - repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [repos.length]); - } - - const repoCacheKey = `repos-${username}-${items.orgName || ''}`; - chrome.storage.local.set({ - repoCache: { - data: repos, - cacheKey: repoCacheKey, - timestamp: Date.now() - } - }); - - if (document.activeElement === repoSearch) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } else if (repoSearch.value) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } else { - filterAndDisplayRepos(''); - } - } - } catch (err) { - - if (repoStatus) { - repoStatus.textContent = `${chrome.i18n.getMessage('errorLabel')}: ${err.message || chrome.i18n.getMessage('repoRefetchFailed')}`; - } - } - } - - window.triggerRepoFetchIfEnabled = triggerRepoFetchIfEnabled; - - chrome.storage.local.get(['selectedRepos', 'useRepoFilter'], (items) => { - if (items.selectedRepos) { - selectedRepos = items.selectedRepos; - updateRepoDisplay(); - } - if (items.useRepoFilter) { - useRepoFilter.checked = items.useRepoFilter; - repoFilterContainer.classList.toggle('hidden', !items.useRepoFilter); - } - }); - - useRepoFilter.addEventListener('change', debounce(async () => { - // --- PLATFORM CHECK: Only run for GitHub --- - let platform = 'github'; - try { - const items = await new Promise(resolve => { - chrome.storage.local.get(['platform'], resolve); - }); - platform = items.platform || 'github'; - } catch (e) { } - if (platform !== 'github') { - repoFilterContainer.classList.add('hidden'); - useRepoFilter.checked = false; - if (repoStatus) repoStatus.textContent = 'Repository filtering is only available for GitHub.'; - return; - } - const enabled = useRepoFilter.checked; - const hasToken = githubTokenInput.value.trim() !== ''; - repoFilterContainer.classList.toggle('hidden', !enabled); - - if (enabled && !hasToken) { - useRepoFilter.checked = false; - repoFilterContainer.classList.add('hidden'); // Explicitly hide the container - hideDropdown(); - const tokenWarning = document.getElementById('tokenWarningForFilter'); - if (tokenWarning) { - tokenWarning.classList.remove('hidden'); - tokenWarning.classList.add('shake-animation'); - setTimeout(() => tokenWarning.classList.remove('shake-animation'), 620); - setTimeout(() => { - tokenWarning.classList.add('hidden'); - }, 3000); - } - return; - } - repoFilterContainer.classList.toggle('hidden', !enabled); - - chrome.storage.local.set({ - useRepoFilter: enabled, - githubCache: null, //forces refresh - }); - checkTokenForFilter(); - if (enabled) { - repoStatus.textContent = 'Loading repos automatically..'; - - try { - const cacheData = await new Promise(resolve => { - chrome.storage.local.get(['repoCache'], resolve); - }); - const items = await new Promise(resolve => { - - chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], resolve); - }); - - const platform = items.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - const username = items[platformUsernameKey]; - - if (!username) { - - repoStatus.textContent = 'Github Username required'; - - return; - } - - const repoCacheKey = `repos-${username}-${items.orgName || ''}`; - - const now = Date.now(); - const cacheAge = cacheData.repoCache?.timestamp ? now - cacheData.repoCache.timestamp : Infinity; - const cacheTTL = 10 * 60 * 1000; // 10 minutes - - if (cacheData.repoCache && - cacheData.repoCache.cacheKey === repoCacheKey && - cacheAge < cacheTTL) { - - console.log('Using cached repositories'); - availableRepos = cacheData.repoCache.data; - repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [availableRepos.length]); - - if (document.activeElement === repoSearch) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } - return; - } - - if (window.fetchUserRepositories) { - const repos = await window.fetchUserRepositories( - - username, - - items.githubToken, - items.orgName || '', - ); - availableRepos = repos; - repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [repos.length]); - - chrome.storage.local.set({ - repoCache: { - data: repos, - cacheKey: repoCacheKey, - timestamp: now - } - }); - - if (document.activeElement === repoSearch) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } - } - } catch (err) { - - - console.error('Auto load repos failed', err); - - if (err.message?.includes('401')) { - repoStatus.textContent = chrome.i18n.getMessage('repoTokenPrivate'); - } else if (err.message?.includes('username')) { - repoStatus.textContent = chrome.i18n.getMessage('githubUsernamePlaceholder'); - } else { - repoStatus.textContent = `${chrome.i18n.getMessage('errorLabel')}: ${err.message || chrome.i18n.getMessage('repoLoadFailed')}`; - } - } - } else { - selectedRepos = []; - updateRepoDisplay(); - chrome.storage.local.set({ selectedRepos: [] }); - repoStatus.textContent = ''; - } - }, 300)); - - repoSearch.addEventListener('keydown', (e) => { - const items = repoDropdown.querySelectorAll('.repository-dropdown-item'); - - switch (e.key) { - case 'ArrowDown': - e.preventDefault(); - highlightedIndex = Math.min(highlightedIndex + 1, items.length - 1); - updateHighlight(items); - break; - case 'ArrowUp': - e.preventDefault(); - highlightedIndex = Math.max(highlightedIndex - 1, 0); - updateHighlight(items); - break; - case 'Enter': - e.preventDefault(); - if (highlightedIndex >= 0 && items[highlightedIndex]) { - fnSelectedRepos(items[highlightedIndex].dataset.repoName); - } - break; - case 'Escape': - hideDropdown(); - break; - } - }); - - repoSearch.addEventListener('input', (e) => { - const query = e.target.value.toLowerCase(); - filterAndDisplayRepos(query); - }) - let programmaticFocus = false; - repoSearch.addEventListener('focus', function () { - if (programmaticFocus) { - programmaticFocus = false; - return; - } - if (repoSearch.value) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } else if (availableRepos.length > 0) { - filterAndDisplayRepos(''); - } - }); - - document.addEventListener('click', (e) => { - if (!e.target.closest('#repoSearch') && !e.target.closest('#repoDropdown')) { - hideDropdown(); - } - }); - - function debugRepoFetch() { - chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], (items) => { - const platform = items.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - const username = items[platformUsernameKey]; - console.log('Current settings:', { - username: username, - hasToken: !!items.githubToken, - org: items.orgName || '' - }); - }); - } - debugRepoFetch(); - async function loadRepos() { - // --- PLATFORM CHECK: Only run for GitHub --- - let platform = 'github'; - try { - const items = await new Promise(resolve => { - chrome.storage.local.get(['platform'], resolve); - }); - platform = items.platform || 'github'; - } catch (e) { } - if (platform !== 'github') { - if (repoStatus) repoStatus.textContent = 'Repository loading is only available for GitHub.'; - return; - } - console.log('window.fetchUserRepositories exists:', !!window.fetchUserRepositories); - console.log('Available functions:', Object.keys(window).filter(key => key.includes('fetch'))); - - if (!window.fetchUserRepositories) { - repoStatus.textContent = 'Repository fetching not available'; - return; - } - - chrome.storage.local.get(['platform', 'githubUsername', 'githubToken'], (items) => { - const platform = items.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - const username = items[platformUsernameKey]; - console.log('Storage data for repo fetch:', { - hasUsername: !!username, - hasToken: !!items.githubToken, - username: username - }); - - - if (!username) { - repoStatus.textContent = 'Username required'; - - return; - } - - performRepoFetch(); - }); - } - - async function performRepoFetch() { - // --- PLATFORM CHECK: Only run for GitHub --- - let platform = 'github'; - try { - const items = await new Promise(resolve => { - chrome.storage.local.get(['platform'], resolve); - }); - platform = items.platform || 'github'; - } catch (e) { } - if (platform !== 'github') { - if (repoStatus) repoStatus.textContent = 'Repository fetching is only available for GitHub.'; - return; - } - console.log('[POPUP-DEBUG] performRepoFetch called.'); - repoStatus.textContent = chrome.i18n.getMessage('repoLoading'); - repoSearch.classList.add('repository-search-loading'); - - try { - const cacheData = await new Promise(resolve => { - chrome.storage.local.get(['repoCache'], resolve); - }); - const storageItems = await new Promise(resolve => { - - chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], resolve) - - }) - const platform = storageItems.platform || 'github'; - const platformUsernameKey = `${platform}Username`; - const username = storageItems[platformUsernameKey]; - const repoCacheKey = `repos-${username}-${storageItems.orgName || ''}`; - const now = Date.now(); - const cacheAge = cacheData.repoCache?.timestamp ? now - cacheData.repoCache.timestamp : Infinity; - const cacheTTL = 10 * 60 * 1000; // 10 minutes - - console.log('[POPUP-DEBUG] Repo cache check:', { - key: repoCacheKey, - cacheKeyInCache: cacheData.repoCache?.cacheKey, - isMatch: cacheData.repoCache?.cacheKey === repoCacheKey, - age: cacheAge, - isFresh: cacheAge < cacheTTL - }); - - if (cacheData.repoCache && - cacheData.repoCache.cacheKey === repoCacheKey && - cacheAge < cacheTTL) { - - console.log('[POPUP-DEBUG] Using cached repositories in manual fetch'); - availableRepos = cacheData.repoCache.data; - repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [availableRepos.length]); - - if (document.activeElement === repoSearch) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } - return; - } - console.log('[POPUP-DEBUG] No valid cache. Fetching from network.'); - availableRepos = await window.fetchUserRepositories( - - username, - - storageItems.githubToken, - storageItems.orgName || '' - ); - repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [availableRepos.length]); - console.log(`[POPUP-DEBUG] Fetched and loaded ${availableRepos.length} repos.`); - - chrome.storage.local.set({ - repoCache: { - data: availableRepos, - cacheKey: repoCacheKey, - timestamp: now - } - }); - - if (document.activeElement === repoSearch) { - filterAndDisplayRepos(repoSearch.value.toLowerCase()); - } - } catch (err) { - console.error(`Failed to load repos:`, err); - - if (err.message && err.message.includes('401')) { - repoStatus.textContent = chrome.i18n.getMessage('repoTokenPrivate'); - } else if (err.message && err.message.includes('username')) { - repoStatus.textContent = chrome.i18n.getMessage('githubUsernamePlaceholder'); - } else { - repoStatus.textContent = `${chrome.i18n.getMessage('errorLabel')}: ${err.message || chrome.i18n.getMessage('repoLoadFailed')}`; - } - } finally { - repoSearch.classList.remove('repository-search-loading'); - } - } - - function filterAndDisplayRepos(query) { - if (availableRepos.length === 0) { - repoDropdown.innerHTML = `${chrome.i18n.getMessage('extensionDisabledMessage')}
`; + } else { + const disabledMessage = `${chrome.i18n.getMessage('extensionDisabledMessage')}
`; + if (scrumReport.innerHTML === disabledMessage) { + scrumReport.innerHTML = ''; + } + } + } + } + + chrome.storage.local.get(['enableToggle'], (items) => { + console.log('[DEBUG] Storage items received:', items); + const enableToggle = items.enableToggle !== false; + console.log('[DEBUG] enableToggle calculated:', enableToggle); + + // If enableToggle is undefined (first install), set it to true by default + if (typeof items.enableToggle === 'undefined') { + console.log('[DEBUG] Setting default enableToggle to true'); + chrome.storage.local.set({ enableToggle: true }); + } + + console.log('[DEBUG] Calling updateContentState with:', enableToggle); + updateContentState(enableToggle); + if (!enableToggle) { + console.log('[DEBUG] Extension disabled, returning early'); + return; + } + + console.log('[DEBUG] Extension enabled, initializing popup'); + initializePopup(); + checkTokenForFilter(); + }); + + chrome.storage.onChanged.addListener((changes, namespace) => { + console.log('[DEBUG] Storage changed:', changes, namespace); + if (namespace === 'local' && changes.enableToggle) { + console.log('[DEBUG] enableToggle changed to:', changes.enableToggle.newValue); + updateContentState(changes.enableToggle.newValue); + if (changes.enableToggle.newValue) { + // re-initialize if enabled + console.log('[DEBUG] Re-initializing popup due to enable toggle change'); + initializePopup(); + } + } + if (changes.startingDate || changes.endingDate) { + console.log('[POPUP-DEBUG] Date changed in storage, triggering repo fetch.', { + startingDate: changes.startingDate?.newValue, + endingDate: changes.endingDate?.newValue, + }); + if (window.triggerRepoFetchIfEnabled) { + window.triggerRepoFetchIfEnabled(); + } + } + }); + + function initializePopup() { + // Migration: Handle existing users with old platformUsername storage + chrome.storage.local.get(['platform', 'platformUsername'], function (result) { + if (result.platformUsername && result.platform) { + // Migrate old platformUsername to platform-specific storage + const platformUsernameKey = `${result.platform}Username`; + chrome.storage.local.set({ [platformUsernameKey]: result.platformUsername }); + // Remove the old key + chrome.storage.local.remove(['platformUsername']); + console.log(`[MIGRATION] Migrated platformUsername to ${platformUsernameKey}`); + } + }); + + // Restore all persistent fields immediately on DOMContentLoaded + const projectNameInput = document.getElementById('projectName'); + const orgInput = document.getElementById('orgInput'); + const userReasonInput = document.getElementById('userReason'); + const showOpenLabelCheckbox = document.getElementById('showOpenLabel'); + const showCommitsCheckbox = document.getElementById('showCommits'); + const githubTokenInput = document.getElementById('githubToken'); + const cacheInput = document.getElementById('cacheInput'); + const enableToggleSwitch = document.getElementById('enable'); + const yesterdayRadio = document.getElementById('yesterdayContribution'); + const startingDateInput = document.getElementById('startingDate'); + const endingDateInput = document.getElementById('endingDate'); + const platformUsername = document.getElementById('platformUsername'); + + chrome.storage.local.get( + [ + 'projectName', + 'orgName', + 'userReason', + 'showOpenLabel', + 'showCommits', + 'githubToken', + 'cacheInput', + 'enableToggle', + 'yesterdayContribution', + 'startingDate', + 'endingDate', + 'selectedTimeframe', + 'platform', + 'githubUsername', + 'gitlabUsername', + ], + function (result) { + if (result.projectName) projectNameInput.value = result.projectName; + if (result.orgName) orgInput.value = result.orgName; + if (result.userReason) userReasonInput.value = result.userReason; + if (typeof result.showOpenLabel !== 'undefined') { + showOpenLabelCheckbox.checked = result.showOpenLabel; + } else { + showOpenLabelCheckbox.checked = true; // Default to true for new users + } + if (typeof result.showCommits !== 'undefined') showCommitsCheckbox.checked = result.showCommits; + if (result.githubToken) githubTokenInput.value = result.githubToken; + if (result.gitlabToken && gitlabTokenInput) gitlabTokenInput.value = result.gitlabToken; + if (result.cacheInput) cacheInput.value = result.cacheInput; + if (enableToggleSwitch) { + if (typeof result.enableToggle !== 'undefined') { + enableToggleSwitch.checked = result.enableToggle; + } else { + enableToggleSwitch.checked = true; // Default to enabled + } + } + if (typeof result.yesterdayContribution !== 'undefined') yesterdayRadio.checked = result.yesterdayContribution; + if (result.startingDate) startingDateInput.value = result.startingDate; + if (result.endingDate) endingDateInput.value = result.endingDate; + + // Load platform-specific username + const platform = result.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + platformUsername.value = result[platformUsernameKey] || ''; + + // Set platform select dropdown to correct value + if (platformSelect) { + platformSelect.value = platform; + } + + // Update platform UI based on loaded platform + updatePlatformUI(platform); + }, + ); + + // Button setup + const generateBtn = document.getElementById('generateReport'); + const copyBtn = document.getElementById('copyReport'); + + generateBtn.addEventListener('click', function () { + chrome.storage.local.get(['platform'], function (result) { + const platform = result.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + + chrome.storage.local.set( + { + platform: platformSelect.value, + [platformUsernameKey]: platformUsername.value, + }, + () => { + let org = orgInput.value.trim().toLowerCase(); + chrome.storage.local.set({ orgName: org }, () => { + // Reload platform from storage before generating report + chrome.storage.local.get(['platform'], function (res) { + platformSelect.value = res.platform || 'github'; + updatePlatformUI(platformSelect.value); + generateBtn.innerHTML = ' Generating...'; + generateBtn.disabled = true; + window.generateScrumReport && window.generateScrumReport(); + }); + }); + }, + ); + }); + }); + + copyBtn.addEventListener('click', function () { + const scrumReport = document.getElementById('scrumReport'); + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = scrumReport.innerHTML; + document.body.appendChild(tempDiv); + tempDiv.style.position = 'absolute'; + tempDiv.style.left = '-9999px'; + + const range = document.createRange(); + range.selectNode(tempDiv); + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + try { + document.execCommand('copy'); + this.innerHTML = ` ${chrome.i18n.getMessage('copiedButton')}`; + setTimeout(() => { + this.innerHTML = ` ${chrome.i18n.getMessage('copyReportButton')}`; + }, 2000); + } catch (err) { + console.error('Failed to copy: ', err); + } finally { + selection.removeAllRanges(); + document.body.removeChild(tempDiv); + } + }); + + // Custom date container click handler + document.getElementById('customDateContainer').addEventListener('click', () => { + document.querySelectorAll('input[name="timeframe"]').forEach((radio) => { + radio.checked = false; + radio.dataset.wasChecked = 'false'; + }); + + const startDateInput = document.getElementById('startingDate'); + const endDateInput = document.getElementById('endingDate'); + startDateInput.readOnly = false; + endDateInput.readOnly = false; + + chrome.storage.local.set({ + yesterdayContribution: false, + selectedTimeframe: null, + }); + }); + + chrome.storage.local.get(['selectedTimeframe', 'yesterdayContribution', 'startingDate', 'endingDate'], (items) => { + console.log('Restoring state:', items); + + if (items.startingDate && items.endingDate && !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; + endDateInput.readOnly = false; + } + document.querySelectorAll('input[name="timeframe"]').forEach((radio) => { + radio.checked = false; + radio.dataset.wasChecked = 'false'; + }); + return; + } + + if (!items.selectedTimeframe) { + items.selectedTimeframe = 'yesterdayContribution'; + items.yesterdayContribution = true; + } + + const radio = document.getElementById(items.selectedTimeframe); + if (radio) { + radio.checked = true; + radio.dataset.wasChecked = 'true'; + + const startDateInput = document.getElementById('startingDate'); + const endDateInput = document.getElementById('endingDate'); + + if (items.selectedTimeframe === 'yesterdayContribution') { + startDateInput.value = getYesterday(); + endDateInput.value = getToday(); + } + startDateInput.readOnly = endDateInput.readOnly = true; + + chrome.storage.local.set({ + startingDate: startDateInput.value, + endingDate: endDateInput.value, + yesterdayContribution: items.selectedTimeframe === 'yesterdayContribution', + selectedTimeframe: items.selectedTimeframe, + }); + } + }); + + // Save all fields to storage on input/change + projectNameInput.addEventListener('input', function () { + chrome.storage.local.set({ projectName: projectNameInput.value }); + }); + orgInput.addEventListener('input', function () { + chrome.storage.local.set({ orgName: orgInput.value.trim().toLowerCase() }); + }); + userReasonInput.addEventListener('input', function () { + chrome.storage.local.set({ userReason: userReasonInput.value }); + }); + showOpenLabelCheckbox.addEventListener('change', function () { + chrome.storage.local.set({ showOpenLabel: showOpenLabelCheckbox.checked }); + }); + showCommitsCheckbox.addEventListener('change', function () { + chrome.storage.local.set({ showCommits: showCommitsCheckbox.checked }); + }); + githubTokenInput.addEventListener('input', function () { + chrome.storage.local.set({ githubToken: githubTokenInput.value }); + }); + cacheInput.addEventListener('input', function () { + chrome.storage.local.set({ cacheInput: cacheInput.value }); + }); + if (enableToggleSwitch) { + console.log('[DEBUG] Setting up enable toggle switch event listener'); + enableToggleSwitch.addEventListener('change', function () { + console.log('[DEBUG] Enable toggle changed to:', enableToggleSwitch.checked); + chrome.storage.local.set({ enableToggle: enableToggleSwitch.checked }); + }); + } + yesterdayRadio.addEventListener('change', function () { + chrome.storage.local.set({ yesterdayContribution: yesterdayRadio.checked }); + }); + startingDateInput.addEventListener('input', function () { + chrome.storage.local.set({ startingDate: startingDateInput.value }); + }); + endingDateInput.addEventListener('input', function () { + chrome.storage.local.set({ endingDate: endingDateInput.value }); + }); + + // Save username to storage on input + platformUsername.addEventListener('input', function () { + chrome.storage.local.get(['platform'], function (result) { + const platform = result.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + chrome.storage.local.set({ [platformUsernameKey]: platformUsername.value }); + }); + }); + } + + function showReportView() { + isSettingsVisible = false; + reportSection.classList.remove('hidden'); + settingsSection.classList.add('hidden'); + settingsToggle.classList.remove('active'); + } + + function showSettingsView() { + isSettingsVisible = true; + reportSection.classList.add('hidden'); + settingsSection.classList.remove('hidden'); + settingsToggle.classList.add('active'); + } + + if (settingsToggle) { + settingsToggle.addEventListener('click', function () { + if (isSettingsVisible) { + showReportView(); + } else { + showSettingsView(); + } + }); + } + + if (homeButton) { + homeButton.addEventListener('click', showReportView); + } + if (scrumHelperHeading) { + scrumHelperHeading.addEventListener('click', showReportView); + } + + showReportView(); + + chrome.storage.local.get(['orgName'], function (result) { + orgInput.value = result.orgName || ''; + }); + + // Debug function to test storage + window.testStorage = function () { + chrome.storage.local.get(['enableToggle'], function (result) { + console.log('[TEST] Current enableToggle value:', result.enableToggle); + }); + }; + + //report filter + const repoSearch = document.getElementById('repoSearch'); + const repoDropdown = document.getElementById('repoDropdown'); + const selectedReposDiv = document.getElementById('selectedRepos'); + const repoTags = document.getElementById('repoTags'); + const repoPlaceholder = document.getElementById('repoPlaceholder'); + const repoCount = document.getElementById('repoCount'); + const repoStatus = document.getElementById('repoStatus'); + const useRepoFilter = document.getElementById('useRepoFilter'); + const repoFilterContainer = document.getElementById('repoFilterContainer'); + + if (repoSearch && useRepoFilter && repoFilterContainer) { + repoSearch.addEventListener('click', function () { + if (!useRepoFilter.checked) { + useRepoFilter.checked = true; + repoFilterContainer.classList.remove('hidden'); + chrome.storage.local.set({ useRepoFilter: true }); + } + }); + } + + if (!repoSearch || !useRepoFilter) { + console.log('Repository, filter elements not found in DOM'); + } else { + let availableRepos = []; + let selectedRepos = []; + let highlightedIndex = -1; + + async function triggerRepoFetchIfEnabled() { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) {} + if (platform !== 'github') { + // Do not run repo fetch for non-GitHub platforms + if (repoStatus) repoStatus.textContent = 'Repository filtering is only available for GitHub.'; + return; + } + if (!useRepoFilter.checked) { + return; + } + + if (repoStatus) { + repoStatus.textContent = chrome.i18n.getMessage('repoRefetching'); + } + + try { + const cacheData = await new Promise((resolve) => { + chrome.storage.local.get(['repoCache'], resolve); + }); + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], resolve); + }); + + const platform = items.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + const username = items[platformUsernameKey]; + + if (!username) { + if (repoStatus) { + repoStatus.textContent = 'Username required'; + } + return; + } + + if (window.fetchUserRepositories) { + const repos = await window.fetchUserRepositories(username, items.githubToken, items.orgName || ''); + + availableRepos = repos; + + if (repoStatus) { + repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [repos.length]); + } + + const repoCacheKey = `repos-${username}-${items.orgName || ''}`; + chrome.storage.local.set({ + repoCache: { + data: repos, + cacheKey: repoCacheKey, + timestamp: Date.now(), + }, + }); + + if (document.activeElement === repoSearch) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } else if (repoSearch.value) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } else { + filterAndDisplayRepos(''); + } + } + } catch (err) { + if (repoStatus) { + repoStatus.textContent = `${chrome.i18n.getMessage('errorLabel')}: ${err.message || chrome.i18n.getMessage('repoRefetchFailed')}`; + } + } + } + + window.triggerRepoFetchIfEnabled = triggerRepoFetchIfEnabled; + + chrome.storage.local.get(['selectedRepos', 'useRepoFilter'], (items) => { + if (items.selectedRepos) { + selectedRepos = items.selectedRepos; + updateRepoDisplay(); + } + if (items.useRepoFilter) { + useRepoFilter.checked = items.useRepoFilter; + repoFilterContainer.classList.toggle('hidden', !items.useRepoFilter); + } + }); + + useRepoFilter.addEventListener( + 'change', + debounce(async () => { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) {} + if (platform !== 'github') { + repoFilterContainer.classList.add('hidden'); + useRepoFilter.checked = false; + if (repoStatus) repoStatus.textContent = 'Repository filtering is only available for GitHub.'; + return; + } + const enabled = useRepoFilter.checked; + const hasToken = githubTokenInput.value.trim() !== ''; + repoFilterContainer.classList.toggle('hidden', !enabled); + + if (enabled && !hasToken) { + useRepoFilter.checked = false; + repoFilterContainer.classList.add('hidden'); // Explicitly hide the container + hideDropdown(); + const tokenWarning = document.getElementById('tokenWarningForFilter'); + if (tokenWarning) { + tokenWarning.classList.remove('hidden'); + tokenWarning.classList.add('shake-animation'); + setTimeout(() => tokenWarning.classList.remove('shake-animation'), 620); + setTimeout(() => { + tokenWarning.classList.add('hidden'); + }, 3000); + } + return; + } + repoFilterContainer.classList.toggle('hidden', !enabled); + + chrome.storage.local.set({ + useRepoFilter: enabled, + githubCache: null, //forces refresh + }); + checkTokenForFilter(); + if (enabled) { + repoStatus.textContent = 'Loading repos automatically..'; + + try { + const cacheData = await new Promise((resolve) => { + chrome.storage.local.get(['repoCache'], resolve); + }); + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], resolve); + }); + + const platform = items.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + const username = items[platformUsernameKey]; + + if (!username) { + repoStatus.textContent = 'Github Username required'; + + return; + } + + const repoCacheKey = `repos-${username}-${items.orgName || ''}`; + + const now = Date.now(); + const cacheAge = cacheData.repoCache?.timestamp ? now - cacheData.repoCache.timestamp : Infinity; + const cacheTTL = 10 * 60 * 1000; // 10 minutes + + if (cacheData.repoCache && cacheData.repoCache.cacheKey === repoCacheKey && cacheAge < cacheTTL) { + console.log('Using cached repositories'); + availableRepos = cacheData.repoCache.data; + repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [availableRepos.length]); + + if (document.activeElement === repoSearch) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } + return; + } + + if (window.fetchUserRepositories) { + const repos = await window.fetchUserRepositories( + username, + + items.githubToken, + items.orgName || '', + ); + availableRepos = repos; + repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [repos.length]); + + chrome.storage.local.set({ + repoCache: { + data: repos, + cacheKey: repoCacheKey, + timestamp: now, + }, + }); + + if (document.activeElement === repoSearch) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } + } + } catch (err) { + console.error('Auto load repos failed', err); + + if (err.message?.includes('401')) { + repoStatus.textContent = chrome.i18n.getMessage('repoTokenPrivate'); + } else if (err.message?.includes('username')) { + repoStatus.textContent = chrome.i18n.getMessage('githubUsernamePlaceholder'); + } else { + repoStatus.textContent = `${chrome.i18n.getMessage('errorLabel')}: ${err.message || chrome.i18n.getMessage('repoLoadFailed')}`; + } + } + } else { + selectedRepos = []; + updateRepoDisplay(); + chrome.storage.local.set({ selectedRepos: [] }); + repoStatus.textContent = ''; + } + }, 300), + ); + + repoSearch.addEventListener('keydown', (e) => { + const items = repoDropdown.querySelectorAll('.repository-dropdown-item'); + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + highlightedIndex = Math.min(highlightedIndex + 1, items.length - 1); + updateHighlight(items); + break; + case 'ArrowUp': + e.preventDefault(); + highlightedIndex = Math.max(highlightedIndex - 1, 0); + updateHighlight(items); + break; + case 'Enter': + e.preventDefault(); + if (highlightedIndex >= 0 && items[highlightedIndex]) { + fnSelectedRepos(items[highlightedIndex].dataset.repoName); + } + break; + case 'Escape': + hideDropdown(); + break; + } + }); + + repoSearch.addEventListener('input', (e) => { + const query = e.target.value.toLowerCase(); + filterAndDisplayRepos(query); + }); + let programmaticFocus = false; + repoSearch.addEventListener('focus', function () { + if (programmaticFocus) { + programmaticFocus = false; + return; + } + if (repoSearch.value) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } else if (availableRepos.length > 0) { + filterAndDisplayRepos(''); + } + }); + + document.addEventListener('click', (e) => { + if (!e.target.closest('#repoSearch') && !e.target.closest('#repoDropdown')) { + hideDropdown(); + } + }); + + function debugRepoFetch() { + chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], (items) => { + const platform = items.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + const username = items[platformUsernameKey]; + console.log('Current settings:', { + username: username, + hasToken: !!items.githubToken, + org: items.orgName || '', + }); + }); + } + debugRepoFetch(); + async function loadRepos() { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) {} + if (platform !== 'github') { + if (repoStatus) repoStatus.textContent = 'Repository loading is only available for GitHub.'; + return; + } + console.log('window.fetchUserRepositories exists:', !!window.fetchUserRepositories); + console.log( + 'Available functions:', + Object.keys(window).filter((key) => key.includes('fetch')), + ); + + if (!window.fetchUserRepositories) { + repoStatus.textContent = 'Repository fetching not available'; + return; + } + + chrome.storage.local.get(['platform', 'githubUsername', 'githubToken'], (items) => { + const platform = items.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + const username = items[platformUsernameKey]; + console.log('Storage data for repo fetch:', { + hasUsername: !!username, + hasToken: !!items.githubToken, + username: username, + }); + + if (!username) { + repoStatus.textContent = 'Username required'; + + return; + } + + performRepoFetch(); + }); + } + + async function performRepoFetch() { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) {} + if (platform !== 'github') { + if (repoStatus) repoStatus.textContent = 'Repository fetching is only available for GitHub.'; + return; + } + console.log('[POPUP-DEBUG] performRepoFetch called.'); + repoStatus.textContent = chrome.i18n.getMessage('repoLoading'); + repoSearch.classList.add('repository-search-loading'); + + try { + const cacheData = await new Promise((resolve) => { + chrome.storage.local.get(['repoCache'], resolve); + }); + const storageItems = await new Promise((resolve) => { + chrome.storage.local.get(['platform', 'githubUsername', 'githubToken', 'orgName'], resolve); + }); + const platform = storageItems.platform || 'github'; + const platformUsernameKey = `${platform}Username`; + const username = storageItems[platformUsernameKey]; + const repoCacheKey = `repos-${username}-${storageItems.orgName || ''}`; + const now = Date.now(); + const cacheAge = cacheData.repoCache?.timestamp ? now - cacheData.repoCache.timestamp : Infinity; + const cacheTTL = 10 * 60 * 1000; // 10 minutes + + console.log('[POPUP-DEBUG] Repo cache check:', { + key: repoCacheKey, + cacheKeyInCache: cacheData.repoCache?.cacheKey, + isMatch: cacheData.repoCache?.cacheKey === repoCacheKey, + age: cacheAge, + isFresh: cacheAge < cacheTTL, + }); + + if (cacheData.repoCache && cacheData.repoCache.cacheKey === repoCacheKey && cacheAge < cacheTTL) { + console.log('[POPUP-DEBUG] Using cached repositories in manual fetch'); + availableRepos = cacheData.repoCache.data; + repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [availableRepos.length]); + + if (document.activeElement === repoSearch) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } + return; + } + console.log('[POPUP-DEBUG] No valid cache. Fetching from network.'); + availableRepos = await window.fetchUserRepositories( + username, + + storageItems.githubToken, + storageItems.orgName || '', + ); + repoStatus.textContent = chrome.i18n.getMessage('repoLoaded', [availableRepos.length]); + console.log(`[POPUP-DEBUG] Fetched and loaded ${availableRepos.length} repos.`); + + chrome.storage.local.set({ + repoCache: { + data: availableRepos, + cacheKey: repoCacheKey, + timestamp: now, + }, + }); + + if (document.activeElement === repoSearch) { + filterAndDisplayRepos(repoSearch.value.toLowerCase()); + } + } catch (err) { + console.error(`Failed to load repos:`, err); + + if (err.message && err.message.includes('401')) { + repoStatus.textContent = chrome.i18n.getMessage('repoTokenPrivate'); + } else if (err.message && err.message.includes('username')) { + repoStatus.textContent = chrome.i18n.getMessage('githubUsernamePlaceholder'); + } else { + repoStatus.textContent = `${chrome.i18n.getMessage('errorLabel')}: ${err.message || chrome.i18n.getMessage('repoLoadFailed')}`; + } + } finally { + repoSearch.classList.remove('repository-search-loading'); + } + } + + function filterAndDisplayRepos(query) { + if (availableRepos.length === 0) { + repoDropdown.innerHTML = `${chrome.i18n.getMessage('orgClearedMessage')}
`; - } - chrome.storage.local.remove(['githubCache', 'repoCache']); - triggerRepoFetchIfEnabled(); - setOrgBtn.disabled = false; - setOrgBtn.innerHTML = originalText; - }); - return; - } - - setOrgBtn.disabled = true; - const originalText = setOrgBtn.innerHTML; - setOrgBtn.innerHTML = ''; - - fetch(`https://api.github.com/orgs/${org}`) - .then(res => { - if (res.status === 404) { - setOrgBtn.disabled = false; - setOrgBtn.innerHTML = originalText; - const oldToast = document.getElementById('invalid-org-toast'); - if (oldToast) oldToast.parentNode.removeChild(oldToast); - const toastDiv = document.createElement('div'); - toastDiv.id = 'invalid-org-toast'; - toastDiv.className = 'toast'; - toastDiv.style.background = '#dc2626'; - toastDiv.style.color = '#fff'; - toastDiv.style.fontWeight = 'bold'; - toastDiv.style.padding = '12px 24px'; - toastDiv.style.borderRadius = '8px'; - toastDiv.style.position = 'fixed'; - toastDiv.style.top = '24px'; - toastDiv.style.left = '50%'; - toastDiv.style.transform = 'translateX(-50%)'; - toastDiv.style.zIndex = '9999'; - toastDiv.innerText = chrome.i18n.getMessage('orgNotFoundMessage'); - document.body.appendChild(toastDiv); - setTimeout(() => { - if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); - }, 3000); - return; - } - 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 - const scrumReport = document.getElementById('scrumReport'); - if (scrumReport) { - scrumReport.innerHTML = `${chrome.i18n.getMessage('orgChangedMessage')}
`; - } - // Clear the githubCache for previous org - chrome.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'; - toastDiv.style.background = '#10b981'; - toastDiv.style.color = '#fff'; - toastDiv.style.fontWeight = 'bold'; - toastDiv.style.padding = '12px 24px'; - toastDiv.style.borderRadius = '8px'; - toastDiv.style.position = 'fixed'; - toastDiv.style.top = '24px'; - toastDiv.style.left = '50%'; - toastDiv.style.transform = 'translateX(-50%)'; - toastDiv.style.zIndex = '9999'; - toastDiv.innerText = chrome.i18n.getMessage('orgSetMessage'); - document.body.appendChild(toastDiv); - setTimeout(() => { - if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); - }, 2500); - - }); - }) - .catch((err) => { - setOrgBtn.disabled = false; - setOrgBtn.innerHTML = originalText; - const oldToast = document.getElementById('invalid-org-toast'); - if (oldToast) oldToast.parentNode.removeChild(oldToast); - const toastDiv = document.createElement('div'); - toastDiv.id = 'invalid-org-toast'; - toastDiv.className = 'toast'; - toastDiv.style.background = '#dc2626'; - toastDiv.style.color = '#fff'; - toastDiv.style.fontWeight = 'bold'; - toastDiv.style.padding = '12px 24px'; - toastDiv.style.borderRadius = '8px'; - toastDiv.style.position = 'fixed'; - toastDiv.style.top = '24px'; - toastDiv.style.left = '50%'; - toastDiv.style.transform = 'translateX(-50%)'; - toastDiv.style.zIndex = '9999'; - toastDiv.innerText = chrome.i18n.getMessage('orgValidationErrorMessage'); - document.body.appendChild(toastDiv); - setTimeout(() => { - if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); - }, 3000); - }); + let org = orgInput.value.trim().toLowerCase(); + + console.log('[Org Check] Checking organization:', org); + if (!org) { + // If org is empty, clear orgName in storage but don't auto-generate report + chrome.storage.local.set({ orgName: '' }, function () { + console.log('[Org Check] Organization cleared from storage'); + const scrumReport = document.getElementById('scrumReport'); + if (scrumReport) { + scrumReport.innerHTML = `${chrome.i18n.getMessage('orgClearedMessage')}
`; + } + chrome.storage.local.remove(['githubCache', 'repoCache']); + triggerRepoFetchIfEnabled(); + setOrgBtn.disabled = false; + setOrgBtn.innerHTML = originalText; + }); + return; + } + + setOrgBtn.disabled = true; + const originalText = setOrgBtn.innerHTML; + setOrgBtn.innerHTML = ''; + + fetch(`https://api.github.com/orgs/${org}`) + .then((res) => { + if (res.status === 404) { + setOrgBtn.disabled = false; + setOrgBtn.innerHTML = originalText; + const oldToast = document.getElementById('invalid-org-toast'); + if (oldToast) oldToast.parentNode.removeChild(oldToast); + const toastDiv = document.createElement('div'); + toastDiv.id = 'invalid-org-toast'; + toastDiv.className = 'toast'; + toastDiv.style.background = '#dc2626'; + toastDiv.style.color = '#fff'; + toastDiv.style.fontWeight = 'bold'; + toastDiv.style.padding = '12px 24px'; + toastDiv.style.borderRadius = '8px'; + toastDiv.style.position = 'fixed'; + toastDiv.style.top = '24px'; + toastDiv.style.left = '50%'; + toastDiv.style.transform = 'translateX(-50%)'; + toastDiv.style.zIndex = '9999'; + toastDiv.innerText = chrome.i18n.getMessage('orgNotFoundMessage'); + document.body.appendChild(toastDiv); + setTimeout(() => { + if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); + }, 3000); + return; + } + 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 + const scrumReport = document.getElementById('scrumReport'); + if (scrumReport) { + scrumReport.innerHTML = `${chrome.i18n.getMessage('orgChangedMessage')}
`; + } + // Clear the githubCache for previous org + chrome.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'; + toastDiv.style.background = '#10b981'; + toastDiv.style.color = '#fff'; + toastDiv.style.fontWeight = 'bold'; + toastDiv.style.padding = '12px 24px'; + toastDiv.style.borderRadius = '8px'; + toastDiv.style.position = 'fixed'; + toastDiv.style.top = '24px'; + toastDiv.style.left = '50%'; + toastDiv.style.transform = 'translateX(-50%)'; + toastDiv.style.zIndex = '9999'; + toastDiv.innerText = chrome.i18n.getMessage('orgSetMessage'); + document.body.appendChild(toastDiv); + setTimeout(() => { + if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); + }, 2500); + }); + }) + .catch((err) => { + setOrgBtn.disabled = false; + setOrgBtn.innerHTML = originalText; + const oldToast = document.getElementById('invalid-org-toast'); + if (oldToast) oldToast.parentNode.removeChild(oldToast); + const toastDiv = document.createElement('div'); + toastDiv.id = 'invalid-org-toast'; + toastDiv.className = 'toast'; + toastDiv.style.background = '#dc2626'; + toastDiv.style.color = '#fff'; + toastDiv.style.fontWeight = 'bold'; + toastDiv.style.padding = '12px 24px'; + toastDiv.style.borderRadius = '8px'; + toastDiv.style.position = 'fixed'; + toastDiv.style.top = '24px'; + toastDiv.style.left = '50%'; + toastDiv.style.transform = 'translateX(-50%)'; + toastDiv.style.zIndex = '9999'; + toastDiv.innerText = chrome.i18n.getMessage('orgValidationErrorMessage'); + document.body.appendChild(toastDiv); + setTimeout(() => { + if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); + }, 3000); + }); }); let cacheInput = document.getElementById('cacheInput'); if (cacheInput) { - chrome.storage.local.get(['cacheInput'], function (result) { - if (result.cacheInput) { - cacheInput.value = result.cacheInput; - } else { - cacheInput.value = 10; - } - }); - - cacheInput.addEventListener('blur', function () { - let ttlValue = parseInt(this.value); - if (isNaN(ttlValue) || ttlValue <= 0 || this.value.trim() === '') { - ttlValue = 10; - this.value = ttlValue; - this.style.borderColor = '#ef4444'; - } else if (ttlValue > 1440) { - ttlValue = 1440; - this.value = ttlValue; - this.style.borderColor = '#f59e0b'; - } else { - this.style.borderColor = '#10b981'; - } - - chrome.storage.local.set({ cacheInput: ttlValue }, function () { - console.log('Cache TTL saved:', ttlValue, 'minutes'); - }); - }); + chrome.storage.local.get(['cacheInput'], function (result) { + if (result.cacheInput) { + cacheInput.value = result.cacheInput; + } else { + cacheInput.value = 10; + } + }); + + cacheInput.addEventListener('blur', function () { + let ttlValue = parseInt(this.value); + if (isNaN(ttlValue) || ttlValue <= 0 || this.value.trim() === '') { + ttlValue = 10; + this.value = ttlValue; + this.style.borderColor = '#ef4444'; + } else if (ttlValue > 1440) { + ttlValue = 1440; + this.value = ttlValue; + this.style.borderColor = '#f59e0b'; + } else { + this.style.borderColor = '#10b981'; + } + + chrome.storage.local.set({ cacheInput: ttlValue }, function () { + console.log('Cache TTL saved:', ttlValue, 'minutes'); + }); + }); } - chrome.storage.local.get(['platform'], function (result) { - const platform = result.platform || 'github'; - platformSelect.value = platform; - updatePlatformUI(platform); + const platform = result.platform || 'github'; + platformSelect.value = platform; + updatePlatformUI(platform); }); // Update UI for platform function updatePlatformUI(platform) { - const usernameLabel = document.getElementById('usernameLabel'); - if (usernameLabel) { - if (platform === 'gitlab') { - usernameLabel.setAttribute('data-i18n', 'gitlabUsernameLabel'); - } else { - usernameLabel.setAttribute('data-i18n', 'githubUsernameLabel'); - } - const key = usernameLabel.getAttribute('data-i18n'); - const message = chrome.i18n.getMessage(key); - if (message) { - usernameLabel.textContent = message; - } - } - - const orgSection = document.querySelector('.orgSection'); - if (orgSection) { - if (platform === 'gitlab') { - orgSection.classList.add('hidden'); - } else { - orgSection.classList.remove('hidden'); - } - } - const githubOnlySections = document.querySelectorAll('.githubOnlySection'); - githubOnlySections.forEach(el => { - if (platform === 'gitlab') { - el.classList.add('hidden'); - } else { - el.classList.remove('hidden'); - } - }); + // Handle GitHub token section visibility + const githubTokenSection = document.querySelector('.githubTokenSection'); + if (githubTokenSection) { + if (platform === 'github') { + githubTokenSection.classList.remove('hidden'); + } else { + githubTokenSection.classList.add('hidden'); + } + } + + // Handle GitLab token section visibility + const gitlabTokenSection = document.querySelector('.gitlabTokenSection'); + if (gitlabTokenSection) { + if (platform === 'gitlab') { + gitlabTokenSection.classList.remove('hidden'); + } else { + gitlabTokenSection.classList.add('hidden'); + } + } + + // Hide GitHub-specific settings for GitLab using the 'hidden' class + + const usernameLabel = document.getElementById('usernameLabel'); + if (usernameLabel) { + if (platform === 'gitlab') { + usernameLabel.setAttribute('data-i18n', 'gitlabUsernameLabel'); + } else { + usernameLabel.setAttribute('data-i18n', 'githubUsernameLabel'); + } + const key = usernameLabel.getAttribute('data-i18n'); + const message = chrome.i18n.getMessage(key); + if (message) { + usernameLabel.textContent = message; + } + } + + const orgSection = document.querySelector('.orgSection'); + if (orgSection) { + if (platform === 'gitlab') { + orgSection.classList.add('hidden'); + } else { + orgSection.classList.remove('hidden'); + } + } + + const githubOnlySections = document.querySelectorAll('.githubOnlySection'); + githubOnlySections.forEach((el) => { + if (platform === 'gitlab') { + el.classList.add('hidden'); + } else { + el.classList.remove('hidden'); + } + }); } platformSelect.addEventListener('change', function () { - const platform = platformSelect.value; - chrome.storage.local.set({ platform }); - const platformUsername = document.getElementById('platformUsername'); - if (platformUsername) { - const currentPlatform = platformSelect.value === 'github' ? 'gitlab' : 'github'; // Get the platform we're switching from - const currentUsername = platformUsername.value; - if (currentUsername.trim()) { - chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); - } - } - - chrome.storage.local.get([`${platform}Username`], function (result) { - if (platformUsername) { - platformUsername.value = result[`${platform}Username`] || ''; - } - }); - - updatePlatformUI(platform); + const platform = platformSelect.value; + chrome.storage.local.set({ platform }); + const platformUsername = document.getElementById('platformUsername'); + if (platformUsername) { + const currentPlatform = platformSelect.value === 'github' ? 'gitlab' : 'github'; // Get the platform we're switching from + const currentUsername = platformUsername.value; + if (currentUsername.trim()) { + chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); + } + } + + chrome.storage.local.get([`${platform}Username`], function (result) { + if (platformUsername) { + platformUsername.value = result[`${platform}Username`] || ''; + } + }); + + updatePlatformUI(platform); }); const customDropdown = document.getElementById('customPlatformDropdown'); @@ -1376,361 +1416,358 @@ const dropdownSelected = document.getElementById('platformDropdownSelected'); const platformSelectHidden = document.getElementById('platformSelect'); function setPlatformDropdown(value) { - if (value === 'gitlab') { - dropdownSelected.innerHTML = ' GitLab'; - } else { - dropdownSelected.innerHTML = ' GitHub'; - } - - const platformUsername = document.getElementById('platformUsername'); - if (platformUsername) { - const currentPlatform = platformSelectHidden.value; - const currentUsername = platformUsername.value; - if (currentUsername.trim()) { - chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); - } - } - - platformSelectHidden.value = value; - chrome.storage.local.set({ platform: value }); - - chrome.storage.local.get([`${value}Username`], function (result) { - if (platformUsername) { - platformUsername.value = result[`${value}Username`] || ''; - } - }); - - updatePlatformUI(value); + if (value === 'gitlab') { + dropdownSelected.innerHTML = ' GitLab'; + } else { + dropdownSelected.innerHTML = ' GitHub'; + } + + const platformUsername = document.getElementById('platformUsername'); + if (platformUsername) { + const currentPlatform = platformSelectHidden.value; + const currentUsername = platformUsername.value; + if (currentUsername.trim()) { + chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); + } + } + + platformSelectHidden.value = value; + chrome.storage.local.set({ platform: value }); + + chrome.storage.local.get([`${value}Username`], function (result) { + if (platformUsername) { + platformUsername.value = result[`${value}Username`] || ''; + } + }); + + updatePlatformUI(value); } dropdownBtn.addEventListener('click', function (e) { - e.stopPropagation(); - customDropdown.classList.toggle('open'); - dropdownList.classList.toggle('hidden'); + e.stopPropagation(); + customDropdown.classList.toggle('open'); + dropdownList.classList.toggle('hidden'); }); -dropdownList.querySelectorAll('li').forEach(item => { - item.addEventListener('click', function (e) { - const newPlatform = this.getAttribute('data-value'); - const currentPlatform = platformSelectHidden.value; - - if (newPlatform !== currentPlatform) { - const platformUsername = document.getElementById('platformUsername'); - if (platformUsername) { - const currentUsername = platformUsername.value; - if (currentUsername.trim()) { - chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); - } - } - } - - setPlatformDropdown(newPlatform); - customDropdown.classList.remove('open'); - dropdownList.classList.add('hidden'); - }); +dropdownList.querySelectorAll('li').forEach((item) => { + item.addEventListener('click', function (e) { + const newPlatform = this.getAttribute('data-value'); + const currentPlatform = platformSelectHidden.value; + + if (newPlatform !== currentPlatform) { + const platformUsername = document.getElementById('platformUsername'); + if (platformUsername) { + const currentUsername = platformUsername.value; + if (currentUsername.trim()) { + chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); + } + } + } + + setPlatformDropdown(newPlatform); + customDropdown.classList.remove('open'); + dropdownList.classList.add('hidden'); + }); }); document.addEventListener('click', function (e) { - if (!customDropdown.contains(e.target)) { - customDropdown.classList.remove('open'); - dropdownList.classList.add('hidden'); - } + if (!customDropdown.contains(e.target)) { + customDropdown.classList.remove('open'); + dropdownList.classList.add('hidden'); + } }); // Keyboard navigation platformDropdownBtn.addEventListener('keydown', function (e) { - if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - customDropdown.classList.add('open'); - dropdownList.classList.remove('hidden'); - dropdownList.querySelector('li').focus(); - } + if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + customDropdown.classList.add('open'); + dropdownList.classList.remove('hidden'); + dropdownList.querySelector('li').focus(); + } }); dropdownList.querySelectorAll('li').forEach((item, idx, arr) => { - item.setAttribute('tabindex', '0'); - item.addEventListener('keydown', function (e) { - if (e.key === 'ArrowDown') { - e.preventDefault(); - (arr[idx + 1] || arr[0]).focus(); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - (arr[idx - 1] || arr[arr.length - 1]).focus(); - } else if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - const newPlatform = this.getAttribute('data-value'); - const currentPlatform = platformSelectHidden.value; - - // Save current username for current platform before switching - if (newPlatform !== currentPlatform) { - const platformUsername = document.getElementById('platformUsername'); - if (platformUsername) { - const currentUsername = platformUsername.value; - if (currentUsername.trim()) { - chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); - } - } - } - - setPlatformDropdown(newPlatform); - customDropdown.classList.remove('open'); - dropdownList.classList.add('hidden'); - dropdownBtn.focus(); - } - }); + item.setAttribute('tabindex', '0'); + item.addEventListener('keydown', function (e) { + if (e.key === 'ArrowDown') { + e.preventDefault(); + (arr[idx + 1] || arr[0]).focus(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + (arr[idx - 1] || arr[arr.length - 1]).focus(); + } else if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + const newPlatform = this.getAttribute('data-value'); + const currentPlatform = platformSelectHidden.value; + + // Save current username for current platform before switching + if (newPlatform !== currentPlatform) { + const platformUsername = document.getElementById('platformUsername'); + if (platformUsername) { + const currentUsername = platformUsername.value; + if (currentUsername.trim()) { + chrome.storage.local.set({ [`${currentPlatform}Username`]: currentUsername }); + } + } + } + + setPlatformDropdown(newPlatform); + customDropdown.classList.remove('open'); + dropdownList.classList.add('hidden'); + dropdownBtn.focus(); + } + }); }); // On load, restore platform from storage chrome.storage.local.get(['platform'], function (result) { - const platform = result.platform || 'github'; - // Just update the UI without clearing username when restoring from storage - if (platform === 'gitlab') { - dropdownSelected.innerHTML = ' GitLab'; - } else { - dropdownSelected.innerHTML = ' GitHub'; - } - platformSelectHidden.value = platform; - updatePlatformUI(platform); + const platform = result.platform || 'github'; + // Just update the UI without clearing username when restoring from storage + if (platform === 'gitlab') { + dropdownSelected.innerHTML = ' GitLab'; + } else { + dropdownSelected.innerHTML = ' GitHub'; + } + platformSelectHidden.value = platform; + updatePlatformUI(platform); }); - - - - -// Tooltip bubble -document.querySelectorAll('.tooltip-container').forEach(container => { - const bubble = container.querySelector('.tooltip-bubble'); - if (!bubble) return; - - function positionTooltip() { - const icon = container.querySelector('.question-icon') || container; - const rect = icon.getBoundingClientRect(); - const bubbleRect = bubble.getBoundingClientRect(); - const padding = 8; - - let top = rect.top + window.scrollY; - let left = rect.right + padding + window.scrollX; - - if (left + bubbleRect.width > window.innerWidth - 10) { - left = rect.left - bubbleRect.width - padding + window.scrollX; - } - if (left < 8) left = 8; - if (top + bubbleRect.height > window.innerHeight - 10) { - top = rect.top - bubbleRect.height - padding + window.scrollY; - } - if (top < 8) top = 8; - - bubble.style.left = left + 'px'; - bubble.style.top = top + 'px'; - } - - container.addEventListener('mouseenter', positionTooltip); - container.addEventListener('focusin', positionTooltip); - container.addEventListener('mousemove', positionTooltip); - container.addEventListener('mouseleave', () => { - bubble.style.left = ''; - bubble.style.top = ''; - }); - container.addEventListener('focusout', () => { - bubble.style.left = ''; - bubble.style.top = ''; - }); +// Tooltip bubble +document.querySelectorAll('.tooltip-container').forEach((container) => { + const bubble = container.querySelector('.tooltip-bubble'); + if (!bubble) return; + + function positionTooltip() { + const icon = container.querySelector('.question-icon') || container; + const rect = icon.getBoundingClientRect(); + const bubbleRect = bubble.getBoundingClientRect(); + const padding = 8; + + let top = rect.top + window.scrollY; + let left = rect.right + padding + window.scrollX; + + if (left + bubbleRect.width > window.innerWidth - 10) { + left = rect.left - bubbleRect.width - padding + window.scrollX; + } + if (left < 8) left = 8; + if (top + bubbleRect.height > window.innerHeight - 10) { + top = rect.top - bubbleRect.height - padding + window.scrollY; + } + if (top < 8) top = 8; + + bubble.style.left = left + 'px'; + bubble.style.top = top + 'px'; + } + + container.addEventListener('mouseenter', positionTooltip); + container.addEventListener('focusin', positionTooltip); + container.addEventListener('mousemove', positionTooltip); + container.addEventListener('mouseleave', () => { + bubble.style.left = ''; + bubble.style.top = ''; + }); + container.addEventListener('focusout', () => { + bubble.style.left = ''; + bubble.style.top = ''; + }); }); // Radio button click handlers with toggle functionality -document.querySelectorAll('input[name="timeframe"]').forEach(radio => { - radio.addEventListener('click', function () { - if (this.dataset.wasChecked === 'true') { - this.checked = false; - this.dataset.wasChecked = 'false'; - - const startDateInput = document.getElementById('startingDate'); - const endDateInput = document.getElementById('endingDate'); - startDateInput.readOnly = false; - endDateInput.readOnly = false; - - chrome.storage.local.set({ - yesterdayContribution: false, - selectedTimeframe: null - }); - } else { - document.querySelectorAll('input[name="timeframe"]').forEach(r => { - r.dataset.wasChecked = 'false'; - }); - this.dataset.wasChecked = 'true'; - toggleRadio(this); - } - }); +document.querySelectorAll('input[name="timeframe"]').forEach((radio) => { + radio.addEventListener('click', function () { + if (this.dataset.wasChecked === 'true') { + this.checked = false; + this.dataset.wasChecked = 'false'; + + const startDateInput = document.getElementById('startingDate'); + const endDateInput = document.getElementById('endingDate'); + startDateInput.readOnly = false; + endDateInput.readOnly = false; + + chrome.storage.local.set({ + yesterdayContribution: false, + selectedTimeframe: null, + }); + } else { + document.querySelectorAll('input[name="timeframe"]').forEach((r) => { + r.dataset.wasChecked = 'false'; + }); + this.dataset.wasChecked = 'true'; + toggleRadio(this); + } + }); }); // refresh cache button document.getElementById('refreshCache').addEventListener('click', async function () { - const button = this; - const originalText = button.innerHTML; - - button.classList.add('loading'); - button.innerHTML = `${chrome.i18n.getMessage('refreshingButton')}`; - button.disabled = true; - - try { - // Determine platform - let platform = 'github'; - try { - const items = await new Promise(resolve => { - chrome.storage.local.get(['platform'], resolve); - }); - platform = items.platform || 'github'; - } catch (e) { } - - // Clear all caches - const keysToRemove = ['githubCache', 'repoCache', 'gitlabCache']; - await new Promise(resolve => { - chrome.storage.local.remove(keysToRemove, resolve); - }); - - // Clear the scrum report - const scrumReport = document.getElementById('scrumReport'); - if (scrumReport) { - scrumReport.innerHTML = `${chrome.i18n.getMessage('cacheClearedMessage')}
`; - } - - if (typeof availableRepos !== 'undefined') { - availableRepos = []; - } - - const repoStatus = document.getElementById('repoStatus'); - if (repoStatus) { - repoStatus.textContent = ''; - } - - button.innerHTML = `${chrome.i18n.getMessage('cacheClearedButton')}`; - button.classList.remove('loading'); - - // Do NOT trigger report generation automatically - - setTimeout(() => { - button.innerHTML = originalText; - button.disabled = false; - }, 2000); - - } catch (error) { - console.error('Cache clear failed:', error); - button.innerHTML = `${chrome.i18n.getMessage('cacheClearFailed')}`; - button.classList.remove('loading'); - - setTimeout(() => { - button.innerHTML = originalText; - button.disabled = false; - }, 3000); - } + const button = this; + const originalText = button.innerHTML; + + button.classList.add('loading'); + button.innerHTML = `${chrome.i18n.getMessage('refreshingButton')}`; + button.disabled = true; + + try { + // Determine platform + let platform = 'github'; + try { + const items = await new Promise((resolve) => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) {} + + // Clear all caches + const keysToRemove = ['githubCache', 'repoCache', 'gitlabCache']; + await new Promise((resolve) => { + chrome.storage.local.remove(keysToRemove, resolve); + }); + + // Clear the scrum report + const scrumReport = document.getElementById('scrumReport'); + if (scrumReport) { + scrumReport.innerHTML = `${chrome.i18n.getMessage('cacheClearedMessage')}
`; + } + + if (typeof availableRepos !== 'undefined') { + availableRepos = []; + } + + const repoStatus = document.getElementById('repoStatus'); + if (repoStatus) { + repoStatus.textContent = ''; + } + + button.innerHTML = `${chrome.i18n.getMessage('cacheClearedButton')}`; + button.classList.remove('loading'); + + // Do NOT trigger report generation automatically + + setTimeout(() => { + button.innerHTML = originalText; + button.disabled = false; + }, 2000); + } catch (error) { + console.error('Cache clear failed:', error); + button.innerHTML = `${chrome.i18n.getMessage('cacheClearFailed')}`; + button.classList.remove('loading'); + + setTimeout(() => { + button.innerHTML = originalText; + button.disabled = false; + }, 3000); + } }); const handleOrgInput = debounce(function () { - let org = orgInput.value.trim().toLowerCase(); - if (!org) { - chrome.storage.local.set({ orgName: '' }, () => { - console.log(`Org cleared, triggering repo fetch for all git`); - chrome.storage.local.remove(['githubCache', 'repoCache']); - triggerRepoFetchIfEnabled(); - }) - return; - } - console.log('[Org Check] Checking organization:', org); - fetch(`https://api.github.com/orgs/${org}`) - .then(res => { - console.log('[Org Check] Response status for', org, ':', res.status); - if (res.status === 404) { - console.log('[Org Check] Organization not found on GitHub:', org); - const oldToast = document.getElementById('invalid-org-toast'); - if (oldToast) oldToast.parentNode.removeChild(oldToast); - const toastDiv = document.createElement('div'); - toastDiv.id = 'invalid-org-toast'; - toastDiv.className = 'toast'; - toastDiv.style.background = '#dc2626'; - toastDiv.style.color = '#fff'; - toastDiv.style.fontWeight = 'bold'; - toastDiv.style.padding = '12px 24px'; - toastDiv.style.borderRadius = '8px'; - toastDiv.style.position = 'fixed'; - toastDiv.style.top = '24px'; - toastDiv.style.left = '50%'; - toastDiv.style.transform = 'translateX(-50%)'; - toastDiv.style.zIndex = '9999'; - toastDiv.innerText = chrome.i18n.getMessage('orgNotFoundMessage'); - document.body.appendChild(toastDiv); - setTimeout(() => { - if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); - }, 3000); - return; - } - const oldToast = document.getElementById('invalid-org-toast'); - if (oldToast) oldToast.parentNode.removeChild(oldToast); - console.log('[Org Check] Organisation exists on GitHub:', org); - chrome.storage.local.set({ orgName: org }, function () { - // if (window.generateScrumReport) window.generateScrumReport(); - triggerRepoFetchIfEnabled(); - }); - }) - .catch((err) => { - console.log('[Org Check] Error validating organisation:', org, err); - const oldToast = document.getElementById('invalid-org-toast'); - if (oldToast) oldToast.parentNode.removeChild(oldToast); - const toastDiv = document.createElement('div'); - toastDiv.id = 'invalid-org-toast'; - toastDiv.className = 'toast'; - toastDiv.style.background = '#dc2626'; - toastDiv.style.color = '#fff'; - toastDiv.style.fontWeight = 'bold'; - toastDiv.style.padding = '12px 24px'; - toastDiv.style.borderRadius = '8px'; - toastDiv.style.position = 'fixed'; - toastDiv.style.top = '24px'; - toastDiv.style.left = '50%'; - toastDiv.style.transform = 'translateX(-50%)'; - toastDiv.style.zIndex = '9999'; - toastDiv.innerText = chrome.i18n.getMessage('orgValidationErrorMessage'); - document.body.appendChild(toastDiv); - setTimeout(() => { - if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); - }, 3000); - }); + let org = orgInput.value.trim().toLowerCase(); + if (!org) { + chrome.storage.local.set({ orgName: '' }, () => { + console.log(`Org cleared, triggering repo fetch for all git`); + chrome.storage.local.remove(['githubCache', 'repoCache']); + triggerRepoFetchIfEnabled(); + }); + return; + } + console.log('[Org Check] Checking organization:', org); + fetch(`https://api.github.com/orgs/${org}`) + .then((res) => { + console.log('[Org Check] Response status for', org, ':', res.status); + if (res.status === 404) { + console.log('[Org Check] Organization not found on GitHub:', org); + const oldToast = document.getElementById('invalid-org-toast'); + if (oldToast) oldToast.parentNode.removeChild(oldToast); + const toastDiv = document.createElement('div'); + toastDiv.id = 'invalid-org-toast'; + toastDiv.className = 'toast'; + toastDiv.style.background = '#dc2626'; + toastDiv.style.color = '#fff'; + toastDiv.style.fontWeight = 'bold'; + toastDiv.style.padding = '12px 24px'; + toastDiv.style.borderRadius = '8px'; + toastDiv.style.position = 'fixed'; + toastDiv.style.top = '24px'; + toastDiv.style.left = '50%'; + toastDiv.style.transform = 'translateX(-50%)'; + toastDiv.style.zIndex = '9999'; + toastDiv.innerText = chrome.i18n.getMessage('orgNotFoundMessage'); + document.body.appendChild(toastDiv); + setTimeout(() => { + if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); + }, 3000); + return; + } + const oldToast = document.getElementById('invalid-org-toast'); + if (oldToast) oldToast.parentNode.removeChild(oldToast); + console.log('[Org Check] Organisation exists on GitHub:', org); + chrome.storage.local.set({ orgName: org }, function () { + // if (window.generateScrumReport) window.generateScrumReport(); + triggerRepoFetchIfEnabled(); + }); + }) + .catch((err) => { + console.log('[Org Check] Error validating organisation:', org, err); + const oldToast = document.getElementById('invalid-org-toast'); + if (oldToast) oldToast.parentNode.removeChild(oldToast); + const toastDiv = document.createElement('div'); + toastDiv.id = 'invalid-org-toast'; + toastDiv.className = 'toast'; + toastDiv.style.background = '#dc2626'; + toastDiv.style.color = '#fff'; + toastDiv.style.fontWeight = 'bold'; + toastDiv.style.padding = '12px 24px'; + toastDiv.style.borderRadius = '8px'; + toastDiv.style.position = 'fixed'; + toastDiv.style.top = '24px'; + toastDiv.style.left = '50%'; + toastDiv.style.transform = 'translateX(-50%)'; + toastDiv.style.zIndex = '9999'; + toastDiv.innerText = chrome.i18n.getMessage('orgValidationErrorMessage'); + document.body.appendChild(toastDiv); + setTimeout(() => { + if (toastDiv.parentNode) toastDiv.parentNode.removeChild(toastDiv); + }, 3000); + }); }, 3000); let lastInvalidOrg = ''; orgInput.addEventListener('input', handleOrgInput); function toggleRadio(radio) { - const startDateInput = document.getElementById('startingDate'); - const endDateInput = document.getElementById('endingDate'); - - console.log('Toggling radio:', radio.id); - - if (radio.id === 'yesterdayContribution') { - startDateInput.value = getYesterday(); - endDateInput.value = getToday(); - } - - startDateInput.readOnly = endDateInput.readOnly = true; - - chrome.storage.local.set({ - startingDate: startDateInput.value, - endingDate: endDateInput.value, - 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, - }); - - triggerRepoFetchIfEnabled(); - }); - + const startDateInput = document.getElementById('startingDate'); + const endDateInput = document.getElementById('endingDate'); + + console.log('Toggling radio:', radio.id); + + if (radio.id === 'yesterdayContribution') { + startDateInput.value = getYesterday(); + endDateInput.value = getToday(); + } + + startDateInput.readOnly = endDateInput.readOnly = true; + + chrome.storage.local.set( + { + startingDate: startDateInput.value, + endingDate: endDateInput.value, + 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, + }); + + triggerRepoFetchIfEnabled(); + }, + ); } async function triggerRepoFetchIfEnabled() { - if (window.triggerRepoFetchIfEnabled) { - await window.triggerRepoFetchIfEnabled(); - } -} \ No newline at end of file + if (window.triggerRepoFetchIfEnabled) { + await window.triggerRepoFetchIfEnabled(); + } +} diff --git a/src/scripts/scrumHelper.js b/src/scripts/scrumHelper.js index 2572cba..75ee245 100644 --- a/src/scripts/scrumHelper.js +++ b/src/scripts/scrumHelper.js @@ -6,14 +6,12 @@ function log(...args) { } } - function logError(...args) { if (DEBUG) { console.error('[SCRUM-HELPER]:', ...args); } } - let refreshButton_Placed = false; let enableToggle = true; let hasInjectedContent = false; @@ -41,6 +39,7 @@ function allIncluded(outputTarget = 'email') { let endingDate = ''; let platformUsernameLocal = ''; let githubToken = ''; + let gitlabToken = ''; let projectName = ''; let lastWeekArray = []; let nextWeekArray = []; @@ -76,13 +75,14 @@ function allIncluded(outputTarget = 'email') { '