From 133030e88959baf5c61c29953607dc0cb078b29e Mon Sep 17 00:00:00 2001 From: loujine Date: Sun, 12 Mar 2023 20:49:44 +0000 Subject: [PATCH 1/3] Indentation --- mb-reledit-guess_works.user.js | 413 ++++---- mbz-loujine-common.js | 1636 ++++++++++++++++---------------- 2 files changed, 1024 insertions(+), 1025 deletions(-) diff --git a/mb-reledit-guess_works.user.js b/mb-reledit-guess_works.user.js index 97b9bc4..7958ea9 100644 --- a/mb-reledit-guess_works.user.js +++ b/mb-reledit-guess_works.user.js @@ -20,247 +20,246 @@ 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 mediumIdx = medium.position - 1; + const trackIdx = medium.tracks.filter(t => t.recording.id === recording.id)[0].position -1; - 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: MB.relationshipEditor.state.entity.mediums[mediumIdx].tracks[trackIdx], + }, + }); + 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 = $('input#mainWork'); + const match = $input.val().match(MBID_REGEX); + if (match) { + const mbid = match[0]; + requests.GET(`/ws/2/work/${mbid}?fmt=json`, (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 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; + } + }); + $('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; }); diff --git a/mbz-loujine-common.js b/mbz-loujine-common.js index 07c9ae9..f6654db 100644 --- a/mbz-loujine-common.js +++ b/mbz-loujine-common.js @@ -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', ` -
-

loujine GM tools

- documentation -
- `); - 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', ` +
+

loujine GM tools

+ documentation +
+ `); + return document.getElementById('loujine-sidebar'); + } } // eslint-disable-next-line no-unused-vars @@ -790,110 +790,110 @@ 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', ` +
+

loujine GM tools

+ documentation +
+ `); + 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', ` -
-

loujine GM tools

- documentation -
- `); - 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( + 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 ?? ''}; + } } // eslint-disable-next-line no-unused-vars From 5b9f725884acc41c1d77fe4902be7c47e4114632 Mon Sep 17 00:00:00 2001 From: loujine Date: Sun, 12 Mar 2023 20:52:28 +0000 Subject: [PATCH 2/3] [guess works] Fetch tracks from the correct MB object for folded mediums (close #80) --- mb-reledit-guess_works.user.js | 12 ++++++++---- mbz-loujine-common.js | 8 +++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mb-reledit-guess_works.user.js b/mb-reledit-guess_works.user.js index 7958ea9..08727dd 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 @@ -28,8 +28,12 @@ const repeatHelp = `Ways to associate subworks SW1, SW2, SW3... with selected tr 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 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({ @@ -38,7 +42,7 @@ const setWork = async (recording, work, partial) => { batchSelection: false, source: recording, targetType: 'work', - track: MB.relationshipEditor.state.entity.mediums[mediumIdx].tracks[trackIdx], + track: track, }, }); await helper.waitFor(() => !!MB.relationshipEditor.relationshipDialogDispatch, 1); diff --git a/mbz-loujine-common.js b/mbz-loujine-common.js index f6654db..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 @@ -882,7 +882,13 @@ class RelationshipEditor { // 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; From 877f38c36d56f6d8e55cdb3db44860863805f6aa Mon Sep 17 00:00:00 2001 From: loujine Date: Sun, 12 Mar 2023 21:04:14 +0000 Subject: [PATCH 3/3] [guess works] Replace jquery syntax (#82) --- mb-reledit-guess_works.user.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mb-reledit-guess_works.user.js b/mb-reledit-guess_works.user.js index 08727dd..80a8857 100644 --- a/mb-reledit-guess_works.user.js +++ b/mb-reledit-guess_works.user.js @@ -144,18 +144,18 @@ const guessWork = () => { }; const autoComplete = () => { - const $input = $('input#mainWork'); - const match = $input.val().match(MBID_REGEX); + 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.data('mbid', mbid); - $input.val(data.title || data.name); - $input.css('background', '#bbffbb'); + input.setAttribute('mbid', mbid); + input.value = data.title || data.name; + input.style.backgroundColor = '#bbffbb'; }); } else { - $input.css('background', '#ffaaaa'); + input.style.backgroundColor = '#ffaaaa'; } }; @@ -254,11 +254,11 @@ $(document).ready(function() { appliedNote = true; } }); - $('input#mainWork').on('input', autoComplete); - document.querySelector('input#fetchSubworks').addEventListener('click', () => { + document.getElementById('mainWork').addEventListener('input', autoComplete); + document.getElementById('fetchSubworks').addEventListener('click', () => { fetchSubWorks( - $('input#mainWork').data('mbid'), - document.querySelector('input#replaceSubworks').checked + document.getElementById('mainWork').getAttribute('mbid'), + document.getElementById('replaceSubworks').checked, ); if (!appliedNote) { relEditor.editNote(GM_info.script, 'Set guessed subworks');