I would like to suggest adding the new button in Edit Watchlist that allows all entries to be displayed in alphabetical order. I would appreciate it if this could be implemented.
Many people use Bitcointalk's Watchlist feature regularly. They add important topics to the watchlist, and remove old topics when they are no longer needed. But when the watchlist grows large, a common problem arises the entries are randomly placed. As a result, you have to scroll through the entire list to find a specific topic, which is time-consuming and a bit annoying.
I'm working on a small userscript to solve this problem:
Bitcointalk Watchlist A-Z SorterThe main task of this userscript will be to add a new button to the Edit Watchlist page, like this:
Clicking the button will sort all topic entries in the watchlist alphabetically. You will have the option to restore the original order if you wish.
Why is this userscript needed?1. It will be easier for those with large watchlists to find specific topics.
2. The hassle of manually scrolling to find topics will be reduced.
3. When removing old entries, users will be able to quickly identify topics.
4. Watchlist management will be cleaner and more organized.
5. No server-side changes to the forum will be required, as all work will be done inside the browser.
Basic technical flow:
1. The script will first check if the current page is a Bitcointalk watchlist page.
2. The page URL will be matched
3. Then the watchlist entries from the page will be detected.
4. Each entry usually has a checkbox and a topic link.
5. The script will create an entry object by taking the checkbox and topic link together.
6. The sorting text will be created from the topic title.
7. A-Z sorting will be done using JavaScript localeCompare().
8. The entries will be reordered in the DOM.
9. The original order will be kept in memory, so that the user can restore it if desired.
Sorting logicTopic titles will be normalized for sorting:
function normalize(text) {
return text.replace(/\s+/g, ' ').trim().toLowerCase();
}
Then it will be compared:
titleA.localeCompare(titleB, undefined, {
numeric: true,
sensitivity: 'base'
});
This will reduce the uppercase/lowercase issue and sorting will be relatively better even if there are numbers.
On the Edit Watchlist page, there will be a new button next to the Remove checked button:

After clicking:

