MusicBrainz: Sort artists

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.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();