diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e8bab6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,108 @@ +name: "Bug Report" +description: "Submit a bug report to help us improve" +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: We value your time and your efforts to submit this bug report. + - type: textarea + id: description + validations: + required: true + attributes: + label: "Description" + description: "A clear and concise description of what the bug is." + placeholder: "It bugs out when ..." + + - type: textarea + id: steps-to-reproduce + validations: + required: true + attributes: + label: "Reproduction steps" + description: "How do you trigger this bug? Please walk us through it step by step." + placeholder: "1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error" + + - type: textarea + id: expected-behavior + validations: + required: true + attributes: + label: "Expected behavior" + description: "What did you think should happen?" + placeholder: "It should ..." + + - type: textarea + id: actual-behavior + validations: + required: true + attributes: + label: "Actual Behavior with Screenshots" + description: "What did actually happen? Add screenshots, if applicable." + placeholder: "It actually ..." + + - type: dropdown + id: operating-system + attributes: + label: "💻 Operating system" + description: "What OS is your app running on?" + options: + - Linux + - MacOS + - Windows + - Something else + validations: + required: true + + - type: dropdown + id: browsers + attributes: + label: What browsers are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - Something else + + - type: textarea + id: additional-context + validations: + required: false + attributes: + label: "Provide any additional context for the Bug." + description: "Add any other context about the problem here." + placeholder: "It actually ..." + + - type: textarea + id: logs + validations: + required: false + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + + - type: checkboxes + id: no-duplicate-issues + attributes: + label: "Have you spent some time to check if this bug has been raised before?" + options: + - label: "I checked and didn't find similar issue" + required: true + + - type: dropdown + id: willing-to-submit-pr + attributes: + label: 🔗 Are you willing to submit PR? + description: This is absolutely not required, but we are happy to guide you in the contribution process. + options: + - "Yes, I am willing to submit a PR!" + - "No" + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/docs_improvement.yml b/.github/ISSUE_TEMPLATE/docs_improvement.yml new file mode 100644 index 0000000..01cf813 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_improvement.yml @@ -0,0 +1,48 @@ +name: "Docs Improvement" +description: "Suggest improvements or additions to the documentation" +title: "[Docs]: " +labels: ["documentation"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to help improve our documentation! Please provide the following details + + - type: input + id: current_section + attributes: + label: "Current Section" + description: "Which section of the documentation needs improvement?" + placeholder: "e.g., Installation Guide" + + - type: textarea + id: improvement_suggestion + attributes: + label: "Improvement Suggestion" + description: "Describe the improvement or addition you are suggesting." + placeholder: "e.g., Add more details about setting up the environment." + + - type: textarea + id: additional_context + attributes: + label: "Additional Context" + description: "Any additional context or information that might be helpful." + placeholder: "e.g., Links to related documentation, screenshots, etc." + + - type: checkboxes + id: no-duplicate-issues + attributes: + label: "Have you spent some time to check if this issue has been raised before?" + options: + - label: "I checked and didn't find similar issue" + required: true + + - type: dropdown + id: willing-to-submit-pr + attributes: + label: "Are you willing to submit PR?" + description: "This is absolutely not required, but we are happy to guide you in the contribution process." + options: + - "Yes I am willing to submit a PR!" + - "No" + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..819bba8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,63 @@ +name: Feature Request +description: "Submit a proposal for a new feature" +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: We value your time and your efforts to submit this feature request. + + - type: textarea + id: feature-description + validations: + required: true + attributes: + label: "Feature description" + description: "A clear and concise description of what the feature is." + placeholder: "You should add ..." + + - type: textarea + id: pitch + validations: + required: false + attributes: + label: "Why is this feature needed ?" + description: "Please explain why this feature should be implemented and how it would be used. Add examples, if applicable." + placeholder: "In my use-case, ..." + + - type: textarea + id: solution + validations: + required: false + attributes: + label: "How do you aim to achieve this?" + description: "A clear and concise description of what you want to happen." + placeholder: "I want this feature to, ..." + + - type: textarea + id: alternative + validations: + required: false + attributes: + label: "Additional Information" + description: "A clear and concise description of any alternative solutions or additional solutions you've considered." + placeholder: "I tried, ..." + + - type: checkboxes + id: no-duplicate-issues + attributes: + label: "Have you spent some time to check if this feature request has been raised before?" + options: + - label: "I checked and didn't find similar issue" + required: true + + - type: dropdown + id: willing-to-submit-pr + attributes: + label: Are you willing to submit PR? + description: This is absolutely not required, but we are happy to guide you in the contribution process. + options: + - "Yes I am willing to submit a PR!" + - "No" + validations: + required: true \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..51fb0ed --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,38 @@ +## Description +Briefly describe the purpose of this pull request and the changes introduced. + +## Type of Change +Select the relevant option(s): + +- [ ] Bug fix +- [ ] New feature +- [ ] Documentation update +- [ ] Code improvement or refactor +- [ ] UI/UX update +- [ ] Other (please specify) + +## Related Issue +Link any related issue(s): +`Closes #issue-number` or `Related to #issue-number` + +## Implementation Details +Summarize the technical changes made and their purpose. + +## Testing +Describe how the changes were tested and verified. + +> Example: +> - Run `npm run dev` and verify new section loads correctly. +> - Tested responsive layout on desktop and mobile. + +## Checklist +Please ensure the following before submitting: + +- [ ] Code follows project guidelines and conventions +- [ ] Changes tested locally and working as intended +- [ ] Documentation updated if required +- [ ] Linked related issue (if applicable) +- [ ] Commits are clear and descriptive + +## Additional Notes +Provide any additional context or dependencies reviewers should know. \ No newline at end of file diff --git a/scripts/content.js b/scripts/content.js index 13563e6..b2d9080 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -7,14 +7,85 @@ function getDirectDownloadLink(driveLink) { return null; } +function cleanFileName(fileName) { + if (!fileName) return null; + + // Trim whitespace first + let cleaned = fileName.trim(); + + // Remove common prefixes with flexible separators (spaces, colons, commas, hyphens) + cleaned = cleaned.replace(/^(Attachment[\s:,\-]*|PDF[\s:,\-]*|Word Document[\s:,\-]*|Microsoft Word[\s:,\-]*|Microsoft Excel[\s:,\-]*|Microsoft PowerPoint[\s:,\-]*|Google Docs[\s:,\-]*|Google Sheets[\s:,\-]*|Google Slides[\s:,\-]*|Document[\s:,\-]*|Spreadsheet[\s:,\-]*|Presentation[\s:,\-]*)+/gi, ''); + + // Trim again after removing prefixes + cleaned = cleaned.trim(); + + // Replace spaces with underscores + cleaned = cleaned.replace(/\s+/g, '_'); + + // Remove any invalid filename characters + cleaned = cleaned.replace(/[<>:"\/\\|?*]/g, ''); + + // Ensure we still have a file extension + if (!cleaned.includes('.')) { + return null; + } + + return cleaned; +} + function extractFileName(anchor) { + // Try multiple selector strategies to find the actual filename + + // Strategy 1: Look for aria-label which often contains the full filename + const ariaLabel = anchor.getAttribute('aria-label'); + if (ariaLabel && ariaLabel.trim() && ariaLabel.includes('.')) { + return ariaLabel.trim(); + } + + // Strategy 2: Look for title attribute + const title = anchor.getAttribute('title'); + if (title && title.trim() && title.includes('.')) { + return title.trim(); + } + + // Strategy 3: Search all descendant divs for filename with extension + const allDivs = anchor.querySelectorAll('div'); + for (const div of allDivs) { + const text = div.textContent.trim(); + // Check if text contains a file extension + if (text && /\.[a-zA-Z0-9]{2,5}$/.test(text)) { + return text; + } + } + + // Strategy 4: Try specific Google Classroom selectors const secondDiv = anchor.querySelector('div:nth-child(2)'); if (secondDiv) { const firstDivInsideSecondDiv = secondDiv.querySelector('div:nth-child(1)'); if (firstDivInsideSecondDiv) { - return firstDivInsideSecondDiv.textContent.trim(); + const text = firstDivInsideSecondDiv.textContent.trim(); + if (text && text.length > 0 && /\.[a-zA-Z0-9]{2,5}$/.test(text)) { + return text; + } + } + } + + // Strategy 5: Look in parent container for filename + const parent = anchor.closest('[data-item-id]') || anchor.closest('div[role="listitem"]'); + if (parent) { + const allText = parent.textContent; + const matches = allText.match(/([^\\/:"*?<>|]+\.[a-zA-Z0-9]{2,5})/g); + if (matches && matches.length > 0) { + // Return the first match that looks like a real filename + for (const match of matches) { + const cleaned = match.trim(); + if (cleaned.length > 3) { // Minimum filename.ext length + return cleaned; + } + } } } + return null; } @@ -23,12 +94,27 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { const driveFiles = Array.from(document.querySelectorAll('a')) .filter(a => a.href.includes("drive.google.com/file/d/")) .map(a => { - const fileName = extractFileName(a); + let fileName = extractFileName(a); const fileLink = getDirectDownloadLink(a.href); + + // Clean the filename + fileName = cleanFileName(fileName); + + // If we still don't have a filename with extension, use file ID as fallback + if (!fileName || !fileName.includes('.')) { + const fileIdMatch = a.href.match(/\/file\/d\/(.*?)\//); + if (fileIdMatch) { + fileName = `download_${fileIdMatch[1]}.file`; + } + } + + console.log('Extracted:', { fileName, fileLink }); // Debug logging + return { name: fileName, link: fileLink }; }) .filter(file => file.link !== null && file.name !== null); + console.log('Total files found:', driveFiles.length); // Debug logging sendResponse({ files: driveFiles }); } }); diff --git a/scripts/popup.js b/scripts/popup.js index d82c494..56350ac 100644 --- a/scripts/popup.js +++ b/scripts/popup.js @@ -4,6 +4,17 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { document.body.textContent = 'This extension only works on Google Classroom. (For now)'; return; } + + // Extract the authuser parameter from the Classroom URL (defaults to 0 if not found) + const authuserMatch = currentTab.url.match(/\/u\/(\d+)\//); + const authuser = authuserMatch ? authuserMatch[1] : '0'; + + // Helper function to safely append authuser to the download link + const appendAuthUser = (url) => { + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}authuser=${authuser}`; + }; + chrome.tabs.sendMessage(tabs[0].id, { action: "getDriveLinks" }, function (response) { if (response && response.files.length > 0) { const fileList = document.getElementById('fileList'); @@ -12,6 +23,7 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = file.link; + checkbox.dataset.filename = file.name; // Store filename with extension const text = document.createElement('span'); text.textContent = file.name; @@ -21,26 +33,58 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { fileList.appendChild(li); li.addEventListener('click', function (event) { - // Prevent the default action if the click is directly on the checkbox if (event.target !== checkbox) { checkbox.checked = !checkbox.checked; } + // select-all + const allChecked = Array.from(fileList.querySelectorAll('input[type="checkbox"]')).every(cb => cb.checked); + document.getElementById('selectAll').checked = allChecked; }); }); - // buttons + // select-all + document.getElementById('selectAll').addEventListener('change', function () { + Array.from(fileList.querySelectorAll('input[type="checkbox"]')).forEach(cb => { + cb.checked = this.checked; + }); + }); + + // select-all-buttons document.getElementById('downloadSelected').addEventListener('click', function () { Array.from(fileList.children).forEach(li => { const checkbox = li.querySelector('input[type="checkbox"]'); if (checkbox.checked) { - chrome.downloads.download({ url: checkbox.value, filename: li.textContent }); + const filename = checkbox.dataset.filename; + console.log('Downloading:', filename, 'from:', checkbox.value); + chrome.downloads.download({ + url: appendAuthUser(checkbox.value), + filename: filename, + saveAs: false + }, function(downloadId) { + if (chrome.runtime.lastError) { + console.error('Download error:', chrome.runtime.lastError); + } else { + console.log('Download started:', downloadId); + } + }); } }); }); document.getElementById('downloadAll').addEventListener('click', function () { response.files.forEach(file => { - chrome.downloads.download({ url: file.link, filename: file.name }); + console.log('Downloading:', file.name, 'from:', file.link); + chrome.downloads.download({ + url: appendAuthUser(file.link), + filename: file.name, + saveAs: false + }, function(downloadId) { + if (chrome.runtime.lastError) { + console.error('Download error:', chrome.runtime.lastError); + } else { + console.log('Download started:', downloadId); + } + }); }); }); @@ -48,4 +92,13 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { document.getElementById('fileList').textContent = 'No Google Drive files found. Try refreshing your page.'; } }); -}); \ No newline at end of file +}); +document.getElementById("darkModeButton").addEventListener('click',function() { + if(document.getElementById("darkModeButton").textContent == "Light Mode"){ + document.getElementById("body").classList.remove("dm"); + document.getElementById("darkModeButton").textContent = "Dark Mode";} + else{ + document.getElementById("body").classList.add("dm"); + document.getElementById("darkModeButton").textContent = "Light Mode"; + } +}); diff --git a/styles/styles.css b/styles/styles.css index cfc6ed9..adffdb2 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -1,9 +1,24 @@ +:root{ + --base-color:linear-gradient(180deg, #f9fafb 0%, #f3f4f6 100%); + --text-color:#1f2937; + --border-color:#e5e7eb; + --title-color:#065f46; + --filelist-color:#ffffff; +} +.dm{ + --base-color: #1f2937; + --text-color: #f9fafb; + --border-color: #374151; + --title-color: #10b981; + --filelist-color: #1e293b; +} + body { font-family: "Segoe UI", sans-serif; - background-color: #f9f9f9; + background-color: var(--base-color); margin: 0; padding: 16px; - color: #222; + color: var(--text-color); display: flex; flex-direction: column; min-width: 320px; @@ -17,7 +32,7 @@ body { align-items: center; flex-wrap: wrap; padding-bottom: 10px; - border-bottom: 1px solid #e0e0e0; + border-bottom: 1px solid var(--border-color); } .header-right { @@ -68,7 +83,7 @@ body { margin-bottom: 16px; max-height: 200px; overflow-y: auto; - border: 1px solid #ddd; + border: 1px solid var(--filelist-color); border-radius: 4px; } @@ -92,4 +107,13 @@ body { .primary-btn:hover { background-color: #014d3c; +} + +.select-all-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 6px; + font-size: 0.9rem; + font-weight: 500; } \ No newline at end of file diff --git a/views/popup.html b/views/popup.html index 5e696ae..c2652ec 100644 --- a/views/popup.html +++ b/views/popup.html @@ -7,7 +7,7 @@ -
+