// ==UserScript==
// @name MusicBrainz: Sort artists
// @description Allows you to change the order of artist names in any of the multiple artists editors. NOTICE: This will remove any artist lookup data already present in the editor. You have to assign this manually again.
// @supportURL https://github.com/JensBee/userscripts
// @namespace http://www.jens-bertram.net/userscripts/sort-artists
// @icon https://wiki.musicbrainz.org/-/images/3/39/MusicBrainz_Logo_Square_Transparent.png
// @license MIT
// @version 0.1.1beta
//
// @require https://greasyfork.org/scripts/5140-musicbrainz-function-library/code/MusicBrainz%20function%20library.js?version=21997
//
// @grant none
// @include *://musicbrainz.org/recording/*/edit
// @include *://*.musicbrainz.org/recording/*/edit
// @include *://musicbrainz.org/recording/create
// @include *://*.musicbrainz.org/recording/create
// @include *://musicbrainz.org/release/*/edit
// @include *://*.musicbrainz.org/release/*/edit
// @include *://musicbrainz.org/release-group/*/edit
// @include *://*.musicbrainz.org/release-group/*/edit
// @include *://musicbrainz.org/release/add
// @include *://*.musicbrainz.org/release/add
// @include *://musicbrainz.org/artist/*/split
// @include *://*.musicbrainz.org/artist/*/split
// ==/UserScript==
// Hackish solution to re-sort artist credits. There's currently no (no known to
// me) option to preserve already associated artist data. Sorting is done by
// first removing all credits, resorting them and adding them again.
//**************************************************************************//
var mbz = mbz || {};
mbz.artist_sort = {
btn: {
down: '<button class="icon track-down artist_sort MBZ-ArtistSort" '
+ 'type="button"></button>',
up: '<button class="icon track-up artist_sort MBZ-ArtistSort" '
+ 'type="button"></button>'
},
notice: '<b>Notice:</b> When sorting artists you\'ll have '
+ 'to lookup all associations already made again.'
};
mbz.artist_sort.BubbleEditor = function() {};
mbz.artist_sort.BubbleEditor.prototype = {
/**
* Executes the sorting.
*/
_doSort: function(api, idx, dir) {
this.rewriteRows(api, this.moveRow(this._scanRowData(api), idx, dir));
},
/**
* Extract data from each artist credit table row.
*/
_scanRowData: function(api) {
var rowsData = [];
var rows = api.getCreditRows();
if (rows.length > 1) {
$.each(rows, function(idx) {
// collect row data
var inputs = api.getCreditInputs($(this));
if (inputs.length == 3) {
// mb-artist, artist-credit, join phrase
rowsData.push([inputs[0].val(), inputs[1].val(),
inputs[2].val()]);
} else {
console.err("Error scanning artist credits: inputs not found.");
}
});
}
return rowsData;
},
/**
* Re-writes row data to move a data row up or down.
* @rowsData current row data array
* @idx Row index to move
* @dir >0 move down, <0 move up
* @return new row data array
*/
moveRow: function(rowsData, idx, dir) {
var newRowData = null;
if (dir > 0 && idx < rowsData.length -1) { // down
newRowData = this.swapRows(rowsData, idx, idx + 1);
} else if (dir < 0 && idx > 0){ // up
newRowData = this.swapRows(rowsData, idx, idx - 1);
}
if (newRowData && newRowData.length > 0) {
return newRowData;
}
return [];
},
/**
* Removes all rows from the editor and add all new ones with the updated
* data.
* @bubbleApi MBZ Bubble API to use for calls
* @rowData new row data to write
*/
rewriteRows: function(bubbleApi, rowData) {
if (rowData.length > 0) {
// get current rows..
var rows = bubbleApi.getCreditRows();
// add a new empty one, that will survive, else any data may be still set
bubbleApi.addArtist("", true);
// remove all previous credits, but the last one will survive
$.each(bubbleApi.getCreditRows(), function(){
bubbleApi.removeArtist(this); // remove by row
});
// add as many rows as we need
for (var i=0; i < rowData.length; i++) {
bubbleApi.addArtist(rowData[i], true);
}
}
},
/**
* Swap position of two rows.
* @rowsData current row data array
* @indexA first row to swap
* @indexB second row to swap
* @return row data array with the two rows position swapped
*/
swapRows: function(rowsData, idxA, idxB) {
var newRowData = [];
for (var idx in rowsData) {
if (idx == idxA) {
newRowData[idx] = rowsData[idxB];
} else if (idx == idxB) {
newRowData[idx] = rowsData[idxA];
} else {
newRowData[idx] = rowsData[idx];
}
}
// switch join phrases
var joinA = newRowData[idxA][2];
var joinB = newRowData[idxB][2];
newRowData[idxA][2] = joinB;
newRowData[idxB][2] = joinA;
return newRowData;
}
};
/**
* Access the artists bubble editor.
*/
mbz.artist_sort.ArtistBubbleEditor = function() {
var b = null;
var initialized = false;
var self = this;
/**
* Executes the sorting.
*/
function doSort(idx, dir) {
self._doSort.call(self, MBZ.BubbleEditor.ArtistCredits, idx, dir);
};
this.init = function(bubble) {
if (initialized) {
return;
}
if (bubble.length == 0) {
console.debug("Credits bubble not found.");
return;
}
initialized = true;
b = bubble;
b.on('click', 'button.MBZ-ArtistSort', function(){
var btn = $(this);
var idx = btn.data('idx');
if (typeof idx !== 'undefined' && idx != null) {
if (btn.hasClass('track-up')) {
doSort(idx, -1);
return false;
} else if (btn.hasClass('track-down')) {
doSort(idx, 1);
return false;
}
}
});
var notice = $(b.find('p').get(0));
notice.after('<p>' + mbz.artist_sort.notice + '</p>');
};
/**
* Attach sort buttons to each data row.
*/
this.attachSortButtons = function() {
var rows = MBZ.BubbleEditor.ArtistCredits.getCreditRows();
if (rows.length > 1) {
var self = this;
$.each(rows, function(idx) {
var cell = $(this).children('td').first().find('label');
if (cell.find('button.artist_sort').length == 0) {
var btnUp = $(mbz.artist_sort.btn.up);
var btnDown = $(mbz.artist_sort.btn.down);
btnUp.data('idx', idx);
btnDown.data('idx', idx);
cell.prepend(btnUp);
cell.prepend(btnDown);
}
});
}
};
MBZ.BubbleEditor.ArtistCredits.onAppear({cb: self.init});
MBZ.BubbleEditor.ArtistCredits.onContentChange({cb: self.attachSortButtons});
};
mbz.artist_sort.ArtistBubbleEditor.prototype =
new mbz.artist_sort.BubbleEditor();
/**
* Access the track artists bubble editor.
*/
mbz.artist_sort.TrackBubbleEditor = function() {
var b = $('#track-ac-bubble');
var rowsData = [];
var initialized = false;
var self = this;
/**
* Executes the sorting.
*/
function doSort(idx, dir) {
self._doSort.call(self, MBZ.BubbleEditor.TrackArtistCredits, idx, dir);
};
/**
* Initialize the component.
*/
function init() {
if (initialized) {
return;
}
initialized = true;
b.find('thead').prepend('<tr><td colspan="4" style="padding-bottom:1em;">'
+ mbz.artist_sort.notice + '</td></tr>');
b.on('click', 'button.MBZ-ArtistSort', function(){
var btn = $(this);
var idx = btn.data('idx');
if (typeof idx !== 'undefined' && idx != null) {
if (btn.hasClass('track-up')) {
doSort(idx, -1);
return false;
} else if (btn.hasClass('track-down')) {
doSort(idx, 1);
return false;
}
}
});
};
/**
* Attach sort buttons to each data row.
*/
this.attachSortButtons = function() {
var rows = MBZ.BubbleEditor.TrackArtistCredits.getCreditRows();
if (rows.length > 1) {
$.each(rows, function(idx) {
var cell = $($(this).find('td').get(3));
if (cell.length == 1 && cell.find('button.MBZ-ArtistSort').length == 0) {
var btnUp = $(mbz.artist_sort.btn.up);
var btnDown = $(mbz.artist_sort.btn.down);
btnUp.data('idx', idx);
btnDown.data('idx', idx);
cell.prepend(btnUp);
cell.prepend(btnDown);
}
});
}
};
MBZ.BubbleEditor.TrackArtistCredits.onContentChange({
cb: this.attachSortButtons});
init();
};
mbz.artist_sort.TrackBubbleEditor.prototype =
new mbz.artist_sort.BubbleEditor();
/**
* Initialize all required components.
*/
mbz.artist_sort.init = function() {
var path = window.location.pathname;
// artist bubble editor is always there
new mbz.artist_sort.ArtistBubbleEditor();
// release page has also a track bubble editor
if (path.contains('/release/')) {
// resize bubble slightly to make space for sort buttons
MBZ.Html.addStyle('#release-editor #track-ac-bubble {width:61%;}');
// track artist bubble editor
new mbz.artist_sort.TrackBubbleEditor();
}
}
mbz.artist_sort.init();