musicbrainz.org: MOUSE-LESS EDITING! Cleverly focus and refocus fields in various MusicBrainz edit pages and tracklist Up Down key navigation
// ==UserScript==
// @name mb. AUTO-FOCUS + KEYBOARD-SELECT
// @version 2025.9.9
// @description musicbrainz.org: MOUSE-LESS EDITING! Cleverly focus and refocus fields in various MusicBrainz edit pages and tracklist Up Down key navigation
// @namespace https://github.com/jesus2099/konami-command
// @supportURL https://github.com/jesus2099/konami-command/labels/mb_AUTO-FOCUS-KEYBOARD-SELECT
// @author jesus2099
// @licence CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/
// @licence GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @since 2012-06-08; https://web.archive.org/web/20131103163357/userscripts.org/scripts/show/135547 / https://web.archive.org/web/20141011084007/userscripts-mirror.org/scripts/show/135547
// @icon data:image/gif;base64,R0lGODlhEAAQAKEDAP+/3/9/vwAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh/glqZXN1czIwOTkAIfkEAQACAwAsAAAAABAAEAAAAkCcL5nHlgFiWE3AiMFkNnvBed42CCJgmlsnplhyonIEZ8ElQY8U66X+oZF2ogkIYcFpKI6b4uls3pyKqfGJzRYAACH5BAEIAAMALAgABQAFAAMAAAIFhI8ioAUAIfkEAQgAAwAsCAAGAAUAAgAAAgSEDHgFADs=
// @grant none
// @include /^https?:\/\/((beta|test)\.)?musicbrainz\.(org|eu)\/(recording|release)\/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\/delete$/
// @match *://*.musicbrainz.org/*/*/add-alias
// @match *://*.musicbrainz.org/*/*/alias/*/delete
// @match *://*.musicbrainz.org/*/*/alias/*/edit
// @match *://*.musicbrainz.org/*/*/edit_annotation*
// @match *://*.musicbrainz.org/*/*/tags*
// @match *://*.musicbrainz.org/artist/*/edit
// @match *://*.musicbrainz.org/artist/create
// @match *://*.musicbrainz.org/cdtoc/*/set-durations?tracklist=*
// @match *://*.musicbrainz.org/cdtoc/attach*
// @match *://*.musicbrainz.org/cdtoc/move*
// @match *://*.musicbrainz.org/cdtoc/remove*
// @match *://*.musicbrainz.org/edit/*/cancel*
// @match *://*.musicbrainz.org/edit-note/*/modify
// @match *://*.musicbrainz.org/isrc/delete*
// @match *://*.musicbrainz.org/label/*/edit
// @match *://*.musicbrainz.org/label/create
// @match *://*.musicbrainz.org/recording/*/add-isrc
// @match *://*.musicbrainz.org/recording/*/edit
// @match *://*.musicbrainz.org/recording/*/remove-puid*
// @match *://*.musicbrainz.org/recording/create*
// @match *://*.musicbrainz.org/release/*/add-cover-art
// @match *://*.musicbrainz.org/release/*/edit
// @match *://*.musicbrainz.org/release/*/edit-cover-art/*
// @match *://*.musicbrainz.org/release/*/remove-cover-art/*
// @match *://*.musicbrainz.org/release/*/reorder-cover-art
// @match *://*.musicbrainz.org/release/add*
// @match *://*.musicbrainz.org/release-group/*/edit
// @match *://*.musicbrainz.org/release-group/create*
// @match *://*.musicbrainz.org/url/*/edit
// @match *://*.musicbrainz.org/work/*/add-iswc
// @match *://*.musicbrainz.org/work/*/edit
// @match *://*.musicbrainz.org/work/create*
// @match *://musicbrainz.eu/*/*/add-alias
// @match *://musicbrainz.eu/*/*/alias/*/delete
// @match *://musicbrainz.eu/*/*/alias/*/edit
// @match *://musicbrainz.eu/*/*/edit_annotation*
// @match *://musicbrainz.eu/*/*/tags*
// @match *://musicbrainz.eu/artist/*/edit
// @match *://musicbrainz.eu/artist/create
// @match *://musicbrainz.eu/cdtoc/*/set-durations?tracklist=*
// @match *://musicbrainz.eu/cdtoc/attach*
// @match *://musicbrainz.eu/cdtoc/move*
// @match *://musicbrainz.eu/cdtoc/remove*
// @match *://musicbrainz.eu/edit/*/cancel*
// @match *://musicbrainz.eu/edit-note/*/modify
// @match *://musicbrainz.eu/isrc/delete*
// @match *://musicbrainz.eu/label/*/edit
// @match *://musicbrainz.eu/label/create
// @match *://musicbrainz.eu/recording/*/add-isrc
// @match *://musicbrainz.eu/recording/*/edit
// @match *://musicbrainz.eu/recording/*/remove-puid*
// @match *://musicbrainz.eu/recording/create*
// @match *://musicbrainz.eu/release/*/add-cover-art
// @match *://musicbrainz.eu/release/*/edit
// @match *://musicbrainz.eu/release/*/edit-cover-art/*
// @match *://musicbrainz.eu/release/*/remove-cover-art/*
// @match *://musicbrainz.eu/release/*/reorder-cover-art
// @match *://musicbrainz.eu/release/add*
// @match *://musicbrainz.eu/release-group/*/edit
// @match *://musicbrainz.eu/release-group/create*
// @match *://musicbrainz.eu/url/*/edit
// @match *://musicbrainz.eu/work/*/add-iswc
// @match *://musicbrainz.eu/work/*/edit
// @match *://musicbrainz.eu/work/create*
// @run-at document-end
// ==/UserScript==
"use strict";
// focus most clever field http://tickets.musicbrainz.org/browse/MBS-2213
var input = getMostCleverInputToFocus();
if (input) {
input.focus();
if (input.getAttribute("type") == "text") {
// Put caret at the text end
var value_length = input.value.length;
if (value_length) {
input.setSelectionRange(value_length, value_length);
}
}
highlight(input);
}
// re‐focus input field after related tool click http://tickets.musicbrainz.org/browse/MBS-7321
// TODO: Maybe remove if bad good idea https://tickets.metabrainz.org/browse/MBS-7321?focusedId=67924&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-67924
var tools = document.querySelectorAll("input[class*='with-guesscase'] ~ button:not(.guesscase-options)");
for (var t = 0; t < tools.length; t++) {
tools[t].addEventListener("click", function(event) {
var related_input = this.parentNode.querySelector("input");
related_input.focus();
highlight(related_input);
});
}
function addTracklistUpDownKeyNavigation() {
// press UP↓/↑DOWN keys to navigate through track positions, names and lengths, auto clean‐up and format track length
var tracklist = document.querySelector("#tracklist");
if (tracklist) {
tracklist.addEventListener("keydown", function(event) {
// detect UP ↑ / DOWN ↓ to push cursor to upper or lower text field
var class_name_match = event.target.className.match(/(?:\s|^)(pos|track-(name|length))(?:\s|$)/);
if (class_name_match && (event.key == "ArrowUp" || event.key == "ArrowDown")) {
var same_class_inputs = tracklist.querySelectorAll("input." + class_name_match[1]);
var index;
for (index = 0; index < same_class_inputs.length; index++) {
if (same_class_inputs[index] == event.target) {
break;
}
}
index += event.key == "ArrowUp" ? -1 : 1;
if (index >= 0 && index < same_class_inputs.length) {
event.preventDefault();
event.stopPropagation();
if (
event.shiftKey
|| event.target.className.match(/pos|track-length/)
|| event.target.selectionEnd - event.target.selectionStart === event.target.value.length
) {
// select all
// input.select() is more straightforward, but it does not scroll the window
same_class_inputs[index].setSelectionRange(0, same_class_inputs[index].value.length);
} else {
if (event.target.selectionStart == event.target.selectionEnd && event.target.selectionStart == event.target.value.length) {
// place the caret at the end
same_class_inputs[index].setSelectionRange(same_class_inputs[index].value.length, same_class_inputs[index].value.length);
} else {
// keep same selection or caret position
same_class_inputs[index].setSelectionRange(event.target.selectionStart, event.target.selectionEnd);
}
}
// activate the input (but does not scroll to make it visible)
same_class_inputs[index].focus();
}
}
});
}
}
function getMostCleverInputToFocus() {
var best_input;
switch (location.pathname.replace(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/, "*").replace(/[0-9]+/, "*")) {
case "/artist/*/add-alias":
case "/work/*/add-alias":
case "/label/*/add-alias":
case "/recording/*/add-alias":
case "/release/*/add-alias":
case "/release-group/*/add-alias":
case "/series/*/add-alias":
case "/artist/*/alias/*/edit":
case "/label/*/alias/*/edit":
case "/recording/*/alias/*/edit":
case "/release/*/alias/*/edit":
case "/release-group/*/alias/*/edit":
case "/series/*/alias/*/edit":
case "/work/*/alias/*/edit":
best_input = document.querySelector("input[id='id-edit-alias.name']");
break;
case "/artist/create":
case "/artist/*/edit":
best_input = document.querySelector("input[id='id-edit-artist.name']");
break;
case "/artist/*/edit_annotation":
case "/label/*/edit_annotation":
case "/recording/*/edit_annotation":
case "/release/*/edit_annotation":
case "/release-group/*/edit_annotation":
case "/work/*/edit_annotation":
best_input = document.querySelector("textarea[id='id-edit-annotation.text']");
break;
case "/cdtoc/attach":
case "/cdtoc/move":
best_input = (
document.querySelector("input[id='id-filter-release.query']")
|| document.querySelector("input[id='id-filter-artist.query']")
);
break;
case "/label/create":
case "/label/*/edit":
best_input = document.querySelector("input[id='id-edit-label.name']");
break;
case "/recording/create":
case "/recording/*/edit":
best_input = document.querySelector("input[id='id-edit-recording.name']");
break;
case "/recording/*/add-isrc":
best_input = document.querySelector("input[id='id-add-isrc.isrc']");
break;
case "/release/add":
case "/release/*/edit":
best_input = (
document.querySelector("input#name")
);
addTracklistUpDownKeyNavigation();
break;
case "/release/*/add-cover-art":
best_input = document.querySelector("input[id='id-add-cover-art.comment']");
break;
case "/release/*/edit-cover-art/*":
best_input = document.querySelector("input[id='id-edit-cover-art.comment']");
break;
case "/artist/*/tags":
case "/label/*/tags":
case "/recording/*/tags":
case "/release/*/tags":
case "/release-group/*/tags":
case "/work/*/tags":
best_input = document.querySelector("#tag-form textarea");
break;
case "/url/*/edit":
best_input = document.querySelector("input[id='id-edit-url.url']");
break;
case "/release-group/create":
case "/release-group/*/edit":
best_input = document.querySelector("input[id='id-edit-release-group.name']");
break;
case "/work/create":
case "/work/*/edit":
best_input = document.querySelector("input[id='id-edit-work.name']");
break;
case "/work/*/add-iswc":
best_input = document.querySelector("input[id='id-add-iswc.iswc']");
break;
}
return best_input ? best_input : document.querySelector("textarea[id*='edit_note'], textarea[id*='edit-note']");
}
// the focus animation
var interval, blue;
function highlight(input) {
blue = 0;
interval = setInterval(function() { hl(input); }, 50);
}
function hl(input) {
input.style.setProperty("background-color", "rgb(255, 255, " + blue + ")");
blue += 50;
if (blue >= 255) {
clearInterval(interval);
input.style.removeProperty("background-color");
}
}