Bookmark GitHub repositories into different lists, remove bookmarks, and manage them without starring the repos
// ==UserScript==
// @name GitHub Bookmark Repositories with Lists and Removal
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Bookmark GitHub repositories into different lists, remove bookmarks, and manage them without starring the repos
// @author low mist
// @match https://github.com/*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const defaultList = 'General';
// Function to get repository info
function getRepoInfo() {
const repo = window.location.pathname.split('/').slice(1, 3).join('/');
const repoUrl = window.location.href;
return { repo, repoUrl };
}
// Function to save a bookmark
function saveBookmark(repo, repoUrl, list) {
const bookmarks = JSON.parse(localStorage.getItem('ghBookmarks') || '{}');
if (!bookmarks[list]) bookmarks[list] = [];
if (!bookmarks[list].some(b => b.repo === repo)) {
bookmarks[list].push({ repo, repoUrl });
localStorage.setItem('ghBookmarks', JSON.stringify(bookmarks));
updateBookmarkButton(true);
}
}
// Function to remove a bookmark
function removeBookmark(repo, list) {
let bookmarks = JSON.parse(localStorage.getItem('ghBookmarks') || '{}');
if (bookmarks[list]) {
bookmarks[list] = bookmarks[list].filter(b => b.repo !== repo);
if (bookmarks[list].length === 0) delete bookmarks[list];
localStorage.setItem('ghBookmarks', JSON.stringify(bookmarks));
updateBookmarkButton(false);
}
}
// Function to check if a repository is bookmarked in any list
function isBookmarked(repo) {
const bookmarks = JSON.parse(localStorage.getItem('ghBookmarks') || '{}');
return Object.keys(bookmarks).some(list => bookmarks[list].some(b => b.repo === repo));
}
// Function to check if a repository is bookmarked in a specific list
function isBookmarkedInList(repo, list) {
const bookmarks = JSON.parse(localStorage.getItem('ghBookmarks') || '{}');
return bookmarks[list] && bookmarks[list].some(b => b.repo === repo);
}
// Function to update bookmark button state
function updateBookmarkButton(bookmarked) {
const button = document.getElementById('bookmarkButton');
button.textContent = bookmarked ? 'Bookmarked' : 'Bookmark';
}
// Function to add a new list
function addNewList(listName) {
const lists = JSON.parse(localStorage.getItem('ghBookmarkLists') || '["General"]');
if (!lists.includes(listName)) {
lists.push(listName);
localStorage.setItem('ghBookmarkLists', JSON.stringify(lists));
}
}
// Inject the bookmark button and dropdown into the page
function addBookmarkButton() {
const { repo, repoUrl } = getRepoInfo();
const actionBar = document.querySelector('.pagehead-actions');
if (actionBar) {
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'inline-block';
const button = document.createElement('button');
button.id = 'bookmarkButton';
button.className = 'btn btn-sm';
button.style.marginLeft = '8px';
button.textContent = isBookmarked(repo) ? 'Bookmarked' : 'Bookmark';
const dropdown = document.createElement('div');
dropdown.style.display = 'none';
dropdown.style.position = 'absolute';
dropdown.style.backgroundColor = '#2d333b'; // Dark background color to match GitHub dark theme
dropdown.style.border = '1px solid #444c56'; // Dark border color
dropdown.style.boxShadow = '0 3px 12px rgba(0, 0, 0, .15)'; // Darker shadow
dropdown.style.zIndex = '1000';
dropdown.style.right = '0';
dropdown.style.top = '28px';
dropdown.style.width = '200px';
dropdown.style.borderRadius = '6px';
dropdown.style.color = '#c9d1d9'; // Light text color for dark mode
const lists = JSON.parse(localStorage.getItem('ghBookmarkLists') || '["General"]');
lists.forEach(list => {
const listItem = document.createElement('div');
listItem.textContent = isBookmarkedInList(repo, list) ? `${list} (Remove)` : list;
listItem.style.padding = '8px 16px';
listItem.style.cursor = 'pointer';
listItem.style.borderBottom = '1px solid #444c56'; // Dark border between items
listItem.addEventListener('click', () => {
if (isBookmarkedInList(repo, list)) {
removeBookmark(repo, list);
listItem.textContent = list;
} else {
saveBookmark(repo, repoUrl, list);
listItem.textContent = `${list} (Remove)`;
}
updateBookmarkButton(isBookmarked(repo));
});
listItem.addEventListener('mouseover', () => {
listItem.style.backgroundColor = '#444c56'; // Highlight on hover
});
listItem.addEventListener('mouseout', () => {
listItem.style.backgroundColor = '#2d333b'; // Revert to dark background on mouse out
});
dropdown.appendChild(listItem);
});
const addListButton = document.createElement('div');
addListButton.textContent = 'Add new list';
addListButton.style.padding = '8px 16px';
addListButton.style.cursor = 'pointer';
addListButton.style.borderTop = '1px solid #444c56'; // Dark separator for the "Add new list" option
addListButton.style.fontWeight = 'bold';
addListButton.addEventListener('click', () => {
const newList = prompt('Enter new list name:');
if (newList) {
addNewList(newList);
const newListItem = document.createElement('div');
newListItem.textContent = newList;
newListItem.style.padding = '8px 16px';
newListItem.style.cursor = 'pointer';
newListItem.style.borderBottom = '1px solid #444c56';
newListItem.addEventListener('click', () => {
if (isBookmarkedInList(repo, newList)) {
removeBookmark(repo, newList);
newListItem.textContent = newList;
} else {
saveBookmark(repo, repoUrl, newList);
newListItem.textContent = `${newList} (Remove)`;
}
updateBookmarkButton(isBookmarked(repo));
});
newListItem.addEventListener('mouseover', () => {
newListItem.style.backgroundColor = '#444c56'; // Highlight on hover
});
newListItem.addEventListener('mouseout', () => {
newListItem.style.backgroundColor = '#2d333b'; // Revert to dark background on mouse out
});
dropdown.insertBefore(newListItem, addListButton);
}
});
dropdown.appendChild(addListButton);
button.addEventListener('click', function() {
dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
});
// Close dropdown if clicked outside
document.addEventListener('click', function(event) {
if (!container.contains(event.target)) {
dropdown.style.display = 'none';
}
});
container.appendChild(button);
container.appendChild(dropdown);
actionBar.appendChild(container);
}
}
// Initialize the script
function init() {
addBookmarkButton();
}
// Run the script when the DOM is fully loaded
window.addEventListener('load', init);
})();