diff --git a/LICENSE b/LICENSE index 261eeb9..9c47d99 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2023 Oreoxmt Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index fd344c6..e69fb3e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ # octopus-github -A GitHub extension + +A userscript for GitHub with the following features: + +- Quick access to the **Files changed** page of a PR +- Bulk comment on multiple PRs + +## Usage + +1. Install a userscript manager of your choice, such as [Tampermonkey](https://tampermonkey.net/). + +2. Add the userscript by clicking [`gh-util.user.js`](https://raw.githubusercontent.com/Oreoxmt/octopus-github/main/gh-util.user.js). + +3. (Optional) Configure your own GitHub [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) (`repo` scope) when you use the bulk comment feature for the first time. + +## FAQs + +### Why is the bulk comment feature not working? + +1. Check if you have configured a Github personal access token. + +2. Check if the token has the `repo` scope. + +3. Check if the token has expired. + +### How to manage the token used by this script? + +1. Go to the **Developer Tools** of your browser and then click the **Application** tab. + +2. In the **Storage** section on the left panel, click **Local Storage > https://github.com**. + +3. Search for the key `octopus-github-util:token` and modify/delete it. diff --git a/gh-util.user.js b/gh-util.user.js new file mode 100644 index 0000000..4fe2a96 --- /dev/null +++ b/gh-util.user.js @@ -0,0 +1,142 @@ +// ==UserScript== +// @name Octopus GitHub +// @version 0.1 +// @description A userscript for GitHub +// @author Oreo +// @match https://github.com/*/pulls* +// @grant none +// ==/UserScript== + +(function () { + + 'use strict'; + + const STORAGEKEY = 'octopus-github-util:token' + + function GetRepositoryInformation() { + // Get the pathname of the current page + var pathname = location.pathname; + + // Split the pathname into an array of parts + var parts = pathname.split('/'); + + // Return an object containing the user name and repository name + return { + owner: parts[1], + name: parts[2], + } + } + + function EnsureToken() { + var token = localStorage.getItem(STORAGEKEY) + if (!token) { + // Prompt user to set token + // TODO: Use HTML element instead of prompt + token = prompt('Enter your GitHub token:'); + if (!token) { + throw 'No token set' + } + localStorage.setItem(STORAGEKEY, token); + } + return token; + } + + // This function can be used to leave a comment on a specific PR + function LeaveCommentOnPR(commentLink, comment) { + // Send the POST request to the GitHub API + // TODO: Use Octokit to create requests + fetch(commentLink, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${EnsureToken()}`, + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + 'body': comment + }) + }).then((response) => { + console.log('response to ', commentLink, response) + }).catch((error) => { + console.log('error on ', commentLink, error) + }) + } + + // TODO: Use toggle instead of button, and add more features to the toggle, e.g., editing tokens. + function CreateCommentButton() { + // First, find the "table-list-header-toggle" div + var toggleDiv = document.querySelector('.table-list-header-toggle.float-right'); + + // Next, create a button element and add it to the page + var button = document.createElement('button'); + button.innerHTML = 'Comment'; + button.setAttribute('class', 'btn btn-sm js-details-target d-inline-block float-left float-none m-0 mr-md-0 js-title-edit-button') + toggleDiv.appendChild(button); + + // Next, add an event listener to the button to listen for clicks + button.addEventListener('click', function () { + EnsureToken(); + + // Get a list of all the checkboxes on the page (these are used to select PRs) + var checkboxes = document.querySelectorAll('input[type=checkbox][data-check-all-item]'); + + // Iterate through the checkboxes and get the ones that are checked + var selectedPRs = []; + + checkboxes.forEach(function (checkbox) { + if (checkbox.checked) { + selectedPRs.push(checkbox.value); + } + }) + + // Prompt the user for a comment to leave on the selected PRs + var comment = prompt('Enter a comment to leave on the selected PRs:'); + if (!comment) { + return; + } + var repo = GetRepositoryInformation(); + + // Leave the comment on each selected PR + selectedPRs.forEach(function (pr) { + var commentLink = `https://api.github.com/repos/${repo.owner}/${repo.name}/issues/${pr}/comments`; + // Leave a comment on the PR + LeaveCommentOnPR(commentLink, comment); + }); + }); + } + + function CreateFileLink() { + + // Get all div elements with an id that starts with "issue_" + var issueElements = document.querySelectorAll('div[id^="issue_"]'); + issueElements.forEach((element) => { + var issueId = element.getAttribute("id") + var originalLinkElement = document.getElementById(issueId + "_link") + var originalLink = originalLinkElement.getAttribute("href") + var newLink = originalLink + "/files" + // Get all span elements within the current element + var spanElements = element.querySelectorAll('span[class="opened-by"]'); + if (spanElements.length == 1) { + var openedBy = spanElements[0]; + var linkSpanElement = document.createElement('span'); + linkSpanElement.setAttribute('class', 'd-inline-block mr-1') + var dotSpanElement = document.createElement('span'); + dotSpanElement.innerHTML = ' • '; + dotSpanElement.setAttribute('class', 'd-inline-block mr-1') + var linkElement = document.createElement('a') + linkElement.setAttribute('href', newLink) + linkElement.setAttribute('class', 'Link--muted') + linkElement.innerHTML = "Files" + linkSpanElement.appendChild(linkElement) + openedBy.insertAdjacentElement('beforebegin', linkSpanElement) + openedBy.insertAdjacentElement('beforebegin', dotSpanElement); + } + }) + + } + + // TODO: Do this everytime on switching to new page + CreateCommentButton(); + CreateFileLink(); + +})();