// ==UserScript==
// @name Greasy Fork Bookmark (AFU IT)
// @namespace https://greasyfork.org/users/1453658
// @version 0.2
// @description Bookmark scripts, Add a bookmark page with active and history, Add a bookmark icon on right bottom
// @author AFU IT (Thanks to ぐらんぴ)
// @match https://greasyfork.org/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @connect greasyfork.org
// @license MIT
// ==/UserScript==
const $s = (el) => document.querySelector(el), $sa = (el) => document.querySelectorAll(el), $c = (el) => document.createElement(el);
let favScripts = GM_getValue("favScripts", []);
let removedScripts = GM_getValue("removedScripts", []);
// console.log(favScripts);
// Create inline notification function (appears next to star)
function createInlineNotification(element, message, isSuccess = true) {
// Remove any existing notification first
const existingNotification = element.parentNode.querySelector('.inline-notification');
if (existingNotification) {
existingNotification.remove();
}
const notification = document.createElement('span');
notification.className = 'inline-notification';
notification.style.cssText = `
margin-left: 10px;
color: ${isSuccess ? '#4CAF50' : '#f44336'};
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
opacity: 1;
transition: opacity 0.5s;
display: inline-block;
vertical-align: middle;
font-weight: normal;
`;
notification.textContent = message;
// Insert after the element
element.parentNode.insertBefore(notification, element.nextSibling);
// Remove after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 500);
}, 3000);
}
// Create notification function with Apple-style design
function createNotification(message, isSuccess = true) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background-color: rgba(50, 50, 50, 0.9);
color: white;
padding: 12px 20px;
border-radius: 10px;
z-index: 1000;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: opacity 0.5s, transform 0.3s;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transform: translateY(0);
border: 1px solid rgba(255, 255, 255, 0.1);
`;
const icon = document.createElement('span');
icon.style.cssText = `
margin-right: 10px;
font-size: 16px;
`;
icon.textContent = isSuccess ? '✓' : '✕';
const messageSpan = document.createElement('span');
messageSpan.textContent = message;
notification.appendChild(icon);
notification.appendChild(messageSpan);
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateY(20px)';
setTimeout(() => notification.remove(), 500);
}, 3000);
}
// Function to fetch script metadata
function fetchScriptMetadata(url) {
return new Promise((resolve, reject) => {
// Extract script ID from URL
const scriptId = url.match(/\/scripts\/(\d+)/)?.[1];
if (!scriptId) {
resolve({});
return;
}
// Use the JSON API endpoint to get script data
const jsonUrl = `https://greasyfork.org/en/scripts/${scriptId}.json`;
GM_xmlhttpRequest({
method: "GET",
url: jsonUrl,
headers: {
"Accept": "application/json"
},
onload: function(response) {
try {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
// Extract relevant metadata
const metadata = {
author: data.user?.name || "",
authorUrl: data.user?.url || "",
dailyInstalls: data.daily_installs || 0,
totalInstalls: data.total_installs || 0,
ratings: {
good: data.good_ratings || 0,
ok: data.ok_ratings || 0,
bad: data.bad_ratings || 0
},
created: data.created_at || "",
updated: data.code_updated_at || "",
ratingScore: data.good_ratings ?
Math.round((data.good_ratings / (data.good_ratings + data.ok_ratings + data.bad_ratings)) * 100) : 0
};
resolve(metadata);
} else {
// Fallback to HTML parsing if JSON API fails
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(htmlResponse) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlResponse.responseText, "text/html");
const metadataBlock = doc.querySelector(".script-meta-block");
if (!metadataBlock) {
resolve({});
return;
}
const authorElement = metadataBlock.querySelector(".script-list-author a");
const dailyInstallsElement = metadataBlock.querySelector(".script-list-daily-installs span");
const totalInstallsElement = metadataBlock.querySelector(".script-list-total-installs span");
const ratingsElement = metadataBlock.querySelector(".script-list-ratings");
const createdDateElement = metadataBlock.querySelector(".script-list-created-date relative-time");
const updatedDateElement = metadataBlock.querySelector(".script-list-updated-date relative-time");
const goodRating = ratingsElement ? ratingsElement.querySelector(".good-rating-count") : null;
const okRating = ratingsElement ? ratingsElement.querySelector(".ok-rating-count") : null;
const badRating = ratingsElement ? ratingsElement.querySelector(".bad-rating-count") : null;
const metadata = {
author: authorElement ? authorElement.textContent : "",
authorUrl: authorElement ? authorElement.href : "",
dailyInstalls: dailyInstallsElement ? dailyInstallsElement.textContent : "0",
totalInstalls: totalInstallsElement ? totalInstallsElement.textContent : "0",
ratings: {
good: goodRating ? goodRating.textContent : "0",
ok: okRating ? okRating.textContent : "0",
bad: badRating ? badRating.textContent : "0"
},
created: createdDateElement ? createdDateElement.getAttribute("datetime") : "",
updated: updatedDateElement ? updatedDateElement.getAttribute("datetime") : "",
ratingScore: ratingsElement ? ratingsElement.getAttribute("data-rating-score") : "0"
};
resolve(metadata);
},
onerror: function(error) {
resolve({});
}
});
}
} catch (e) {
resolve({});
}
},
onerror: function(error) {
// Fallback to empty metadata
resolve({});
}
});
});
}
// Function to extract author information from HTML
function extractAuthorInfo(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, "text/html");
const authorElement = doc.querySelector(".script-list-author a, .script-show-author a");
if (authorElement) {
resolve({
author: authorElement.textContent.trim(),
authorUrl: authorElement.href
});
} else {
resolve({});
}
} catch (e) {
resolve({});
}
},
onerror: function(error) {
resolve({});
}
});
});
}
function scriptPage() {
if (location.href.match('/scripts/') && $s(".script-meta-block")) {
let title = $s("#script-info > header > h2").innerText;
let parent = $s('#script-links'),
li = $c('li');
// Use star icon
li.innerHTML = `<a style="cursor: pointer; display: inline-flex; align-items: center; vertical-align: middle;">☆</a>`;
// Direct bookmark without modal
li.addEventListener('click', e => {
const defaultTag = "Bookmarked";
// Check if script is already bookmarked
if (favScripts.findIndex(({ scriptTitle }) => scriptTitle === title) === -1) {
// Get metadata for the script
Promise.all([
fetchScriptMetadata(location.href),
extractAuthorInfo(location.href)
]).then(([metadata, authorInfo]) => {
// Combine metadata with author info
const combinedMetadata = {
...metadata,
author: authorInfo.author || metadata.author || document.querySelector(".script-show-author a")?.textContent.trim() || "",
authorUrl: authorInfo.authorUrl || metadata.authorUrl || document.querySelector(".script-show-author a")?.href || ""
};
// Add to bookmarks with default tag and metadata
favScripts.push({
url: location.href,
tag: defaultTag,
scriptTitle: title,
description: $s(".script-description").textContent,
lastModified: new Date().toISOString(),
...combinedMetadata
});
// Save to GM storage
GM_setValue("favScripts", favScripts);
// Change star icon
li.innerHTML = '<a style="cursor: pointer; color: #E09015; display: inline-flex; align-items: center; vertical-align: middle;">★</a>';
// Show inline notification
createInlineNotification(li.querySelector('a'), `Added to bookmarks`);
}).catch(err => {
console.error("Error fetching metadata:", err);
// Try to get author directly from the page
const authorElement = document.querySelector(".script-show-author a");
const author = authorElement ? authorElement.textContent.trim() : "";
const authorUrl = authorElement ? authorElement.href : "";
// Add with basic info if metadata fetch fails
favScripts.push({
url: location.href,
tag: defaultTag,
scriptTitle: title,
description: $s(".script-description").textContent,
lastModified: new Date().toISOString(),
author: author,
authorUrl: authorUrl
});
GM_setValue("favScripts", favScripts);
li.innerHTML = '<a style="cursor: pointer; color: #E09015; display: inline-flex; align-items: center; vertical-align: middle;">★</a>';
createInlineNotification(li.querySelector('a'), `Added to bookmarks`);
});
} else {
// Move to removed scripts history
let scriptToRemove = favScripts.find(item => item.scriptTitle === title);
if (scriptToRemove) {
// Add to history but keep only one entry per script
const existingIndex = removedScripts.findIndex(item => item.scriptTitle === title);
if (existingIndex !== -1) {
removedScripts.splice(existingIndex, 1);
}
removedScripts.push({
...scriptToRemove,
removedAt: new Date().toISOString()
});
GM_setValue("removedScripts", removedScripts);
}
// Remove from bookmarks
let indexToRemove = favScripts.findIndex(item => item.scriptTitle === title);
if (indexToRemove > -1) favScripts.splice(indexToRemove, 1);
// Change star icon
li.innerHTML = '<a style="cursor: pointer; display: inline-flex; align-items: center; vertical-align: middle;">☆</a>';
// Show inline notification
createInlineNotification(li.querySelector('a'), `Removed from bookmarks`, false);
// Save to GM storage
GM_setValue("favScripts", favScripts);
}
});
parent.appendChild(li);
// If this script already bookmarked, show filled star
if (favScripts.findIndex(({ scriptTitle }) => scriptTitle === title) !== -1) {
li.innerHTML = '<a style="cursor: pointer; color: #E09015; display: inline-flex; align-items: center; vertical-align: middle;">★</a>';
}
}
}
function listPage() {
if (location.href.match('/scripts') && !location.href.match('/scripts/')) {
// Add star buttons to script listing
$sa("#browse-script-list > li").forEach(item => {
const titleElement = item.querySelector(".script-link");
if (titleElement) {
const title = titleElement.textContent;
const url = titleElement.href;
// Create star container
const starContainer = document.createElement('span');
starContainer.style.cssText = "margin-left: 10px; cursor: pointer; display: inline-flex; align-items: center; vertical-align: middle;";
// Check if script is already bookmarked
const isBookmarked = favScripts.findIndex(({ scriptTitle }) => scriptTitle === title) !== -1;
starContainer.innerHTML = isBookmarked ? '★' : '☆';
starContainer.style.color = isBookmarked ? '#E09015' : 'white';
// Add click event
starContainer.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
const defaultTag = "Bookmarked";
if (starContainer.innerHTML === '☆') {
// Fetch metadata and add to bookmarks
Promise.all([
fetchScriptMetadata(url),
extractAuthorInfo(url)
]).then(([metadata, authorInfo]) => {
// Combine metadata with author info
const combinedMetadata = {
...metadata,
author: authorInfo.author || metadata.author || "",
authorUrl: authorInfo.authorUrl || metadata.authorUrl || ""
};
favScripts.push({
url: url,
tag: defaultTag,
scriptTitle: title,
description: item.querySelector(".script-description") ? item.querySelector(".script-description").textContent : "",
lastModified: new Date().toISOString(),
...combinedMetadata
});
GM_setValue("favScripts", favScripts);
starContainer.innerHTML = '★';
starContainer.style.color = '#E09015';
createInlineNotification(starContainer, `Added to bookmarks`);
}).catch(err => {
console.error("Error fetching metadata:", err);
// Try to get author from the list item
const authorElement = item.querySelector(".script-list-author a");
const author = authorElement ? authorElement.textContent.trim() : "";
const authorUrl = authorElement ? authorElement.href : "";
// Add with basic info if metadata fetch fails
favScripts.push({
url: url,
tag: defaultTag,
scriptTitle: title,
description: item.querySelector(".script-description") ? item.querySelector(".script-description").textContent : "",
lastModified: new Date().toISOString(),
author: author,
authorUrl: authorUrl
});
GM_setValue("favScripts", favScripts);
starContainer.innerHTML = '★';
starContainer.style.color = '#E09015';
createInlineNotification(starContainer, `Added to bookmarks`);
});
} else {
// Remove from bookmarks
let indexToRemove = favScripts.findIndex(item => item.scriptTitle === title);
if (indexToRemove > -1) {
// Add to history
const scriptToRemove = favScripts[indexToRemove];
const existingIndex = removedScripts.findIndex(item => item.scriptTitle === title);
if (existingIndex !== -1) {
removedScripts.splice(existingIndex, 1);
}
removedScripts.push({
...scriptToRemove,
removedAt: new Date().toISOString()
});
GM_setValue("removedScripts", removedScripts);
// Remove from bookmarks
favScripts.splice(indexToRemove, 1);
}
starContainer.innerHTML = '☆';
starContainer.style.color = 'white';
createInlineNotification(starContainer, `Removed from bookmarks`, false);
}
GM_setValue("favScripts", favScripts);
});
// Insert after title
titleElement.parentNode.insertBefore(starContainer, titleElement.nextSibling);
}
});
}
}
function favPage() {
if (location.href == "https://greasyfork.org/bookmarks") {
document.title = 'My Bookmarks';
$s("body > div > section").remove();
$s("body > div").innerHTML = `
<div class="sidebarred">
<div class="sidebarred-main-content">
<div class="open-sidebar sidebar-collapsed">☰</div>
<div class="search-box">
<input type="text" id="bookmark-search" placeholder="Search bookmarks..."
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #555; margin-bottom: 15px; background-color: #333; color: white;">
</div>
<div style="display: flex; margin-bottom: 15px;">
<button id="show-active" class="tab-button active" style="flex: 1; padding: 8px; border: none; background: #444; color: white; cursor: pointer; border-radius: 4px 0 0 4px;">Active</button>
<button id="show-history" class="tab-button" style="flex: 1; padding: 8px; border: none; background: #333; color: white; cursor: pointer; border-radius: 0 4px 4px 0;">History</button>
</div>
<ol id="browse-script-list" class="script-list">
</ol>
<div id="history-container" style="display: none;">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px;">
<button id="clear-history" style="padding: 6px 12px; background-color: #555; color: white; border: none; border-radius: 4px; cursor: pointer;">Clear History</button>
</div>
<ol id="history-script-list" class="script-list">
</ol>
</div>
</div>
</div>`;
function addScripts(filter, tagFilter, listElement = "#browse-script-list") {
// reset list items
if($sa(`${listElement} > li`)) $sa(`${listElement} > li`).forEach(elm => elm.remove());
// Determine which list to populate
let scriptsList = listElement === "#browse-script-list" ? favScripts : removedScripts;
// Sort by lastModified date (newest first) for active bookmarks
if (listElement === "#browse-script-list") {
scriptsList = [...scriptsList].sort((a, b) => {
const dateA = a.lastModified ? new Date(a.lastModified) : new Date(0);
const dateB = b.lastModified ? new Date(b.lastModified) : new Date(0);
return dateB - dateA;
});
}
// For history tab, sort by removedAt date (newest first) and deduplicate
if (listElement === "#history-script-list") {
// Sort by date (newest first)
scriptsList = [...scriptsList].sort((a, b) =>
new Date(b.removedAt) - new Date(a.removedAt)
);
// Deduplicate by scriptTitle (keep only the most recent)
const uniqueTitles = new Set();
scriptsList = scriptsList.filter(script => {
if (uniqueTitles.has(script.scriptTitle)) {
return false;
}
uniqueTitles.add(script.scriptTitle);
return true;
});
}
// Filter and add scripts to the list
scriptsList.forEach(fav => {
if ((!filter || filter === "All" || fav.url.startsWith(filter))) {
let elm = $s(listElement),
li = $c("li");
// Format numbers with commas
const formatNumber = (num) => {
return typeof num === 'string' ? num : num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
// Create the metadata HTML
const metadataHtml = `
<div class="script-meta-block">
<dl class="inline-script-stats">
<dt class="script-list-author"><span>Author</span></dt>
<dd class="script-list-author"><span>${fav.author ? `<a href="${fav.authorUrl || '#'}">${fav.author}</a>` : 'Unknown'}</span></dd>
<dt class="script-list-daily-installs"><span>Daily installs</span></dt>
<dd class="script-list-daily-installs"><span>${formatNumber(fav.dailyInstalls || 0)}</span></dd>
<dt class="script-list-total-installs"><span>Total installs</span></dt>
<dd class="script-list-total-installs"><span>${formatNumber(fav.totalInstalls || 0)}</span></dd>
<dt class="script-list-ratings"><span>Ratings</span></dt>
<dd class="script-list-ratings" data-rating-score="${fav.ratingScore || 0}"><span>
<span class="good-rating-count" title="Number of people who rated it Good or added it to favorites.">${fav.ratings ? fav.ratings.good : 0}</span>
<span class="ok-rating-count" title="Number of people who rated it OK.">${fav.ratings ? fav.ratings.ok : 0}</span>
<span class="bad-rating-count" title="Number of people who rated it Bad.">${fav.ratings ? fav.ratings.bad : 0}</span>
</span></dd>
<dt class="script-list-created-date"><span>Created</span></dt>
<dd class="script-list-created-date"><span>${fav.created ? `<relative-time datetime="${fav.created}" prefix="" title="${new Date(fav.created).toLocaleString()}">${new Date(fav.created).toISOString().split('T')[0]}</relative-time>` : 'Unknown'}</span></dd>
<dt class="script-list-updated-date"><span>Updated</span></dt>
<dd class="script-list-updated-date"><span>${fav.updated ? `<relative-time datetime="${fav.updated}" prefix="" title="${new Date(fav.updated).toLocaleString()}">${new Date(fav.updated).toISOString().split('T')[0]}</relative-time>` : 'Unknown'}</span></dd>
</dl>
</div>
`;
// Use star icon for bookmarks page
li.innerHTML = `
<span style="display: flex; align-items: center;">
<a class="script-link" href="${fav.url}">${fav.scriptTitle}</a>
<button class="star" style="margin-left: auto; font-size: 24px; background: none; border: none; cursor: pointer; color: #E09015; display: inline-flex; align-items: center; vertical-align: middle;">★</button>
</span>
<span class="script-description description">${fav.description}</span>
${metadataHtml}
<span class="data-tag" data-tag="${fav.tag}" style="display: none;">${fav.tag}</span>
${listElement === "#history-script-list" ? `<span class="removed-at" style="font-size: 12px; color: #999;">Removed: ${new Date(fav.removedAt).toLocaleString()}</span>` : ''}`;
elm.appendChild(li);
// star click function
if (listElement === "#browse-script-list") {
li.querySelector('.star').onclick = e => {
let starHref = e.target.closest('span').querySelector('a').href;
let starTag = e.target.closest('li').querySelector('.data-tag').getAttribute('data-tag');
let starTitle = e.target.closest('span').querySelector('a').textContent;
let starDescription = e.target.closest('li').querySelector('.description').textContent;
// Get the full script data
const scriptData = favScripts.find(script => script.scriptTitle === starTitle);
// Move to removed scripts history
const existingIndex = removedScripts.findIndex(item => item.scriptTitle === starTitle);
if (existingIndex !== -1) {
removedScripts.splice(existingIndex, 1);
}
removedScripts.push({
...scriptData,
removedAt: new Date().toISOString()
});
GM_setValue("removedScripts", removedScripts);
// Remove from active bookmarks
let indexToRemove = favScripts.findIndex(i => i.url === starHref && i.tag === starTag);
if (indexToRemove > -1) favScripts.splice(indexToRemove, 1);
GM_setValue("favScripts", favScripts);
// Remove from display
e.target.closest('li').remove();
createInlineNotification(e.target, `Removed from bookmarks`, false);
};
} else if (listElement === "#history-script-list") {
// For history items, change star to restore icon
li.querySelector('.star').innerHTML = '↺';
li.querySelector('.star').style.color = '#E09015';
li.querySelector('.star').onclick = e => {
let starHref = e.target.closest('span').querySelector('a').href;
let starTag = e.target.closest('li').querySelector('.data-tag').getAttribute('data-tag');
let starTitle = e.target.closest('span').querySelector('a').textContent;
// Get the full script data
const scriptData = removedScripts.find(script => script.scriptTitle === starTitle);
// Add back to active bookmarks with updated lastModified
favScripts.push({
...scriptData,
lastModified: new Date().toISOString()
});
GM_setValue("favScripts", favScripts);
// Remove from history
let indexToRemove = removedScripts.findIndex(i => i.url === starHref && i.tag === starTag);
if (indexToRemove > -1) removedScripts.splice(indexToRemove, 1);
GM_setValue("removedScripts", removedScripts);
// Remove from display
e.target.closest('li').remove();
// Refresh active tab to show the restored bookmark immediately
addScripts(null, null, "#browse-script-list");
// Switch to active tab
$s('#show-active').click();
createInlineNotification(e.target, `Restored to bookmarks`);
};
}
}
});
}
// Initial load of active bookmarks
addScripts();
// Clear history button functionality
$s('#clear-history').addEventListener('click', () => {
// Show confirmation dialog
if (confirm("Are you sure you want to clear all history?")) {
removedScripts = [];
GM_setValue("removedScripts", removedScripts);
$s('#history-script-list').innerHTML = '';
createNotification("History cleared successfully");
}
});
// Tab switching
$s('#show-active').addEventListener('click', () => {
$s('#show-active').classList.add('active');
$s('#show-active').style.background = '#444';
$s('#show-active').style.color = 'white';
$s('#show-history').classList.remove('active');
$s('#show-history').style.background = '#333';
$s('#show-history').style.color = 'white';
$s('#browse-script-list').style.display = '';
$s('#history-container').style.display = 'none';
});
$s('#show-history').addEventListener('click', () => {
$s('#show-history').classList.add('active');
$s('#show-history').style.background = '#444';
$s('#show-history').style.color = 'white';
$s('#show-active').classList.remove('active');
$s('#show-active').style.background = '#333';
$s('#show-active').style.color = 'white';
$s('#browse-script-list').style.display = 'none';
$s('#history-container').style.display = '';
addScripts(null, null, "#history-script-list");
});
// Add search functionality
$s('#bookmark-search').addEventListener('input', e => {
const searchTerm = e.target.value.toLowerCase();
const activeList = $s('#show-active').classList.contains('active');
const listSelector = activeList ? '#browse-script-list > li' : '#history-script-list > li';
$sa(listSelector).forEach(item => {
const title = item.querySelector('.script-link').textContent.toLowerCase();
const desc = item.querySelector('.script-description').textContent.toLowerCase();
if (title.includes(searchTerm) || desc.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
let sidebarred = $s(".sidebarred"),
div = $c("div");
div.innerHTML = `
<div class="sidebar collapsed">
<div class="close-sidebar">
<div class="sidebar-title">Filter Options</div>
<div>☰</div>
</div>
<div id="script-list-option-groups" class="list-option-groups">
<form class="sidebar-search">
<input type="hidden" name="sort" value="created">
<div id="script-list-sort" class="list-option-group">Sites:
<ul>
<li class="list-option"><a>All</a></li>
<li class="list-option"><a>Greasyfork</a></li>
<li class="list-option"><a>Sleazyfork</a></li>
</ul>
</div>
</form>
</div>
</div>`;
div.style.cursor = "pointer";
sidebarred.appendChild(div);
// Sorting by: function
$sa('.list-option').forEach(i => {
i.onclick = e => {
const activeList = $s('#show-active').classList.contains('active');
const listSelector = activeList ? "#browse-script-list" : "#history-script-list";
switch (i.textContent) {
case "All":
addScripts(null, null, listSelector);
break;
case "Greasyfork":
addScripts('https://greasy', null, listSelector);
break;
case "Sleazyfork":
addScripts('https://sleazy', null, listSelector);
break;
}
};
});
}
}
function allPage() {
let li = document.createElement('li');
li.innerHTML = `<a href="https://greasyfork.org/bookmarks" style="color: white;">My Bookmarks</a>`;
$s("#site-nav > nav").appendChild(li.cloneNode(true));
if ($s("#mobile-nav > nav")) $s("#mobile-nav > nav").appendChild(li.cloneNode(true)); // mobile
// Add floating bookmark icon in bottom right
const bookmarkButton = document.createElement('div');
bookmarkButton.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background-color: #333;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 999;
transition: transform 0.2s;
`;
bookmarkButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24" fill="white" stroke="white" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
`;
bookmarkButton.addEventListener('mouseover', () => {
bookmarkButton.style.transform = 'scale(1.1)';
});
bookmarkButton.addEventListener('mouseout', () => {
bookmarkButton.style.transform = 'scale(1)';
});
bookmarkButton.addEventListener('click', () => {
window.location.href = 'https://greasyfork.org/bookmarks';
});
document.body.appendChild(bookmarkButton);
}
// Run functions
scriptPage();
listPage();
favPage();
allPage();