diff --git a/mb-reledit-guess_works.user.js b/mb-reledit-guess_works.user.js
index 97b9bc4..80a8857 100644
--- a/mb-reledit-guess_works.user.js
+++ b/mb-reledit-guess_works.user.js
@@ -4,7 +4,7 @@
// @name MusicBrainz relation editor: Guess related works in batch
// @namespace mbz-loujine
// @author loujine
-// @version 2023.3.6
+// @version 2023.3.12
// @downloadURL https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mb-reledit-guess_works.user.js
// @updateURL https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mb-reledit-guess_works.user.js
// @supportURL https://github.com/loujine/musicbrainz-scripts
@@ -20,247 +20,250 @@
const MBID_REGEX = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/;
const repeatHelp = `Ways to associate subworks SW1, SW2, SW3... with selected tracks T1, T2, T3...
- 1,1,1 (or empty) -> SW1 on T1, SW2 on T2, SW3 on T3...
- 1,0,1 -> SW1 on T1, SW2 skipped, SW3 on T2...
- 1,2,1 -> SW1 on T1, SW2 on T2 and T3 (as partial); SW3 on T4...
- 1,-1,1 -> SW1 and SW2 on T1, SW3 on T2...
+ 1,1,1 (or empty) -> SW1 on T1, SW2 on T2, SW3 on T3...
+ 1,0,1 -> SW1 on T1, SW2 skipped, SW3 on T2...
+ 1,2,1 -> SW1 on T1, SW2 on T2 and T3 (as partial); SW3 on T4...
+ 1,-1,1 -> SW1 and SW2 on T1, SW3 on T2...
`;
const setWork = async (recording, work, partial) => {
- const medium = MB.relationshipEditor.state.mediumsByRecordingId.get(recording.id)[0];
- const mediumIdx = medium.position - 1;
- const trackIdx = medium.tracks.filter(t => t.recording.id === recording.id)[0].position -1;
+ const medium = MB.relationshipEditor.state.mediumsByRecordingId.get(recording.id)[0];
+ const tracks = medium.tracks
+ // if medium was unfolded manually, medium.tracks stays empty
+ // but relEditor.state.loadedTracks has the new data
+ ? medium.tracks
+ : MB.relationshipEditor.state.loadedTracks.get(medium.position);
+ const track = tracks.filter(t => t.recording.id === recording.id)[0];
- await helper.waitFor(() => !MB.relationshipEditor.relationshipDialogDispatch, 1);
- MB.relationshipEditor.dispatch({
- type: 'update-dialog-location',
- location: {
- batchSelection: false,
- source: recording,
- targetType: 'work',
- track: MB.relationshipEditor.state.entity.mediums[mediumIdx].tracks[trackIdx],
+ await helper.waitFor(() => !MB.relationshipEditor.relationshipDialogDispatch, 1);
+ MB.relationshipEditor.dispatch({
+ type: 'update-dialog-location',
+ location: {
+ batchSelection: false,
+ source: recording,
+ targetType: 'work',
+ track: track,
+ },
+ });
+ await helper.waitFor(() => !!MB.relationshipEditor.relationshipDialogDispatch, 1);
+
+ MB.relationshipEditor.relationshipDialogDispatch({
+ type: 'update-target-entity',
+ source: recording,
+ action: {
+ type: 'update-autocomplete',
+ source: recording,
+ action: {
+ type: 'select-item',
+ item: {
+ type: 'option',
+ entity: work,
+ id: work.id,
+ name: work.name,
},
- });
- await helper.waitFor(() => !!MB.relationshipEditor.relationshipDialogDispatch, 1);
+ },
+ },
+ });
+ if (partial) {
+ const attrType = MB.linkedEntities.link_attribute_type[server.attr.partial];
MB.relationshipEditor.relationshipDialogDispatch({
- type: 'update-target-entity',
- source: recording,
- action: {
- type: 'update-autocomplete',
- source: recording,
- action: {
- type: 'select-item',
- item: {
- type: 'option',
- entity: work,
- id: work.id,
- name: work.name,
- },
- },
- },
+ type: 'set-attributes',
+ attributes: [{
+ type: {gid: attrType.gid},
+ typeID: attrType.id,
+ typeName: attrType.name,
+ }],
});
+ }
+ await helper.delay(1);
- if (partial) {
- const attrType = MB.linkedEntities.link_attribute_type[server.attr.partial];
- MB.relationshipEditor.relationshipDialogDispatch({
- type: 'set-attributes',
- attributes: [{
- type: {gid: attrType.gid},
- typeID: attrType.id,
- typeName: attrType.name,
- }],
- });
- }
- await helper.delay(1);
-
- if (document.querySelector('.dialog-content p.error')) {
- console.error('Dialog error, probably an identical relation already exists');
- document.querySelector('.dialog-content button.negative').click();
- } else {
- document.querySelector('.dialog-content button.positive').click();
- }
+ if (document.querySelector('.dialog-content p.error')) {
+ console.error('Dialog error, probably an identical relation already exists');
+ document.querySelector('.dialog-content button.negative').click();
+ } else {
+ document.querySelector('.dialog-content button.positive').click();
+ }
};
const replaceWork = async (recording, work) => {
- const rel = recording.relationships.filter(rel => rel.target_type === 'work')[0];
+ const rel = recording.relationships.filter(rel => rel.target_type === 'work')[0];
- await helper.waitFor(() => !MB.relationshipEditor.relationshipDialogDispatch, 1);
+ await helper.waitFor(() => !MB.relationshipEditor.relationshipDialogDispatch, 1);
- document.getElementById(`edit-relationship-recording-work-${rel.id}`).click();
- await helper.waitFor(() => !!MB.relationshipEditor.relationshipDialogDispatch, 1);
+ document.getElementById(`edit-relationship-recording-work-${rel.id}`).click();
+ await helper.waitFor(() => !!MB.relationshipEditor.relationshipDialogDispatch, 1);
- MB.relationshipEditor.relationshipDialogDispatch({
- type: 'update-target-entity',
- source: recording,
- action: {
- type: 'update-autocomplete',
- source: recording,
- action: {
- type: 'select-item',
- item: {
- type: 'option',
- entity: work,
- id: work.id,
- name: work.name,
- },
- },
+ MB.relationshipEditor.relationshipDialogDispatch({
+ type: 'update-target-entity',
+ source: recording,
+ action: {
+ type: 'update-autocomplete',
+ source: recording,
+ action: {
+ type: 'select-item',
+ item: {
+ type: 'option',
+ entity: work,
+ id: work.id,
+ name: work.name,
},
- });
- await helper.delay(1);
+ },
+ },
+ });
+ await helper.delay(1);
- if (document.querySelector('.dialog-content p.error')) {
- console.error('Dialog error, probably an identical relation already exists');
- document.querySelector('.dialog-content button.negative').click();
- } else {
- document.querySelector('.dialog-content button.positive').click();
- }
+ if (document.querySelector('.dialog-content p.error')) {
+ console.error('Dialog error, probably an identical relation already exists');
+ document.querySelector('.dialog-content button.negative').click();
+ } else {
+ document.querySelector('.dialog-content button.positive').click();
+ }
};
const guessWork = () => {
- let idx = 0;
- relEditor.orderedSelectedRecordings().forEach(recording => {
- const url =
- '/ws/js/work/?q=' +
- encodeURIComponent(document.getElementById('prefix').value) +
- ' ' +
- encodeURIComponent(recording.name) +
- '&artist=' +
- encodeURIComponent(recording.artist) +
- '&fmt=json&limit=1';
- if (!recording.related_works.length) {
- idx += 1;
- setTimeout(function () {
- requests.GET(url, function (resp) {
- setWork(recording, JSON.parse(resp)[0]);
- });
- }, idx * server.timeout);
- }
- });
+ let idx = 0;
+ relEditor.orderedSelectedRecordings().forEach(recording => {
+ const url =
+ '/ws/js/work/?q=' +
+ encodeURIComponent(document.getElementById('prefix').value) +
+ ' ' +
+ encodeURIComponent(recording.name) +
+ '&artist=' +
+ encodeURIComponent(recording.artist) +
+ '&fmt=json&limit=1';
+ if (!recording.related_works.length) {
+ idx += 1;
+ setTimeout(() => {
+ requests.GET(url, (resp) => {
+ setWork(recording, JSON.parse(resp)[0]);
+ });
+ }, idx * server.timeout);
+ }
+ });
};
const autoComplete = () => {
- const $input = $('input#mainWork');
- const match = $input.val().match(MBID_REGEX);
- if (match) {
- const mbid = match[0];
- requests.GET(`/ws/2/work/${mbid}?fmt=json`, function (data) {
- data = JSON.parse(data);
- $input.data('mbid', mbid);
- $input.val(data.title || data.name);
- $input.css('background', '#bbffbb');
- });
- } else {
- $input.css('background', '#ffaaaa');
- }
+ const input = document.getElementById('mainWork');
+ const match = input.value.match(MBID_REGEX);
+ if (match) {
+ const mbid = match[0];
+ requests.GET(`/ws/2/work/${mbid}?fmt=json`, (data) => {
+ data = JSON.parse(data);
+ input.setAttribute('mbid', mbid);
+ input.value = data.title || data.name;
+ input.style.backgroundColor = '#bbffbb';
+ });
+ } else {
+ input.style.backgroundColor = '#ffaaaa';
+ }
};
const fetchSubWorks = (workMbid, replace) => {
- replace = replace || false;
- if (workMbid.split('/').length > 1) {
- workMbid = workMbid.split('/')[4];
+ replace = replace || false;
+ if (workMbid.split('/').length > 1) {
+ workMbid = workMbid.split('/')[4];
+ }
+ requests.GET(`/ws/js/entity/${workMbid}?inc=rels`, (resp) => {
+ let repeats = document.getElementById('repeats').value.trim();
+ const subWorks = helper.sortSubworks(JSON.parse(resp));
+ let total = subWorks.length;
+ if (repeats) {
+ repeats = repeats.split(/[,; ]+/).map(s => Number.parseInt(s));
+ total = repeats.reduce((n, m) => Math.max(n,0) + Math.max(m,0), 0);
+ } else {
+ repeats = subWorks.map(() => 1);
}
- requests.GET(`/ws/js/entity/${workMbid}?inc=rels`, function (resp) {
- let repeats = document.getElementById('repeats').value.trim();
- const subWorks = helper.sortSubworks(JSON.parse(resp));
- let total = subWorks.length;
- if (repeats) {
- repeats = repeats.split(/[,; ]+/).map(s => Number.parseInt(s));
- total = repeats.reduce((n, m) => Math.max(n,0) + Math.max(m,0), 0);
- } else {
- repeats = subWorks.map(() => 1);
- }
- const repeatedSubWorks = Array(total);
- const partialSubWorks = Array(total);
- let start = 0;
- subWorks.forEach((sb, sbIdx) => {
- if (repeats[sbIdx] < 0) {
- repeatedSubWorks[start-1].push(sb);
- partialSubWorks.fill(false, start-1, start);
- } else {
- repeatedSubWorks.fill([sb], start, start + repeats[sbIdx]);
- partialSubWorks.fill(
- repeats[sbIdx] > 1 ? true : false, start, start + repeats[sbIdx]);
- start += repeats[sbIdx];
- }
- });
+ const repeatedSubWorks = Array(total);
+ const partialSubWorks = Array(total);
+ let start = 0;
+ subWorks.forEach((sb, sbIdx) => {
+ if (repeats[sbIdx] < 0) {
+ repeatedSubWorks[start-1].push(sb);
+ partialSubWorks.fill(false, start-1, start);
+ } else {
+ repeatedSubWorks.fill([sb], start, start + repeats[sbIdx]);
+ partialSubWorks.fill(
+ repeats[sbIdx] > 1 ? true : false, start, start + repeats[sbIdx]);
+ start += repeats[sbIdx];
+ }
+ });
- relEditor.orderedSelectedRecordings().forEach(async (recording, recIdx) => {
- await helper.delay(recIdx * 200);
- if (recIdx >= repeatedSubWorks.length) {
- return;
- }
- repeatedSubWorks[recIdx].forEach(async (subw, subwIdx) => {
- await helper.delay(subwIdx * 60);
- if (replace && recording.related_works.length) {
- replaceWork(recording, subw);
- } else if (!recording.related_works.length) {
- setWork(recording, subw, partialSubWorks[recIdx]);
- }
- });
- });
+ relEditor.orderedSelectedRecordings().forEach(async (recording, recIdx) => {
+ await helper.delay(recIdx * 200);
+ if (recIdx >= repeatedSubWorks.length) {
+ return;
+ }
+ repeatedSubWorks[recIdx].forEach(async (subw, subwIdx) => {
+ await helper.delay(subwIdx * 60);
+ if (replace && recording.related_works.length) {
+ replaceWork(recording, subw);
+ } else if (!recording.related_works.length) {
+ setWork(recording, subw, partialSubWorks[recIdx]);
+ }
+ });
});
+ });
};
(function displayToolbar() {
- relEditor.container(document.querySelector('div.tabs'))
- .insertAdjacentHTML('beforeend', `
-
-
-
- Search for works
-
-
-
-
- prefix:
-
-
-
-
-
-
Link to parts of a main Work
-
- Fill the main work mbid to link selected recordings to (ordered) parts of the work.
-
-
- Repeats:
-
-
-
đ
-
-
-
-
-
Main work name:
-
-
-
-
- `);
+ relEditor.container(document.querySelector('div.tabs')).insertAdjacentHTML('beforeend', `
+
+
+
+ Search for works
+
+
+
+
+ prefix:
+
+
+
+
+
+
Link to parts of a main Work
+
+ Fill the main work mbid to link selected recordings to (ordered) parts of the work.
+
+
+ Repeats:
+
+
+
đ
+
+
+
+
+
Main work name:
+
+
+
+
+ `);
})();
$(document).ready(function() {
- let appliedNote = false;
- document.getElementById('searchWork').addEventListener('click', () => {
- guessWork();
- if (!appliedNote) {
- relEditor.editNote(GM_info.script, 'Set guessed works');
- appliedNote = true;
- }
- });
- $('input#mainWork').on('input', autoComplete);
- document.querySelector('input#fetchSubworks').addEventListener('click', () => {
- fetchSubWorks(
- $('input#mainWork').data('mbid'),
- document.querySelector('input#replaceSubworks').checked
- );
- if (!appliedNote) {
- relEditor.editNote(GM_info.script, 'Set guessed subworks');
- appliedNote = true;
- }
- });
- return false;
+ let appliedNote = false;
+ document.getElementById('searchWork').addEventListener('click', () => {
+ guessWork();
+ if (!appliedNote) {
+ relEditor.editNote(GM_info.script, 'Set guessed works');
+ appliedNote = true;
+ }
+ });
+ document.getElementById('mainWork').addEventListener('input', autoComplete);
+ document.getElementById('fetchSubworks').addEventListener('click', () => {
+ fetchSubWorks(
+ document.getElementById('mainWork').getAttribute('mbid'),
+ document.getElementById('replaceSubworks').checked,
+ );
+ if (!appliedNote) {
+ relEditor.editNote(GM_info.script, 'Set guessed subworks');
+ appliedNote = true;
+ }
+ });
+ return false;
});
diff --git a/mbz-loujine-common.js b/mbz-loujine-common.js
index 07c9ae9..fed41fd 100644
--- a/mbz-loujine-common.js
+++ b/mbz-loujine-common.js
@@ -4,7 +4,7 @@
// @name mbz-loujine-common
// @namespace mbz-loujine
// @author loujine
-// @version 2023.3.9
+// @version 2023.3.12
// @description musicbrainz.org: common functions
// @compatible firefox+greasemonkey
// @license MIT
@@ -18,641 +18,641 @@ const wikiUrl = 'https://github.com/loujine/musicbrainz-scripts/wiki';
// from musicbrainz-server/root/static/scripts/tests/typeInfo.js
class Server {
- constructor() {
- this.recordingLinkType = {
- instrument: 148,
- vocals: 149,
- orchestra: 150,
- conductor: 151,
- chorusmaster: 152,
- concertmaster: 760,
- performer: 156,
- work: 278,
- place: 693,
- area: 698,
- };
- this.instrumentType = {
- instrument: 14,
- strings: 69,
- bass: 70,
- double_bass: 71,
- acoustic_bass_guitar: 73,
- electric_bass_guitar: 74,
- guitar_family: 75,
- violin_family: 82,
- cello: 84,
- violin: 86,
- membranophone: 125,
- drums: 126,
- harpsichord: 174,
- piano: 180,
- guitar: 229,
- bass_guitar: 277,
- string_quartet: 1067,
- };
- this.vocalType = {
- vocal: 3,
- lead: 4,
- alto: 5,
- bass: 7,
- soprano: 10,
- tenor: 11,
- choir: 13,
- };
- this.workType = {
- 'Song': 17,
- 'Aria': 1,
- 'Audio drama': 25,
- 'Ballet': 2,
- 'Beijing opera': 26,
- 'Cantata': 3,
- 'Concerto': 4,
- 'Ătude': 20,
- 'Incidental music': 30,
- 'Madrigal': 7,
- 'Mass': 8,
- 'Motet': 9,
- 'Musical': 29,
- 'Opera': 10,
- 'Operetta': 24,
- 'Oratorio': 11,
- 'Overture': 12,
- 'Partita': 13,
- 'Play': 28,
- 'Poem': 21,
- 'Prose': 23,
- 'Quartet': 14,
- 'Sonata': 5,
- 'Song-cycle': 15,
- 'Soundtrack': 22,
- 'Suite': 6,
- 'Symphonic poem': 18,
- 'Symphony': 16,
- 'Zarzuela': 19,
- };
- this.workLinkType = {
- arrangement: 350,
- composer: 168,
- subwork: 281,
- writer: 167,
- };
- this.releaseLinkTypeID = {
- 44: 'instrument',
- 60: 'vocals',
- 45: 'orchestra',
- 46: 'conductor',
- 53: 'chorusmaster',
- 51: 'performer',
- };
- this._performingRoles = [
- 'instrument',
- 'vocals',
- 'orchestra',
- 'conductor',
- 'performer',
- ];
- this._minorPerformingRoles = [
- 'concertmaster',
- 'chorusmaster',
- ];
- this.aliasArtistType = {
- 'Artist name': 1,
- 'Legal name': 2,
- 'Search hint': 3,
- };
- this.aliasInstrumentType = {
- 'Instrument name': 1,
- 'Search hint': 2,
- 'Brand name': 3,
- };
- this.aliasType = {
- 'Name': 1,
- 'Search hint': 2,
- };
- this.attr = {
- additional: 1,
- strings: 69,
- cello: 84,
- violin: 86,
- piano: 180,
- guest: 194,
- bowedStrings: 275,
- string_quartet: 1067,
- piano_trio: 1070,
- string_trio: 1074,
- cover: 567,
- live: 578,
- partial: 579,
- instrumental: 580,
- video: 582,
- solo: 596,
- medley: 750,
- };
- this.language = {
- '[Multiple languages]': 284,
- '[No lyrics]': 486,
- 'Arabic': 18,
- 'Chinese': 76,
- 'Czech': 98,
- 'Danish': 100,
- 'Dutch': 113,
- 'English': 120,
- 'Finnish': 131,
- 'French': 134,
- 'German': 145,
- 'Greek': 159,
- 'Hungarian': 176,
- 'Italian': 195,
- 'Japanese': 198,
- 'Korean': 224,
- 'Norwegian': 309,
- 'Occitan': 318,
- 'Polish': 338,
- 'Portuguese': 340,
- 'Russian': 353,
- 'Spanish': 393,
- 'Swedish': 403,
- 'Turkish': 433,
- };
- this.languageFromISO = {
- ara: 'Arabic',
- zho: 'Chinese',
- ces: 'Czech',
- dan: 'Danish',
- nld: 'Dutch',
- eng: 'English',
- fin: 'Finnish',
- fra: 'French',
- deu: 'German',
- ell: 'Greek',
- hun: 'Hungarian',
- ita: 'Italian',
- jpn: 'Japanese',
- kor: 'Korean',
- mul: '[Multiple languages]',
- nor: 'Norwegian',
- oci: 'Occitan',
- pol: 'Polish',
- por: 'Portuguese',
- rus: 'Russian',
- spa: 'Spanish',
- swe: 'Swedish',
- tur: 'Turkish',
- zxx: '[No lyrics]',
- };
- this.locale = {
- 'Afrikaans': 'af',
- 'Azerbaijani': 'az',
- 'Albanian': 'sq',
- 'Amharic': 'am',
- 'Arabic': 'ar',
- 'Armenian': 'hy',
- 'Asturian': 'ast',
- 'Bengali/Bangla': 'bn',
- 'Basque': 'eu',
- 'Belarusian': 'be',
- 'Bosnian': 'bs',
- 'Breton': 'br',
- 'Bulgarian': 'bg',
- 'Burmese': 'my',
- 'Cantonese': 'yue',
- 'Catalan': 'ca',
- 'Central Kurdish/Sorani': 'ckb',
- 'Chinese': 'zh',
- 'Chinese Hong Kong SAR China Traditional': 'zh_Hant_HK',
- 'Chinese Simplified': 'zh_Hans',
- 'Chinese Singapore Simplified': 'zh_Hans_SG',
- 'Chinese Taiwan Traditional': 'zh_Hant_TW',
- 'Chinese Traditional': 'zh_Hant',
- 'Croatian': 'hr',
- 'Czech': 'cs',
- 'Danish': 'da',
- 'Dutch': 'nl',
- 'English': 'en',
- 'Esperanto': 'eo',
- 'Estonian': 'et',
- 'Faroese': 'fo',
- 'Filipino': 'fil',
- 'Finnish': 'fi',
- 'French': 'fr',
- 'Galician': 'gl',
- 'Georgian': 'ka',
- 'German': 'de',
- 'Greek': 'el',
- 'Gujarati': 'gu',
- 'Hawaiâian': 'haw',
- 'Hebrew': 'he',
- 'Hindi': 'hi',
- 'Hungarian': 'hu',
- 'Icelandic': 'is',
- 'Indonesian': 'id',
- 'Interlingua': 'ia',
- 'Irish': 'ga',
- 'Italian': 'it',
- 'Japanese': 'ja',
- 'Javanese': 'jv',
- 'Kannada': 'kn',
- 'Kazakh': 'kk',
- 'Khmer (Central)': 'km',
- 'Korean': 'ko',
- 'Kyrgyz': 'ky',
- 'Latvian': 'lv',
- 'Lithuanian': 'lt',
- 'Low German': 'nds',
- 'Luxembourgish': 'lb',
- 'Macedonian': 'mk',
- 'Malay': 'ms',
- 'Malayam': 'ml',
- 'MÄori': 'mi',
- 'Marathi': 'mr',
- 'Mongolian': 'mn',
- 'Nepali': 'ne',
- 'Norwegian BokmÄl': 'nb',
- 'Norwegian Nynorsk': 'nn',
- 'Occitan': 'oc',
- 'Persian (Farsi)': 'fa',
- 'Polish': 'pl',
- 'Portuguese': 'pt',
- 'Punjabi': 'pa',
- 'Quechua': 'qu',
- 'Romanian': 'ro',
- 'Russian': 'ru',
- 'Scottish Gaelic': 'gd',
- 'Serbian': 'sr',
- 'Serbo-Croatian': 'sh',
- 'Sinhala': 'si',
- 'Slovakian': 'sk',
- 'Slovenian': 'sl',
- 'Somali': 'so',
- 'Spanish': 'es',
- 'Swahili': 'sw',
- 'Swedish': 'sv',
- 'Tamil': 'ta',
- 'Tajik': 'tg',
- 'Telugu': 'te',
- 'Thai': 'th',
- 'Turkish': 'tr',
- 'Urdu': 'ur',
- 'Ukrainian': 'uk',
- 'Uyghur': 'ug',
- 'Uzbek': 'uz',
- 'Vietnamese': 'vi',
- 'Welsh (Cymric)': 'cy',
- 'Western Frisian': 'fy',
- 'Yiddish': 'yi',
- 'Yoruba': 'yo',
- 'Zulu': 'zu',
- };
- this.workKeyAttr = {
- 'C major': 2,
- 'C minor': 3,
- 'C-sharp major': 4,
- 'C-sharp minor': 5,
- 'D-flat major': 6,
- 'D-flat minor': 7,
- 'D major': 8,
- 'D minor': 9,
- 'D-sharp minor': 10,
- 'E-flat major': 11,
- 'E-flat minor': 12,
- 'E major': 13,
- 'E minor': 14,
- 'E-sharp minor': 15,
- 'F-flat major': 16,
- 'F major': 17,
- 'F minor': 18,
- 'F-sharp major': 19,
- 'F-sharp minor': 20,
- 'G-flat major': 21,
- 'G major': 22,
- 'G minor': 23,
- 'G-sharp major': 24,
- 'G-sharp minor': 25,
- 'A-flat major': 26,
- 'A-flat minor': 27,
- 'A major': 28,
- 'A minor': 29,
- 'A-sharp minor': 30,
- 'B-flat major': 31,
- 'B-flat minor': 32,
- 'B major': 33,
- 'B minor': 34,
- 'C Dorian': 789,
- 'D Dorian': 790,
- 'E Dorian': 791,
- 'F Dorian': 792,
- 'G Dorian': 793,
- 'A Dorian': 794,
- 'B Dorian': 795,
- };
- this.unknownArtistId = 97546;
- // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
- // we wait for 'server.timeout' milliseconds between two queries
- this.timeout = 1000;
- }
-
- releaseToRecordingLink(linkTypeID) {
- return this.recordingLinkType[this.releaseLinkTypeID[linkTypeID]];
- }
-
- performingLinkTypes(skipMinorRoles = false) {
- const roles = skipMinorRoles ? this._performingRoles : [
- ...this._performingRoles, ...this._minorPerformingRoles
- ];
- return roles.map(role => this.recordingLinkType[role]);
- }
+ constructor() {
+ this.recordingLinkType = {
+ instrument: 148,
+ vocals: 149,
+ orchestra: 150,
+ conductor: 151,
+ chorusmaster: 152,
+ concertmaster: 760,
+ performer: 156,
+ work: 278,
+ place: 693,
+ area: 698,
+ };
+ this.instrumentType = {
+ instrument: 14,
+ strings: 69,
+ bass: 70,
+ double_bass: 71,
+ acoustic_bass_guitar: 73,
+ electric_bass_guitar: 74,
+ guitar_family: 75,
+ violin_family: 82,
+ cello: 84,
+ violin: 86,
+ membranophone: 125,
+ drums: 126,
+ harpsichord: 174,
+ piano: 180,
+ guitar: 229,
+ bass_guitar: 277,
+ string_quartet: 1067,
+ };
+ this.vocalType = {
+ vocal: 3,
+ lead: 4,
+ alto: 5,
+ bass: 7,
+ soprano: 10,
+ tenor: 11,
+ choir: 13,
+ };
+ this.workType = {
+ 'Song': 17,
+ 'Aria': 1,
+ 'Audio drama': 25,
+ 'Ballet': 2,
+ 'Beijing opera': 26,
+ 'Cantata': 3,
+ 'Concerto': 4,
+ 'Ătude': 20,
+ 'Incidental music': 30,
+ 'Madrigal': 7,
+ 'Mass': 8,
+ 'Motet': 9,
+ 'Musical': 29,
+ 'Opera': 10,
+ 'Operetta': 24,
+ 'Oratorio': 11,
+ 'Overture': 12,
+ 'Partita': 13,
+ 'Play': 28,
+ 'Poem': 21,
+ 'Prose': 23,
+ 'Quartet': 14,
+ 'Sonata': 5,
+ 'Song-cycle': 15,
+ 'Soundtrack': 22,
+ 'Suite': 6,
+ 'Symphonic poem': 18,
+ 'Symphony': 16,
+ 'Zarzuela': 19,
+ };
+ this.workLinkType = {
+ arrangement: 350,
+ composer: 168,
+ subwork: 281,
+ writer: 167,
+ };
+ this.releaseLinkTypeID = {
+ 44: 'instrument',
+ 60: 'vocals',
+ 45: 'orchestra',
+ 46: 'conductor',
+ 53: 'chorusmaster',
+ 51: 'performer',
+ };
+ this._performingRoles = [
+ 'instrument',
+ 'vocals',
+ 'orchestra',
+ 'conductor',
+ 'performer',
+ ];
+ this._minorPerformingRoles = [
+ 'concertmaster',
+ 'chorusmaster',
+ ];
+ this.aliasArtistType = {
+ 'Artist name': 1,
+ 'Legal name': 2,
+ 'Search hint': 3,
+ };
+ this.aliasInstrumentType = {
+ 'Instrument name': 1,
+ 'Search hint': 2,
+ 'Brand name': 3,
+ };
+ this.aliasType = {
+ 'Name': 1,
+ 'Search hint': 2,
+ };
+ this.attr = {
+ additional: 1,
+ strings: 69,
+ cello: 84,
+ violin: 86,
+ piano: 180,
+ guest: 194,
+ bowedStrings: 275,
+ string_quartet: 1067,
+ piano_trio: 1070,
+ string_trio: 1074,
+ cover: 567,
+ live: 578,
+ partial: 579,
+ instrumental: 580,
+ video: 582,
+ solo: 596,
+ medley: 750,
+ };
+ this.language = {
+ '[Multiple languages]': 284,
+ '[No lyrics]': 486,
+ 'Arabic': 18,
+ 'Chinese': 76,
+ 'Czech': 98,
+ 'Danish': 100,
+ 'Dutch': 113,
+ 'English': 120,
+ 'Finnish': 131,
+ 'French': 134,
+ 'German': 145,
+ 'Greek': 159,
+ 'Hungarian': 176,
+ 'Italian': 195,
+ 'Japanese': 198,
+ 'Korean': 224,
+ 'Norwegian': 309,
+ 'Occitan': 318,
+ 'Polish': 338,
+ 'Portuguese': 340,
+ 'Russian': 353,
+ 'Spanish': 393,
+ 'Swedish': 403,
+ 'Turkish': 433,
+ };
+ this.languageFromISO = {
+ ara: 'Arabic',
+ zho: 'Chinese',
+ ces: 'Czech',
+ dan: 'Danish',
+ nld: 'Dutch',
+ eng: 'English',
+ fin: 'Finnish',
+ fra: 'French',
+ deu: 'German',
+ ell: 'Greek',
+ hun: 'Hungarian',
+ ita: 'Italian',
+ jpn: 'Japanese',
+ kor: 'Korean',
+ mul: '[Multiple languages]',
+ nor: 'Norwegian',
+ oci: 'Occitan',
+ pol: 'Polish',
+ por: 'Portuguese',
+ rus: 'Russian',
+ spa: 'Spanish',
+ swe: 'Swedish',
+ tur: 'Turkish',
+ zxx: '[No lyrics]',
+ };
+ this.locale = {
+ 'Afrikaans': 'af',
+ 'Azerbaijani': 'az',
+ 'Albanian': 'sq',
+ 'Amharic': 'am',
+ 'Arabic': 'ar',
+ 'Armenian': 'hy',
+ 'Asturian': 'ast',
+ 'Bengali/Bangla': 'bn',
+ 'Basque': 'eu',
+ 'Belarusian': 'be',
+ 'Bosnian': 'bs',
+ 'Breton': 'br',
+ 'Bulgarian': 'bg',
+ 'Burmese': 'my',
+ 'Cantonese': 'yue',
+ 'Catalan': 'ca',
+ 'Central Kurdish/Sorani': 'ckb',
+ 'Chinese': 'zh',
+ 'Chinese Hong Kong SAR China Traditional': 'zh_Hant_HK',
+ 'Chinese Simplified': 'zh_Hans',
+ 'Chinese Singapore Simplified': 'zh_Hans_SG',
+ 'Chinese Taiwan Traditional': 'zh_Hant_TW',
+ 'Chinese Traditional': 'zh_Hant',
+ 'Croatian': 'hr',
+ 'Czech': 'cs',
+ 'Danish': 'da',
+ 'Dutch': 'nl',
+ 'English': 'en',
+ 'Esperanto': 'eo',
+ 'Estonian': 'et',
+ 'Faroese': 'fo',
+ 'Filipino': 'fil',
+ 'Finnish': 'fi',
+ 'French': 'fr',
+ 'Galician': 'gl',
+ 'Georgian': 'ka',
+ 'German': 'de',
+ 'Greek': 'el',
+ 'Gujarati': 'gu',
+ 'Hawaiâian': 'haw',
+ 'Hebrew': 'he',
+ 'Hindi': 'hi',
+ 'Hungarian': 'hu',
+ 'Icelandic': 'is',
+ 'Indonesian': 'id',
+ 'Interlingua': 'ia',
+ 'Irish': 'ga',
+ 'Italian': 'it',
+ 'Japanese': 'ja',
+ 'Javanese': 'jv',
+ 'Kannada': 'kn',
+ 'Kazakh': 'kk',
+ 'Khmer (Central)': 'km',
+ 'Korean': 'ko',
+ 'Kyrgyz': 'ky',
+ 'Latvian': 'lv',
+ 'Lithuanian': 'lt',
+ 'Low German': 'nds',
+ 'Luxembourgish': 'lb',
+ 'Macedonian': 'mk',
+ 'Malay': 'ms',
+ 'Malayam': 'ml',
+ 'MÄori': 'mi',
+ 'Marathi': 'mr',
+ 'Mongolian': 'mn',
+ 'Nepali': 'ne',
+ 'Norwegian BokmÄl': 'nb',
+ 'Norwegian Nynorsk': 'nn',
+ 'Occitan': 'oc',
+ 'Persian (Farsi)': 'fa',
+ 'Polish': 'pl',
+ 'Portuguese': 'pt',
+ 'Punjabi': 'pa',
+ 'Quechua': 'qu',
+ 'Romanian': 'ro',
+ 'Russian': 'ru',
+ 'Scottish Gaelic': 'gd',
+ 'Serbian': 'sr',
+ 'Serbo-Croatian': 'sh',
+ 'Sinhala': 'si',
+ 'Slovakian': 'sk',
+ 'Slovenian': 'sl',
+ 'Somali': 'so',
+ 'Spanish': 'es',
+ 'Swahili': 'sw',
+ 'Swedish': 'sv',
+ 'Tamil': 'ta',
+ 'Tajik': 'tg',
+ 'Telugu': 'te',
+ 'Thai': 'th',
+ 'Turkish': 'tr',
+ 'Urdu': 'ur',
+ 'Ukrainian': 'uk',
+ 'Uyghur': 'ug',
+ 'Uzbek': 'uz',
+ 'Vietnamese': 'vi',
+ 'Welsh (Cymric)': 'cy',
+ 'Western Frisian': 'fy',
+ 'Yiddish': 'yi',
+ 'Yoruba': 'yo',
+ 'Zulu': 'zu',
+ };
+ this.workKeyAttr = {
+ 'C major': 2,
+ 'C minor': 3,
+ 'C-sharp major': 4,
+ 'C-sharp minor': 5,
+ 'D-flat major': 6,
+ 'D-flat minor': 7,
+ 'D major': 8,
+ 'D minor': 9,
+ 'D-sharp minor': 10,
+ 'E-flat major': 11,
+ 'E-flat minor': 12,
+ 'E major': 13,
+ 'E minor': 14,
+ 'E-sharp minor': 15,
+ 'F-flat major': 16,
+ 'F major': 17,
+ 'F minor': 18,
+ 'F-sharp major': 19,
+ 'F-sharp minor': 20,
+ 'G-flat major': 21,
+ 'G major': 22,
+ 'G minor': 23,
+ 'G-sharp major': 24,
+ 'G-sharp minor': 25,
+ 'A-flat major': 26,
+ 'A-flat minor': 27,
+ 'A major': 28,
+ 'A minor': 29,
+ 'A-sharp minor': 30,
+ 'B-flat major': 31,
+ 'B-flat minor': 32,
+ 'B major': 33,
+ 'B minor': 34,
+ 'C Dorian': 789,
+ 'D Dorian': 790,
+ 'E Dorian': 791,
+ 'F Dorian': 792,
+ 'G Dorian': 793,
+ 'A Dorian': 794,
+ 'B Dorian': 795,
+ };
+ this.unknownArtistId = 97546;
+ // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
+ // we wait for 'server.timeout' milliseconds between two queries
+ this.timeout = 1000;
+ }
+
+ releaseToRecordingLink(linkTypeID) {
+ return this.recordingLinkType[this.releaseLinkTypeID[linkTypeID]];
+ }
+
+ performingLinkTypes(skipMinorRoles = false) {
+ const roles = skipMinorRoles ? this._performingRoles : [
+ ...this._performingRoles, ...this._minorPerformingRoles
+ ];
+ return roles.map(role => this.recordingLinkType[role]);
+ }
}
const server = new Server();
function buildOptions(obj) {
- // to be replaced by Object.entries when all supported browser versions
- // recognize it
- return Object.entries(obj)
- .map(([type, code]) => ``)
- .join('');
+ // to be replaced by Object.entries when all supported browser versions
+ // recognize it
+ return Object.entries(obj)
+ .map(([type, code]) => ``)
+ .join('');
}
function buildLanguageOptions(obj) {
- return Object.entries(obj)
- .map(
- ([type, code]) =>
- ``
- )
- .join('');
+ return Object.entries(obj)
+ .map(
+ ([type, code]) =>
+ ``
+ )
+ .join('');
}
function buildLocaleOptions(obj) {
- return Object.entries(obj)
- .map(
- ([type, code]) =>
- ``
- )
- .sort()
- .join('');
+ return Object.entries(obj)
+ .map(
+ ([type, code]) =>
+ ``
+ )
+ .sort()
+ .join('');
}
// eslint-disable-next-line no-unused-vars
const aliases = {
- artistType: `
-
- `,
- instrumentType: `
-
- `,
- type: `
-
- `,
- locale: `
-
- `,
+ artistType: `
+
+ `,
+ instrumentType: `
+
+ `,
+ type: `
+
+ `,
+ locale: `
+
+ `,
};
// eslint-disable-next-line no-unused-vars
const works = {
- type: `
-
- `,
-
- lang: `
-
- `,
-
- key: `
-
- `,
+ type: `
+
+ `,
+
+ lang: `
+
+ `,
+
+ key: `
+
+ `,
};
// eslint-disable-next-line no-unused-vars
const roles = {
- roles: `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- roleAttrs: `
-
-
- ${buildOptions(server.instrumentType)}
-
- ${buildOptions(server.vocalType)}
- `,
+ roles: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ roleAttrs: `
+
+
+ ${buildOptions(server.instrumentType)}
+
+ ${buildOptions(server.vocalType)}
+ `,
};
const requests = (function () {
- const self = {};
-
- self._request = function (verb, url, param, callback) {
- const xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4) {
- callback(xhr); // eslint-disable-line callback-return
- }
- };
- xhr.open(verb, url, true);
- if (verb === 'POST') {
- xhr.setRequestHeader(
- 'Content-Type',
- 'application/x-www-form-urlencoded'
- );
- }
- xhr.timeout = 10000;
- xhr.ontimeout = function () {
- console.error('The request for ' + url + ' timed out.');
- };
- xhr.send(param);
- };
-
- self.GET = function (url, callback) {
- self._request('GET', url, null, function (xhr) {
- if (xhr.status === 200 && xhr.responseText !== null) {
- callback(xhr.responseText); // eslint-disable-line callback-return
- } else {
- console.log('Error ', xhr.status, ': ', url);
- }
- });
+ const self = {};
+
+ self._request = function (verb, url, param, callback) {
+ const xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ callback(xhr); // eslint-disable-line callback-return
+ }
};
-
- self.POST = function (url, param, successCallback, failCallback) {
- self._request('POST', url, param, function (xhr) {
- if (xhr.status === 200 || xhr.status === 0) {
- successCallback(xhr);
- } else {
- failCallback(xhr);
- }
- });
+ xhr.open(verb, url, true);
+ if (verb === 'POST') {
+ xhr.setRequestHeader(
+ 'Content-Type',
+ 'application/x-www-form-urlencoded'
+ );
+ }
+ xhr.timeout = 10000;
+ xhr.ontimeout = function () {
+ console.error('The request for ' + url + ' timed out.');
};
- return self;
+ xhr.send(param);
+ };
+
+ self.GET = function (url, callback) {
+ self._request('GET', url, null, function (xhr) {
+ if (xhr.status === 200 && xhr.responseText !== null) {
+ callback(xhr.responseText); // eslint-disable-line callback-return
+ } else {
+ console.log('Error ', xhr.status, ': ', url);
+ }
+ });
+ };
+
+ self.POST = function (url, param, successCallback, failCallback) {
+ self._request('POST', url, param, function (xhr) {
+ if (xhr.status === 200 || xhr.status === 0) {
+ successCallback(xhr);
+ } else {
+ failCallback(xhr);
+ }
+ });
+ };
+ return self;
})();
class Edits {
- urlFromMbid(entityType, mbid) {
- return `/${entityType}/${encodeURIComponent(mbid)}/edit`;
- }
-
- /* in order to determine the edit parameters required by POST
- * we first load the /edit page and parse the JSON data
- * in the sourceData (before 2023) or source_entity block
- */
- getEditParams(url, callback) {
- requests.GET(url, resp => {
- let data;
- if (resp.includes('source_entity')) {
- data = new RegExp(/source_entity":(.*)},"user":/).exec(resp)[1];
- } else {
- // pre PR musicbrainz-server#2582
- data = new RegExp(/sourceData: (.*),\n/).exec(resp)[1];
- }
- callback(JSON.parse(data));
- });
- }
-
- getWorkEditParams(url, callback) {
- this.getEditParams(url, data => {
- const editData = {
- name: data.name,
- type_id: data.typeID,
- languages: data.languages.map(it => it.language.id),
- iswcs: data.iswcs.map(it => it.iswc),
- attributes: data.attributes.map(attr => ({
- type_id: attr.typeID,
- value: attr.value,
- })),
- };
- callback(editData);
- });
- }
-
- /* get edit POST parameters from a JSON API call
- * need to reconvert all values to internal ids */
- getWorkEditParamsFromJSON(url, callback) {
- fetch(url)
- .then(resp => resp.json())
- .then(data => {
- const editData = {
- name: data.title,
- type_id: server.workType[data.type],
- languages: data.languages.map(l => server.languageFromISO[l]),
- iswcs: data.iswcs,
- relations: data.relations,
- };
- editData.attributes = data.attributes.filter(
- attr => attr.type === 'Key'
- ).map(attr =>
- ({
- type_id: 1,
- value: server.workKeyAttr[attr.value],
- })
- );
- callback(editData);
- });
- }
-
- encodeName(name) {
- return encodeURIComponent(name).replace(/%20/g, '+');
- }
-
- prepareEdit(editData) {
- const data = {
- name: this.encodeName(editData.name),
- type_id: editData.type_id || ' ',
+ urlFromMbid(entityType, mbid) {
+ return `/${entityType}/${encodeURIComponent(mbid)}/edit`;
+ }
+
+ /* in order to determine the edit parameters required by POST
+ * we first load the /edit page and parse the JSON data
+ * in the sourceData (before 2023) or source_entity block
+ */
+ getEditParams(url, callback) {
+ requests.GET(url, resp => {
+ let data;
+ if (resp.includes('source_entity')) {
+ data = new RegExp(/source_entity":(.*)},"user":/).exec(resp)[1];
+ } else {
+ // pre PR musicbrainz-server#2582
+ data = new RegExp(/sourceData: (.*),\n/).exec(resp)[1];
+ }
+ callback(JSON.parse(data));
+ });
+ }
+
+ getWorkEditParams(url, callback) {
+ this.getEditParams(url, data => {
+ const editData = {
+ name: data.name,
+ type_id: data.typeID,
+ languages: data.languages.map(it => it.language.id),
+ iswcs: data.iswcs.map(it => it.iswc),
+ attributes: data.attributes.map(attr => ({
+ type_id: attr.typeID,
+ value: attr.value,
+ })),
+ };
+ callback(editData);
+ });
+ }
+
+ /* get edit POST parameters from a JSON API call
+ * need to reconvert all values to internal ids */
+ getWorkEditParamsFromJSON(url, callback) {
+ fetch(url)
+ .then(resp => resp.json())
+ .then(data => {
+ const editData = {
+ name: data.title,
+ type_id: server.workType[data.type],
+ languages: data.languages.map(l => server.languageFromISO[l]),
+ iswcs: data.iswcs,
+ relations: data.relations,
};
- editData.languages.forEach((lang, idx) => {
- // FIXME error message if unknown language, edit will fail
- data['languages.' + idx] = server.language[lang] ? server.language[lang] : lang;
- });
- if (editData.iswcs === undefined || !editData.iswcs.length) {
- data['iswcs.0'] = null;
- } else {
- editData.iswcs.forEach((iswc, idx) => {
- data['iswcs.' + idx] = iswc;
- });
- }
- // attributes (key)
- if (editData.attributes) {
- editData.attributes.forEach((attr, idx) => {
- data['attributes.' + idx + '.type_id'] = attr.type_id;
- data['attributes.' + idx + '.value'] = attr.value;
- });
- }
- return data;
- }
-
- formatEdit(editType, info) {
- return Object.entries(info)
- .map(([prop, val]) =>
- val === null
- ? `${editType}.${prop}`
- : `${editType}.${prop}=${val}`
- )
- .join('&');
- }
+ editData.attributes = data.attributes.filter(
+ attr => attr.type === 'Key'
+ ).map(attr =>
+ ({
+ type_id: 1,
+ value: server.workKeyAttr[attr.value],
+ })
+ );
+ callback(editData);
+ });
+ }
+
+ encodeName(name) {
+ return encodeURIComponent(name).replace(/%20/g, '+');
+ }
+
+ prepareEdit(editData) {
+ const data = {
+ name: this.encodeName(editData.name),
+ type_id: editData.type_id || ' ',
+ };
+ editData.languages.forEach((lang, idx) => {
+ // FIXME error message if unknown language, edit will fail
+ data['languages.' + idx] = server.language[lang] ? server.language[lang] : lang;
+ });
+ if (editData.iswcs === undefined || !editData.iswcs.length) {
+ data['iswcs.0'] = null;
+ } else {
+ editData.iswcs.forEach((iswc, idx) => {
+ data['iswcs.' + idx] = iswc;
+ });
+ }
+ // attributes (key)
+ if (editData.attributes) {
+ editData.attributes.forEach((attr, idx) => {
+ data['attributes.' + idx + '.type_id'] = attr.type_id;
+ data['attributes.' + idx + '.value'] = attr.value;
+ });
+ }
+ return data;
+ }
+
+ formatEdit(editType, info) {
+ return Object.entries(info)
+ .map(([prop, val]) =>
+ val === null
+ ? `${editType}.${prop}`
+ : `${editType}.${prop}=${val}`
+ )
+ .join('&');
+ }
}
// eslint-disable-next-line no-unused-vars
@@ -661,97 +661,97 @@ const edits = new Edits();
class Helper {
- comparefct(a, b) {
- // Sort function for performers in the recording artist list
- const link = server.recordingLinkType;
- const order = [
- link.vocals,
- link.instrument,
- link.orchestra,
- link.conductor,
- link.performer,
- ];
- if (a.link === b.link) {
- return 0;
- }
- return order.indexOf(a.link) > order.indexOf(b.link) ? 1 : -1;
- }
-
- mbidFromURL(url) {
- return (url || document.URL).split('/')[4].slice(0, 36);
- }
-
- wsUrl(entityType, options, mbid) {
- let url = `/ws/2/${entityType}/`;
- mbid = mbid || this.mbidFromURL();
- options = options || [];
- url += encodeURIComponent(mbid);
- url += '?fmt=json';
- options.forEach((option, idx) => {
- const prefix = idx === 0 ? '&inc=' : encodeURIComponent(' ');
- url += prefix + option;
- });
- return new URL(
- url,
- document.location.protocol + '//' + document.location.host
- );
- }
-
- _isEntityTypeURL(entityType) {
- return document.URL.split('/')[3] === entityType;
- }
-
- isArtistURL() {
- return this._isEntityTypeURL('artist');
- }
-
- isInstrumentURL() {
- return this._isEntityTypeURL('instrument');
- }
-
- isReleaseURL() {
- return this._isEntityTypeURL('release');
- }
-
- isWorkURL() {
- return this._isEntityTypeURL('work');
- }
-
- sortBy(key) {
- return (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
- }
-
- sortSubworks(work) {
- let rels = work.relationships;
- rels = rels.filter(
- rel =>
- rel.linkTypeID === server.workLinkType.subwork &&
- !rel.backward
- );
- rels = rels.sort(this.sortBy('linkOrder'));
- return rels.map(rel => rel.target);
- }
-
- isUserLoggedIn() {
- const isLoggedIn = !document.querySelector('a[href*="/register"]');
- if (!isLoggedIn) {
- console.debug('User is not logged in, exiting the script');
- }
- return isLoggedIn;
- }
-
- delay(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
-
- waitFor(pollingFunction, pollingInterval) {
- return new Promise(async (resolve) => { // eslint-disable-line no-async-promise-executor
- while (pollingFunction() === false) {
- await this.delay(pollingInterval);
- }
- resolve();
- });
- }
+ comparefct(a, b) {
+ // Sort function for performers in the recording artist list
+ const link = server.recordingLinkType;
+ const order = [
+ link.vocals,
+ link.instrument,
+ link.orchestra,
+ link.conductor,
+ link.performer,
+ ];
+ if (a.link === b.link) {
+ return 0;
+ }
+ return order.indexOf(a.link) > order.indexOf(b.link) ? 1 : -1;
+ }
+
+ mbidFromURL(url) {
+ return (url || document.URL).split('/')[4].slice(0, 36);
+ }
+
+ wsUrl(entityType, options, mbid) {
+ let url = `/ws/2/${entityType}/`;
+ mbid = mbid || this.mbidFromURL();
+ options = options || [];
+ url += encodeURIComponent(mbid);
+ url += '?fmt=json';
+ options.forEach((option, idx) => {
+ const prefix = idx === 0 ? '&inc=' : encodeURIComponent(' ');
+ url += prefix + option;
+ });
+ return new URL(
+ url,
+ document.location.protocol + '//' + document.location.host
+ );
+ }
+
+ _isEntityTypeURL(entityType) {
+ return document.URL.split('/')[3] === entityType;
+ }
+
+ isArtistURL() {
+ return this._isEntityTypeURL('artist');
+ }
+
+ isInstrumentURL() {
+ return this._isEntityTypeURL('instrument');
+ }
+
+ isReleaseURL() {
+ return this._isEntityTypeURL('release');
+ }
+
+ isWorkURL() {
+ return this._isEntityTypeURL('work');
+ }
+
+ sortBy(key) {
+ return (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
+ }
+
+ sortSubworks(work) {
+ let rels = work.relationships;
+ rels = rels.filter(
+ rel =>
+ rel.linkTypeID === server.workLinkType.subwork &&
+ !rel.backward
+ );
+ rels = rels.sort(this.sortBy('linkOrder'));
+ return rels.map(rel => rel.target);
+ }
+
+ isUserLoggedIn() {
+ const isLoggedIn = !document.querySelector('a[href*="/register"]');
+ if (!isLoggedIn) {
+ console.debug('User is not logged in, exiting the script');
+ }
+ return isLoggedIn;
+ }
+
+ delay(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+ }
+
+ waitFor(pollingFunction, pollingInterval) {
+ return new Promise(async (resolve) => { // eslint-disable-line no-async-promise-executor
+ while (pollingFunction() === false) {
+ await this.delay(pollingInterval);
+ }
+ resolve();
+ });
+ }
}
// eslint-disable-next-line no-unused-vars
@@ -759,29 +759,29 @@ const helper = new Helper();
class Sidebar {
- editNote(meta, msg) {
- const separator = '\n â\n';
- msg = msg ? '\n' + msg : '';
- const signature = `GM script: "${meta.name}" (${meta.version})\n`;
- return [msg, signature].join(separator);
- }
-
- container() {
- const container = document.getElementById('loujine-sidebar');
- if (container !== null) {
- return container;
- }
- document.querySelector('#sidebar h2.editing + ul.links').insertAdjacentHTML('afterend', `
-
- `);
- return document.getElementById('loujine-sidebar');
- }
+ editNote(meta, msg) {
+ const separator = '\n â\n';
+ msg = msg ? '\n' + msg : '';
+ const signature = `GM script: "${meta.name}" (${meta.version})\n`;
+ return [msg, signature].join(separator);
+ }
+
+ container() {
+ const container = document.getElementById('loujine-sidebar');
+ if (container !== null) {
+ return container;
+ }
+ document.querySelector('#sidebar h2.editing + ul.links').insertAdjacentHTML('afterend', `
+
+ `);
+ return document.getElementById('loujine-sidebar');
+ }
}
// eslint-disable-next-line no-unused-vars
@@ -790,110 +790,116 @@ const sidebar = new Sidebar();
class RelationshipEditor {
- constructor() {
- this.dispatchDefaults = {
- batchSelectionCount: null,
- creditsToChangeForSource: '',
- creditsToChangeForTarget: '',
- oldRelationshipState: null,
- };
- this.stateDefaults = {
- _lineage: [],
- _original: null,
- _status: 0,
- attributes: null,
- begin_date: null,
- editsPending: false,
- end_date: null,
- ended: false,
- entity0_credit: '',
- entity1_credit: '',
- id: null,
- linkOrder: 0,
- linkTypeID: null,
+ constructor() {
+ this.dispatchDefaults = {
+ batchSelectionCount: null,
+ creditsToChangeForSource: '',
+ creditsToChangeForTarget: '',
+ oldRelationshipState: null,
+ };
+ this.stateDefaults = {
+ _lineage: [],
+ _original: null,
+ _status: 0,
+ attributes: null,
+ begin_date: null,
+ editsPending: false,
+ end_date: null,
+ ended: false,
+ entity0_credit: '',
+ entity1_credit: '',
+ id: null,
+ linkOrder: 0,
+ linkTypeID: null,
+ };
+ }
+
+ // from https://github.com/kellnerd/musicbrainz-scripts/blob/main/utils/dom/react.js
+ setReactTextareaValue(input, value) {
+ const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
+ setter.call(input, value);
+ input.dispatchEvent(new Event('input', { bubbles: true }));
+ }
+
+ editNote(meta, msg) {
+ const node = document.getElementById('edit-note-text');
+ msg = msg ? '\n' + msg : '';
+ const separator = '\n â\n';
+ const signature = `GM script: "${meta.name}" (${meta.version})\n`;
+ let existingMsg = node.value;
+ let existingSign;
+ if (existingMsg.includes(separator)) {
+ [existingMsg, existingSign] = existingMsg.split(separator);
+ this.setReactTextareaValue(
+ node,
+ [existingMsg + msg, existingSign + signature].join(separator),
+ );
+ } else {
+ this.setReactTextareaValue(
+ node,
+ [existingMsg + msg, signature].join(separator),
+ );
+ }
+
+ }
+
+ container(node) {
+ const container = document.getElementById('loujine-menu');
+ if (container !== null) {
+ return container;
+ }
+ node.insertAdjacentHTML('afterend', `
+
+ `);
+ return document.getElementById('loujine-menu');
+ }
+
+ // from https://github.com/kellnerd/musicbrainz-scripts/blob/main/src/relationship-editor/createRelationship.js
+ createAttributeTree(attributes) {
+ return MB.tree.fromDistinctAscArray(attributes
+ .map((attribute) => {
+ const attributeType = MB.linkedEntities.link_attribute_type[attribute.type.gid];
+ return {
+ ...attribute,
+ type: attributeType,
+ typeID: attributeType.id,
};
- }
-
- // from https://github.com/kellnerd/musicbrainz-scripts/blob/main/utils/dom/react.js
- setReactTextareaValue(input, value) {
- const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
- setter.call(input, value);
- input.dispatchEvent(new Event('input', { bubbles: true }));
- }
-
- editNote(meta, msg) {
- const node = document.getElementById('edit-note-text');
- msg = msg ? '\n' + msg : '';
- const separator = '\n â\n';
- const signature = `GM script: "${meta.name}" (${meta.version})\n`;
- let existingMsg = node.value;
- let existingSign;
- if (existingMsg.includes(separator)) {
- [existingMsg, existingSign] = existingMsg.split(separator);
- this.setReactTextareaValue(
- node,
- [existingMsg + msg, existingSign + signature].join(separator),
- );
- } else {
- this.setReactTextareaValue(
- node,
- [existingMsg + msg, signature].join(separator),
- );
- }
-
- }
-
- container(node) {
- const container = document.getElementById('loujine-menu');
- if (container !== null) {
- return container;
- }
- node.insertAdjacentHTML('afterend', `
-
- `);
- return document.getElementById('loujine-menu');
- }
-
- // from https://github.com/kellnerd/musicbrainz-scripts/blob/main/src/relationship-editor/createRelationship.js
- createAttributeTree(attributes) {
- return MB.tree.fromDistinctAscArray(attributes
- .map((attribute) => {
- const attributeType = MB.linkedEntities.link_attribute_type[attribute.type.gid];
- return {
- ...attribute,
- type: attributeType,
- typeID: attributeType.id,
- };
- })
- );
- }
-
- orderedSelectedRecordings() {
- const recordings = MB.tree.toArray(MB.relationshipEditor.state.selectedRecordings);
- if (!recordings.length) {
- alert('No relation selected');
- }
-
- // sort recordings by order in tracklist to avoid having the dialog jump everywhere
- const recOrder = MB.getSourceEntityInstance().mediums.flatMap(
- m => m.tracks
- ).map(t => t.recording.id);
- recordings.sort((r1, r2) => recOrder.indexOf(r1.id) - recOrder.indexOf(r2.id));
- return recordings;
- }
-
- parseDate(dateProp) {
- if (dateProp === null) {
- return {year: '', month: '', day: ''};
- }
- return {year: dateProp.year ?? '', month: dateProp.month ?? '', day: dateProp.day ?? ''};
- }
+ })
+ );
+ }
+
+ orderedSelectedRecordings() {
+ const recordings = MB.tree.toArray(MB.relationshipEditor.state.selectedRecordings);
+ if (!recordings.length) {
+ alert('No relation selected');
+ }
+
+ // sort recordings by order in tracklist to avoid having the dialog jump everywhere
+ const recOrder = MB.getSourceEntityInstance().mediums.flatMap(
+ // tracks on mediums 1-10 loaded by default
+ m => m.tracks
+ ).concat(
+ // tracks on unfolded mediums
+ Array.from(MB.relationshipEditor.state.loadedTracks.keys()).sort().flatMap(
+ k => MB.relationshipEditor.state.loadedTracks.get(k)
+ )
+ ).map(t => t.recording.id);
+ recordings.sort((r1, r2) => recOrder.indexOf(r1.id) - recOrder.indexOf(r2.id));
+ return recordings;
+ }
+
+ parseDate(dateProp) {
+ if (dateProp === null) {
+ return {year: '', month: '', day: ''};
+ }
+ return {year: dateProp.year ?? '', month: dateProp.month ?? '', day: dateProp.day ?? ''};
+ }
}
// eslint-disable-next-line no-unused-vars