The user can return to the original order from the sorted view if they wish.
How to Install:
Step 1: First, install a userscript manager extension in your browser (Tampermonkey).
Step 2: Go to the extension dashboard and click on Create a new script or the plus (+) icon.
Step 3: Copy and paste the entire code below and save .
Step 4: Now refresh the BitcoinTalk forum. You will see a beautiful market ticker on the right side!
GreasyFork DownloadUserscript version 1.1.0 -
06-09-2026 --> 05:00:03 PMA button has been added. You can now add a custom display title name there, leaving the URL unchanged. If you give a custom name, it will be saved in localStorage. As a result, even if the watchlist page loads later, the saved custom name will be displayed instead of the original title.
// ==UserScript==
// @name Bitcointalk Watchlist Alphabetical Sorter
// @namespace bitcointalk-watchlist-tools
// @version 1.1.0
// @description Adds Sort A-Z and selected-entry rename buttons on Bitcointalk's edit watchlist page.
// @author GhostOfBitcoin
// @match https://bitcointalk.org/watchlist.php*
// @match http://bitcointalk.org/watchlist.php*
// @include https://bitcointalk.org/watchlist.php*
// @include http://bitcointalk.org/watchlist.php*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
'use strict';
const BUTTON_ID = 'bt-watchlist-sort-az';
const EDIT_BUTTON_ID = 'bt-watchlist-edit-title';
const STATUS_ID = 'bt-watchlist-sort-status';
const STORAGE_KEY = 'bt-watchlist-custom-titles';
const LINK_SELECTOR = 'a[href*="topic="], a[href*="board="]';
let originalEntries = null;
let sorted = false;
function cleanText(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function sortText(value) {
return cleanText(value).toLocaleLowerCase();
}
function loadCustomTitles() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') || {};
} catch (error) {
return {};
}
}
function saveCustomTitles(titles) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(titles));
}
function normalizeHref(href) {
try {
const url = new URL(href, location.href);
url.hash = '';
return url.href;
} catch (error) {
return String(href || '');
}
}
function getEntryLink(entry) {
for (const node of entry.nodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (node.matches && node.matches(LINK_SELECTOR)) return node;
const link = node.querySelector ? node.querySelector(LINK_SELECTOR) : null;
if (link) return link;
}
return null;
}
function applyCustomTitles() {
const titles = loadCustomTitles();
document.querySelectorAll(LINK_SELECTOR).forEach((link) => {
const customTitle = titles[normalizeHref(link.href)];
if (customTitle) link.textContent = customTitle;
});
}
function isWatchlistPage() {
return /\/watchlist\.php/i.test(location.pathname);
}
function isControlCheckbox(box) {
const text = `${box.name || ''} ${box.id || ''} ${box.value || ''}`.toLowerCase();
return text.includes('all') || text.includes('checkall') || text.includes('check_all');
}
function getTitleFromNode(node) {
const link = node.querySelector ? node.querySelector(LINK_SELECTOR) : null;
return link ? sortText(link.textContent) : '';
}
function nextNodeBelongsToEntry(node) {
if (!node) return false;
if (node.nodeType === Node.TEXT_NODE) return true;
if (node.nodeType !== Node.ELEMENT_NODE) return false;
if (node.matches('br')) return true;
return false;
}
function collectTrailingBreaks(startNode, nodes) {
let node = startNode.nextSibling;
let guard = 0;
while (nextNodeBelongsToEntry(node) && guard < 10) {
nodes.push(node);
if (node.nodeType === Node.ELEMENT_NODE && node.matches('br')) break;
node = node.nextSibling;
guard += 1;
}
}
function buildEntryFromCheckbox(box, index) {
if (isControlCheckbox(box)) return null;
const tableRow = box.closest('tr');
if (tableRow && tableRow.querySelector(LINK_SELECTOR)) {
return {
index,
parent: tableRow.parentNode,
title: getTitleFromNode(tableRow),
nodes: [tableRow]
};
}
const label = box.closest('label');
if (label && label.querySelector(LINK_SELECTOR)) {
const nodes = [label];
collectTrailingBreaks(label, nodes);
return {
index,
parent: label.parentNode,
title: getTitleFromNode(label),
nodes
};
}
const parent = box.parentNode;
if (!parent) return null;
let link = null;
let node = box.nextSibling;
let guard = 0;
while (node && node.parentNode === parent && guard < 30) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches(LINK_SELECTOR)) {
link = node;
break;
}
link = node.querySelector(LINK_SELECTOR);
if (link) break;
if (node.matches('br') || node.matches('input[type="checkbox"]')) break;
}
node = node.nextSibling;
guard += 1;
}
if (!link) return null;
const nodes = [];
node = box;
guard = 0;
while (node && node.parentNode === parent && guard < 40) {
nodes.push(node);
if (node.nodeType === Node.ELEMENT_NODE && node.matches('br')) break;
const next = node.nextSibling;
if (next && next.nodeType === Node.ELEMENT_NODE && next.matches('input[type="checkbox"]')) {
break;
}
node = next;
guard += 1;
}
return {
index,
parent,
title: sortText(link.textContent),
nodes
};
}
function findEntries() {
applyCustomTitles();
const boxes = Array.from(document.querySelectorAll('input[type="checkbox"]'));
const entries = boxes
.map(buildEntryFromCheckbox)
.filter((entry) => entry && entry.parent && entry.title && entry.nodes.length > 0);
const groups = new Map();
entries.forEach((entry) => {
if (!groups.has(entry.parent)) groups.set(entry.parent, []);
groups.get(entry.parent).push(entry);
});
let bestEntries = [];
groups.forEach((groupEntries) => {
if (groupEntries.length > bestEntries.length) bestEntries = groupEntries;
});
return bestEntries.length > 1 ? bestEntries : [];
}
function getSelectedEntries() {
return findEntries().filter((entry) => {
return entry.nodes.some((node) => {
if (node.nodeType !== Node.ELEMENT_NODE) return false;
const checkbox = node.matches('input[type="checkbox"]')
? node
: node.querySelector('input[type="checkbox"]');
return checkbox && checkbox.checked && !isControlCheckbox(checkbox);
});
});
}
function editSelectedEntryTitle() {
const selectedEntries = getSelectedEntries();
if (selectedEntries.length !== 1) {
setStatus('Select exactly one entry to rename');
return;
}
const link = getEntryLink(selectedEntries[0]);
if (!link) {
setStatus('Selected entry link was not found');
return;
}
const currentTitle = cleanText(link.textContent);
const nextTitle = window.prompt('Edit selected watchlist entry name:', currentTitle);
if (nextTitle === null) return;
const cleanTitle = cleanText(nextTitle);
if (!cleanTitle) {
setStatus('Name was not changed');
return;
}
const titles = loadCustomTitles();
titles[normalizeHref(link.href)] = cleanTitle;
saveCustomTitles(titles);
link.textContent = cleanTitle;
setStatus('Selected entry name updated');
}
function moveEntries(entries) {
const parent = entries[0].parent;
const marker = document.createComment('bt-watchlist-sort-marker');
parent.insertBefore(marker, entries[0].nodes[0]);
entries.forEach((entry) => {
entry.nodes.forEach((node) => parent.insertBefore(node, marker));
});
parent.removeChild(marker);
}
function sortEntries() {
const entries = findEntries();
if (!entries.length) {
setStatus('No sortable entries found');
return;
}
if (!originalEntries) {
originalEntries = entries.map((entry) => ({
parent: entry.parent,
title: entry.title,
nodes: entry.nodes.slice()
}));
}
const sortedEntries = entries.slice().sort((a, b) => {
return a.title.localeCompare(b.title, undefined, {
numeric: true,
sensitivity: 'base'
});
});
moveEntries(sortedEntries);
setStatus(`Sorted ${sortedEntries.length} entries`);
}
function restoreEntries() {
if (!originalEntries) {
setStatus('Original order is not saved yet');
return;
}
moveEntries(originalEntries);
setStatus('Original order restored');
}
function setStatus(message) {
const status = document.getElementById(STATUS_ID);
if (status) status.textContent = message;
}
function insertButton() {
if (document.getElementById(BUTTON_ID)) return;
const entries = findEntries();
const wrapper = document.createElement('div');
wrapper.style.margin = '10px 0';
const button = document.createElement('button');
button.id = BUTTON_ID;
button.type = 'button';
button.textContent = 'Sort A-Z';
button.style.marginRight = '8px';
button.style.padding = '4px 10px';
button.style.cursor = 'pointer';
const editButton = document.createElement('button');
editButton.id = EDIT_BUTTON_ID;
editButton.type = 'button';
editButton.textContent = 'Edit selected name';
editButton.style.marginRight = '8px';
editButton.style.padding = '4px 10px';
editButton.style.cursor = 'pointer';
const status = document.createElement('span');
status.id = STATUS_ID;
status.style.color = '#555';
status.textContent = entries.length ? `Found ${entries.length} entries` : 'Sorter loaded';
button.addEventListener('click', () => {
if (sorted) {
restoreEntries();
button.textContent = 'Sort A-Z';
sorted = false;
return;
}
sortEntries();
button.textContent = 'Restore original order';
sorted = true;
});
editButton.addEventListener('click', editSelectedEntryTitle);
wrapper.appendChild(button);
wrapper.appendChild(editButton);
wrapper.appendChild(status);
const removeButton = Array.from(document.querySelectorAll('input, button')).find((element) => {
const text = `${element.value || ''} ${element.textContent || ''}`.toLowerCase();
return text.includes('remove checked');
});
if (removeButton && removeButton.parentNode) {
removeButton.parentNode.insertBefore(wrapper, removeButton.nextSibling);
return;
}
const title = Array.from(document.querySelectorAll('b, h1, h2, h3, td, div')).find((element) => {
return cleanText(element.textContent).toLowerCase() === 'edit watchlist';
});
if (title && title.parentNode) {
title.parentNode.insertBefore(wrapper, title.nextSibling);
return;
}
document.body.insertBefore(wrapper, document.body.firstChild);
}
function init() {
if (!isWatchlistPage()) return;
applyCustomTitles();
insertButton();
}
init();
window.setTimeout(init, 500);
window.setTimeout(init, 1500);
})();
If you want, I can add some more useful features in the future, such as :
- Search / Filter Box so that a specific topic can be found very easily.
- The ability to group Boards and Topics separately so that the watchlist will be more organized.
- Recently Updated Topic First Option so that the most recently updated topics are shown at the top.