// ==UserScript== // @name MB: QoL: Paste multiple external links at once // @description Enables pasting multiple links, separated by whitespace, into the external link editor. // @version 2023.7.2 // @author ROpdebee // @license MIT; https://opensource.org/licenses/MIT // @namespace https://github.com/ROpdebee/mb-userscripts // @homepageURL https://github.com/ROpdebee/mb-userscripts // @supportURL https://github.com/ROpdebee/mb-userscripts/issues // @downloadURL https://raw.github.com/ROpdebee/mb-userscripts/dist/mb_multi_external_links.user.js // @updateURL https://raw.github.com/ROpdebee/mb-userscripts/dist/mb_multi_external_links.meta.js // @match *://*.musicbrainz.org/*/edit // @match *://*.musicbrainz.org/*/edit?* // @match *://*.musicbrainz.org/release/*/edit-relationships* // @match *://*.musicbrainz.org/*/add // @match *://*.musicbrainz.org/*/add?* // @match *://*.musicbrainz.org/*/create // @match *://*.musicbrainz.org/*/create?* // @match *://atisket.pulsewidth.org.uk/* // @match *://etc.marlonob.info/atisket/* // @run-at document-end // @grant none // ==/UserScript== // For original source code, see https://github.com/ROpdebee/mb-userscripts/tree/main/src/mb_multi_external_links (function () { 'use strict'; /* minified: babel helpers, nativejsx, babel-plugin-transform-async-to-promises */ function getDefaultExportFromCjs(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var appendChildren=function(t,e){(e=Array.isArray(e)?e:[e]).forEach((function(e){e instanceof HTMLElement?t.appendChild(e):(e||"string"==typeof e)&&t.appendChild(document.createTextNode(e.toString()));}));},appendChildren$1=getDefaultExportFromCjs(appendChildren),setAttributes=function(t,e){if("[object Object]"!==Object.prototype.toString.call(e)||"function"!=typeof e.constructor||"[object Object]"!==Object.prototype.toString.call(e.constructor.prototype)||!Object.prototype.hasOwnProperty.call(e.constructor.prototype,"isPrototypeOf"))throw new DOMException("Failed to execute 'setAttributes' on 'Element': "+Object.prototype.toString.call(e)+" is not a plain object.");for(var r in e)t.setAttribute(r,e[r]);};getDefaultExportFromCjs(setAttributes);var setStyles=function(t,e){for(var r in e)t.style[r]=e[r];},setStyles$1=getDefaultExportFromCjs(setStyles),_Pact=function(){function t(){}return t.prototype.then=function(e,r){var n=new t,o=this.s;if(o){var i=1&o?e:r;if(i){try{_settle(n,1,i(this.v));}catch(a){_settle(n,2,a);}return n}return this}return this.o=function(t){try{var o=t.v;1&t.s?_settle(n,1,e?e(o):o):r?_settle(n,1,r(o)):_settle(n,2,o);}catch(a){_settle(n,2,a);}},n},t}();function _settle(t,e,r){if(!t.s){if(r instanceof _Pact){if(!r.s)return void(r.o=_settle.bind(null,t,e));1&e&&(e=r.s),r=r.v;}if(r&&r.then)return void r.then(_settle.bind(null,t,e),_settle.bind(null,t,2));t.s=e,t.v=r;var n=t.o;n&&n(t);}}function _async(t){return function(){for(var e=[],r=0;rt.length)&&(e=t.length);for(var r=0,n=new Array(e);r=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,l=!1;return {s:function(){r=r.call(t);},n:function(){var t=r.next();return a=t.done,t},e:function(t){l=!0,i=t;},f:function(){try{a||null==r.return||r.return();}finally{if(l)throw i}}}}function _toPrimitive(t,e){if("object"!=typeof t||null===t)return t;var r=t[Symbol.toPrimitive];if(void 0!==r){var n=r.call(t,e||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return ("string"===e?String:Number)(t)}function _toPropertyKey(t){var e=_toPrimitive(t,"string");return "symbol"==typeof e?e:String(e)}!function(){function t(t){this._entry=t,this._pact=null,this._resolve=null,this._return=null,this._promise=null;}function e(t){return {value:t,done:!0}}function r(t){return {value:t,done:!1}}t.prototype._yield=function(t){return this._resolve(t&&t.then?t.then(r):r(t)),this._pact=new _Pact},t.prototype.next=function(t){var r=this;return r._promise=new Promise((function(n){var o=r._pact;if(null===o){var i=r._entry;if(null===i)return n(r._promise);function a(t){r._resolve(t&&t.then?t.then(e):e(t)),r._pact=null,r._resolve=null;}r._entry=null,r._resolve=n;var l=i(r);l&&l.then?l.then(a,(function(t){if(t===_earlyReturn)a(r._return);else {var e=new _Pact;r._resolve(e),r._pact=null,r._resolve=null,_resolve(e,2,t);}})):a(l);}else r._pact=null,r._resolve=n,_settle(o,1,t);}))},t.prototype.return=function(t){var r=this;return r._promise=new Promise((function(n){var o=r._pact;if(null===o)return null===r._entry?n(r._promise):(r._entry=null,n(t&&t.then?t.then(e):e(t)));r._return=t,r._resolve=n,r._pact=null,_settle(o,2,_earlyReturn);}))},t.prototype.throw=function(t){var e=this;return e._promise=new Promise((function(r,n){var o=e._pact;if(null===o)return null===e._entry?r(e._promise):(e._entry=null,n(t));e._resolve=r,e._pact=null,_settle(o,2,t);}))},t.prototype[_asyncIteratorSymbol]=function(){return this};}(); /* minified: lib */ class ConsoleSink{constructor(e){_defineProperty(this,"scriptName",void 0),_defineProperty(this,"onSuccess",this.onInfo.bind(this)),this.scriptName=e;}formatMessage(e){return "[".concat(this.scriptName,"] ").concat(e)}onDebug(e){console.debug(this.formatMessage(e));}onLog(e){console.log(this.formatMessage(e));}onInfo(e){console.info(this.formatMessage(e));}onWarn(e,t){e=this.formatMessage(e),t?console.warn(e,t):console.warn(e);}onError(e,t){e=this.formatMessage(e),t?console.error(e,t):console.error(e);}}var LogLevel=function(e){return e[e.DEBUG=0]="DEBUG",e[e.LOG=1]="LOG",e[e.INFO=2]="INFO",e[e.SUCCESS=3]="SUCCESS",e[e.WARNING=4]="WARNING",e[e.ERROR=5]="ERROR",e}({}),HANDLER_NAMES={[LogLevel.DEBUG]:"onDebug",[LogLevel.LOG]:"onLog",[LogLevel.INFO]:"onInfo",[LogLevel.SUCCESS]:"onSuccess",[LogLevel.WARNING]:"onWarn",[LogLevel.ERROR]:"onError"},DEFAULT_OPTIONS={logLevel:LogLevel.INFO,sinks:[]};class Logger{constructor(e){_defineProperty(this,"_configuration",void 0),this._configuration=_objectSpread2(_objectSpread2({},DEFAULT_OPTIONS),e);}fireHandlers(e,t,n){e{var o=r[HANDLER_NAMES[e]];o&&(n?o.call(r,t,n):o.call(r,t));}));}debug(e){this.fireHandlers(LogLevel.DEBUG,e);}log(e){this.fireHandlers(LogLevel.LOG,e);}info(e){this.fireHandlers(LogLevel.INFO,e);}success(e){this.fireHandlers(LogLevel.SUCCESS,e);}warn(e,t){this.fireHandlers(LogLevel.WARNING,e,t);}error(e,t){this.fireHandlers(LogLevel.ERROR,e,t);}configure(e){Object.assign(this._configuration,e);}get configuration(){return this._configuration}addSink(e){this._configuration.sinks.push(e);}}var LOGGER=new Logger,USERSCRIPT_ID="mb_multi_external_links";class AssertionError extends Error{}function assert(e,t){if(!e)throw new AssertionError(null!=t?t:"Assertion failed")}function assertNonNull(e,t){assert(null!==e,null!=t?t:"Assertion failed: Expected value to be non-null");}function assertHasValue(e,t){assert(null!=e,null!=t?t:"Assertion failed: Expected value to be defined and non-null");}function asyncSleep(e){return new Promise((t=>setTimeout(t,e)))}function retryTimes(e,t,n){var r=_async((function(t){return _catch(e,(function(e){if(t<=1)throw e;return asyncSleep(n).then((()=>r(t-1)))}))}));return t<=0?Promise.reject(new TypeError("Invalid number of retry times: ".concat(t))):r(t)}function logFailure(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"An error occurred";e.catch((e=>{LOGGER.error(t,e);}));}function createPersistentCheckbox(e,t,n){return [function(){var t=document.createElement("input");return t.setAttribute("type","checkbox"),t.setAttribute("id",e),t.addEventListener("change",(t=>{t.currentTarget.checked?localStorage.setItem(e,"delete_to_disable"):localStorage.removeItem(e),n(t);})),t.setAttribute("defaultChecked",!!localStorage.getItem(e)),t}.call(this),function(){var n=document.createElement("label");return n.setAttribute("for",e),appendChildren$1(n,t),n}.call(this)]}function qs(e,t){var n=qsMaybe(e,t);return assertNonNull(n,"Could not find required element"),n}function qsMaybe(e,t){return (null!=t?t:document).querySelector(e)}function qsa(e,t){return [...(null!=t?t:document).querySelectorAll(e)]}function onDocumentLoaded(e){"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e);}function onAddEntityDialogLoaded(e,t){null===qsMaybe(".content-loading",e.parentElement)?t():e.addEventListener("load",(()=>{t();}));}var inputValueDescriptor=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"value");function setInputValue(e,t){var n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];inputValueDescriptor.set.call(e,t),n&&e.dispatchEvent(new Event("input",{bubbles:!0}));}function insertStylesheet(e,t){if(void 0===t&&(t="ROpdebee_".concat(USERSCRIPT_ID,"_css")),null===qsMaybe("style#".concat(t))){var n=function(){var n=document.createElement("style");return n.setAttribute("id",t),appendChildren$1(n,e),n}.call(this);document.head.insertAdjacentElement("beforeend",n);}}function parseVersion(e){return e.split(".").map((e=>parseInt(e)))}function versionLessThan(e,t){for(var n=0;nt[n])return !1;n++;}return e.lengthversionLessThan(n,parseVersion(e.versionAdded))));0!==r.length&&showFeatureNotification(t.name,t.version,r.map((e=>e.description)));}else localStorage.setItem(LAST_DISPLAYED_KEY,t.version);}function showFeatureNotification(e,t,n){insertStylesheet(css_248z,"ROpdebee_Update_Banner");var r=function(){var o=document.createElement("div");o.setAttribute("class","banner warning-header");var a=document.createElement("p");o.appendChild(a),appendChildren$1(a,"".concat(e," was updated to v").concat(t,"! "));var s=document.createElement("a");s.setAttribute("href",CHANGELOG_URL),a.appendChild(s);var i=document.createTextNode("See full changelog here");s.appendChild(i),appendChildren$1(a,". New features since last update:");var l=document.createElement("div");l.setAttribute("class","ROpdebee_feature_list"),o.appendChild(l);var c=document.createElement("ul");l.appendChild(c),appendChildren$1(c,n.map((e=>function(){var t=document.createElement("li");return appendChildren$1(t,e),t}.call(this))));var u=document.createElement("button");return u.setAttribute("class","dismiss-banner remove-item icon"),u.setAttribute("data-banner-name","alert"),u.setAttribute("type","button"),u.addEventListener("click",(()=>{r.remove(),localStorage.setItem(LAST_DISPLAYED_KEY,GM.info.script.version);})),o.appendChild(u),o}.call(this);qs("#page").insertAdjacentElement("beforebegin",r);} LOGGER.configure({ logLevel: LogLevel.INFO, }); LOGGER.addSink(new ConsoleSink(USERSCRIPT_ID)); if (document.location.hostname === 'musicbrainz.org' || document.location.hostname.endsWith('.musicbrainz.org')) { onDocumentLoaded(maybeDisplayNewFeatures); } function getExternalLinksEditor(mbInstance) { var _ref, _mbInstance$releaseEd, _mbInstance$releaseEd2; var editor = (_ref = (_mbInstance$releaseEd = (_mbInstance$releaseEd2 = mbInstance.releaseEditor) === null || _mbInstance$releaseEd2 === void 0 ? void 0 : _mbInstance$releaseEd2.externalLinks.externalLinksEditorRef) !== null && _mbInstance$releaseEd !== void 0 ? _mbInstance$releaseEd : mbInstance.sourceExternalLinksEditor) === null || _ref === void 0 ? void 0 : _ref.current; assertHasValue(editor, 'Cannot find external links editor object'); return editor; } function getLastInput(editor) { var linkInputs = qsa('input.value', editor.tableRef.current); return linkInputs[linkInputs.length - 1]; } var initSplitter = _async(function (windowInstance) { var editorContainer = qsMaybe('#external-links-editor-container', windowInstance.document); if (!editorContainer) return; return _await(retryTimes(() => getExternalLinksEditor(windowInstance.MB), 100, 50), function (editor) { var splitter = new LinkSplitter(editor); var _createPersistentChec = createPersistentCheckbox('ROpdebee_multi_links_no_split', 'Don\'t split links', () => { splitter.toggle(); }), _createPersistentChec2 = _slicedToArray(_createPersistentChec, 2), checkboxElmt = _createPersistentChec2[0], labelElmt = _createPersistentChec2[1]; splitter.setEnabled(!checkboxElmt.checked); insertCheckboxElements(editor, checkboxElmt, labelElmt); }); }); var submitUrls = _async(function (editor, urls) { if (urls.length === 0) return; var lastInput = getLastInput(editor); LOGGER.debug('Submitting URL '.concat(urls[0])); setInputValue(lastInput, urls[0]); return _call(asyncSleep, function () { lastInput.dispatchEvent(new Event('focusout', { bubbles: true })); return _awaitIgnored(submitUrls(editor, urls.slice(1))); }); }); class LinkSplitter { constructor(editor) { var _this = this; _defineProperty(this, 'editor', void 0); _defineProperty(this, 'originalOnChange', void 0); _defineProperty(this, 'patchedOnChange', void 0); this.editor = editor; this.originalOnChange = editor.handleUrlChange.bind(editor); this.patchedOnChange = function () { var rawUrl = arguments.length <= 2 ? undefined : arguments[2]; LOGGER.debug('onchange received URLs '.concat(rawUrl)); var splitUrls = rawUrl.trim().split(/\s+/); if (splitUrls.length <= 1) { _this.originalOnChange(...arguments); return; } var lastUrl = splitUrls[splitUrls.length - 1]; var firstUrls = splitUrls.slice(0, -1); logFailure(submitUrls(editor, firstUrls).then(() => { var lastInput = getLastInput(_this.editor); LOGGER.debug('Submitting URL '.concat(lastUrl)); setInputValue(lastInput, lastUrl); lastInput.focus(); }), 'Something went wrong. onUrlBlur signature change?'); }; } enable() { LOGGER.debug('Enabling link splitter'); this.editor.handleUrlChange = this.patchedOnChange; } disable() { LOGGER.debug('Disabling link splitter'); this.editor.handleUrlChange = this.originalOnChange; } toggle() { this.setEnabled(this.editor.handleUrlChange === this.originalOnChange); } setEnabled(enabled) { if (enabled) { this.enable(); } else { this.disable(); } } } function insertCheckboxElements(editor, checkboxElmt, labelElmt) { var _lastInput$parentElem, _lastInput$parentElem2; editor.tableRef.current.after(checkboxElmt, labelElmt); var lastInput = getLastInput(editor); var marginLeft = lastInput.offsetLeft + ((_lastInput$parentElem = (_lastInput$parentElem2 = lastInput.parentElement) === null || _lastInput$parentElem2 === void 0 ? void 0 : _lastInput$parentElem2.offsetLeft) !== null && _lastInput$parentElem !== void 0 ? _lastInput$parentElem : 0); checkboxElmt.style.marginLeft = ''.concat(marginLeft, 'px'); } function onIframeAdded(iframe) { LOGGER.debug('Initialising on iframe '.concat(iframe.src)); onAddEntityDialogLoaded(iframe, () => { logFailure(initSplitter(iframe.contentWindow)); }); } function listenForIframes() { var iframeObserver = new MutationObserver(mutations => { var _iterator = _createForOfIteratorHelper(mutations.flatMap(mut => [...mut.addedNodes])), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var addedNode = _step.value; if (addedNode instanceof HTMLElement && addedNode.classList.contains('iframe-dialog')) { var iframe = qsMaybe('iframe', addedNode); if (iframe) { onIframeAdded(iframe); } } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } }); iframeObserver.observe(document, { subtree: true, childList: true }); } function attachLinkSplitter() { logFailure(initSplitter(window)); listenForIframes(); } var insertAtisketCopyButtons = (() => { function createButton(textGetter) { var btn = function () { var $$a = document.createElement('button'); $$a.setAttribute('type', 'button'); $$a.addEventListener('click', () => { logFailure(navigator.clipboard.writeText(textGetter())); btn.textContent = '(copied)'; setTimeout(() => btn.textContent = 'Copy', 1500); }); setStyles$1($$a, { marginLeft: '1ex' }); var $$b = document.createTextNode('Copy'); $$a.appendChild($$b); return $$a; }.call(this); return btn; } function getArtistLinks() { var urls = qsa('.prop.artists a:not(.mb)').map(a => a.href); return urls.join('\n'); } function getReleaseLinks() { var urls = qsa('section#mba-links a').map(a => a.href); return urls.join('\n'); } return () => { var _qsMaybe, _qsMaybe2; (_qsMaybe = qsMaybe('.prop.artists div')) === null || _qsMaybe === void 0 ? void 0 : _qsMaybe.prepend(createButton(getArtistLinks)); (_qsMaybe2 = qsMaybe('section#mba-links > h2')) === null || _qsMaybe2 === void 0 ? void 0 : _qsMaybe2.append(createButton(getReleaseLinks)); }; })(); if (document.location.hostname === 'musicbrainz.org' || document.location.hostname.endsWith('.musicbrainz.org')) { attachLinkSplitter(); } else if (document.location.hostname === 'atisket.pulsewidth.org.uk' || document.location.hostname === 'etc.marlonob.info') { insertAtisketCopyButtons(); } })();