您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improvements to the "Combine works" button on combination pages
// ==UserScript== // @name LibraryThing better "Combine Works" button // @namespace https://greasyfork.org/en/users/11592-max-starkenburg // @description Improvements to the "Combine works" button on combination pages // @include http*://*librarything.tld/combine.php?* // @include http*://*librarything.com/combine.php?* // @version 3 // @grant none // ==/UserScript== // Some variables reused in multiple places var body = document.getElementsByTagName("body")[0]; var combineForm = document.getElementsByName("works")[0]; var noneSelected = "<li style='padding-left: 10px;'><i>No items currently selected</i></li>"; // Set some styling for various new features var head = document.getElementsByTagName("head")[0]; var style = document.createElement("style"); style.type = "text/css"; style.textContent = '\ .gm-frozen{ color: #888 !important; background-color: #f9f9f9 !important; transition: background-color 1s, color 1s; } \ .gm-frozen a, .alwaysblue .gm-frozen a { color: #888 !important; transition: color 1s; }\ #gm-new-buttons { position: fixed; right: 0; bottom: 0; margin: 0; width: 430px; padding: 10px 15px; background-color: #f7f7f7; border: solid #999; border-width: 1px 0 0 1px; border-radius: 3px; box-shadow: 0 0 10px #ddd; }\ #gm-selected-list { padding-left: 0; list-style-type: none; }\ #gm-selected-list li.gm-item{ max-width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; border: dotted #E7EE82; padding: 0 5px; border-width: 2px; margin-top: -2px; background-color: #F8FF93; }\ #gm-dim { position: fixed; top: 0; left: 0; z-index: 1999; opacity: .8; width: 100%; height: 100%; background-color: #999; display: none; }\ #gm-confirmation { border: 1px solid #999; background-color: white; box-shadow: 0 0 15px 0px #777; position: fixed; z-index: 2000; left: 50%; transition: top .4s; overflow: auto; }\ #gm-confirmation-contents { margin: 20px; }\ #gm-close-x { float: right; cursor: pointer; font-size: 1.3em; margin: 10px 15px 0 0; }\ form #gm-spinner { position: absolute; margin-left: 5px; }\ '; head.appendChild(style); // Clone the element containing the existing buttons var newButtons = combineForm.children[3].cloneNode(true); newButtons.id = "gm-new-buttons"; // Make some adjustments to the new "Combine" button var newCombine = newButtons.children[0]; newCombine.removeAttribute("onclick"); newCombine.type = "button"; // without this, it was submitting the form anyway even if howmanychecked returned < 2 newCombine.addEventListener("click", loadConfirmation, false); // Change "Reset" to "Clear selected", since I thought it seemed clearer (also, it's not a true reset, since it won't unfreeze "frozen" works) newButtons.children[1].value = "Clear selected"; // Have a dedicated (if redundant) button to refresh page, since it's not totally intuitive how to see updates on "frozen" works newRefresh = document.createElement("input"); newRefresh.name = "refresh"; newRefresh.type = "button"; newRefresh.value = "Get updates (refresh page)"; newRefresh.addEventListener("click",specialRefresh,false); newButtons.appendChild(newRefresh); // "Special" so that works get unselected (since they might not be quite the same post-combination), // but it's a "soft" refresh so you're not thrown all the way back up to the top again, losing your place, or at least in some browsers function specialRefresh(){ enableInputs(); document.getElementsByName("reset")[0].click(); // not using .reset() on form because there's a conflict with the existing button's name // without the timeout, it wasn't always completing the above lines setTimeout( function() { window.location.reload(false) }, 100); // false to make it a soft refresh } // Make a list and header for the currently selected items var selectedList = document.createElement("ul"); selectedList.id = "gm-selected-list"; selectedList.innerHTML = noneSelected; newButtons.insertBefore(selectedList,newButtons.firstChild); var selectedHeader = document.createElement("b"); selectedHeader.textContent = "Selected works "; newButtons.insertBefore(selectedHeader, selectedList); // Have a running count of how many works are currently selected var currentCount = document.createElement('span'); currentCount.textContent = "(0)"; newButtons.insertBefore(currentCount, selectedList); // Append the new buttons and list to the page (as descendant of form) combineForm.appendChild(newButtons); // Helper function to reenable the inputs that change their disabled status at certain points var allinputs = document.getElementsByTagName("input"); function enableInputs() { for (var i=0; i<allinputs.length; i++) { allinputs[i].disabled = false; } } // This function that populates the list when works are selected function showSelected() { // Make a count/total var currentlySelected = document.querySelectorAll('.combinetable input[type="checkbox"]:checked'); var currentLength = currentlySelected.length; var currentHTML = currentLength == 0 ? "(0)" : "<b>(" + currentLength + ")</b>"; currentCount.innerHTML = currentHTML; // Add the names of the currently selected works to the list var worksTitles = ""; for (var i=0; i<currentLength; i++) { var title = currentlySelected[i].parentElement.nextSibling.childNodes[0].childNodes[1].nodeValue; var title = title.replace(/'/g,"'"); // escape single quotes so title attr doesn't get truncated worksTitles += "<li class='gm-item' title='" + title + "'>" + title + "</li>"; } selectedList.innerHTML = currentLength == 0 ? noneSelected : worksTitles; } // Run this initially even on page load, since sometimes works are still selected or disabled after a soft reload showSelected(); enableInputs(); // Make the reset/clear buttons empty the new selected list display (without unfreezing any frozen works) var resets = document.querySelectorAll("input[type='reset']"); for (var i=0; i<resets.length; i++) { resets[i].removeAttribute("onclick"); resets[i].addEventListener("click", function() { // Alteration of resetcb() on page, to prevent unfreezing any currently frozen works on a form reset for (var j=1; j<numberofrows+1; j++) { // numberofrows is set in the existing html page var currentRow = document.getElementById("r"+j); if (currentRow.className != 'gm-frozen') currentRow.className = 'c_n'; } currentCount.textContent = "(0)"; selectedList.innerHTML = noneSelected; }); } // Make clicking any of the works' checkboxes populate the list in the fixed box workBoxes = document.getElementsByClassName("combinetable")[0].getElementsByTagName("tr"); for (k=0; k<workBoxes.length; k++) { workBoxes[k].addEventListener("click", showSelected); } // Create a pop-up like div that will get populated with the confirmation form var confirmation = document.createElement("div"); confirmation.id = "gm-confirmation"; // with an "x" to close, same as cancel, but perhaps more intuitive sometimes var closeX = document.createElement("span"); closeX.title = "close"; closeX.textContent = "×"; closeX.id = "gm-close-x"; confirmation.appendChild(closeX); closeX.addEventListener("click", hideConfirmation); // and with a child div for the sake of some small implementation details var confirmationContents = document.createElement("div"); confirmationContents.id = "gm-confirmation-contents"; confirmation.appendChild(confirmationContents); body.appendChild(confirmation); // Stuff to handle the sizing and modal qualities of the confirmation pop-up div var dim = document.createElement("div"); dim.id = "gm-dim"; body.appendChild(dim); var hideDistance = "-1000px"; function confirmationSize() { // expand it to most of the window so that it's the only focus at the moment var width = window.innerWidth - 200; var height = window.innerHeight - 150; confirmation.style.width = width + "px"; confirmation.style.marginLeft = "-" + (width / 2) + "px"; confirmation.style.height = height + "px"; hideDistance = "-" + (height + 50) + "px"; if (confirmation.style.top != "75px") { confirmation.style.top = hideDistance; } } confirmationSize(); window.addEventListener("resize", confirmationSize, false); // Helper function for adding a spinner image to give you the reassuring illusion that things are processing function addSpinner(appendToMe) { var spinner = document.createElement("img"); spinner.id = "gm-spinner"; spinner.src = "/pics/blog/spinner_mediumblack.gif"; appendToMe.appendChild(spinner); } // Pull down the div, stuff it with the contents of the confirmation page you'd get it you had just clicked old-style "Confirm" function loadConfirmation() { if (howmanychecked()) { // Make sure there are more than 2 checked dim.style.display = "block"; addSpinner(confirmationContents); confirmation.style.top = '75px'; // Though I originally added the timeout to avoid, a bug, I thought the delay also helped with not making things _too_ fast // (like after a while I could start to just click through without paying enough attention) setTimeout(function(){ var xhr = new XMLHttpRequest(); xhr.open('POST', 'work_combineworks.php', true); // go get the confirmation page's HTML xhr.onload = function () { // Stuff the confirmation page's HTML into the innerHTML of a new document // I tried a few other ways to convert the HTML into reusable DOM, but none were working out for me var newDoc = document.implementation.createHTMLDocument(); newDoc.documentElement.innerHTML = this.responseText; // Pull in the styling info from the <head> of the confirmation page's HTML. // Prepend an ID ancestor to its selectors to prevent conflicts with that styling vs. the current page's styling // Append this modified CSS to current page (unless that's already been done before) if (!document.contains(document.getElementById("gm-localized-style"))) { var externalStyles = newDoc.getElementsByTagName("style")[0].sheet.cssRules; // newDoc.styleSheets[0] is undefined in Chrome var localizedRules = [], prefixedRule; for (var i=0; i<externalStyles.length; i++) { prefixedRule = "#gm-confirmation " + externalStyles[i].cssText; localizedRules.push(prefixedRule); } var localizedStyle = document.createElement("style"); localizedStyle.id = "gm-localized-style"; localizedStyle.textContent = localizedRules.join(" "); head.appendChild(localizedStyle); } // Remove the spinner image confirmationContents.removeChild(confirmationContents.firstChild); // Inject the body of the confirmation page's HTML, minus header and footer, into the pop-up div var confirmCode = newDoc.getElementsByClassName("content")[0]; confirmationContents.appendChild(confirmCode); // Don't submit as normal, else you'd get redirected var confirmButton = confirmation.getElementsByTagName("input")[0]; confirmButton.type = "button"; confirmButton.removeAttribute("onclick"); confirmButton.addEventListener("click", submitConfirmation); // Change cancel button to hide the pop-up div instead of going back one page var cancelButton = confirmation.getElementsByTagName("input")[1]; cancelButton.removeAttribute("onclick"); cancelButton.addEventListener("click", hideConfirmation); }; xhr.send(new FormData(combineForm)); // send the appropriate input through with the POST }, 1500); } } // Submit the form in the background, and make the relevant works "frozen" to prevent possibility of "recombining" them, // or combining using a work number that's now redirected (not sure what would happen in that case, but I'd rather not even try) function submitConfirmation() { var confirmForm = document.getElementsByName("confirm")[0]; var xhr = new XMLHttpRequest(); xhr.open('POST', 'work_combineworks_submit.php', true); // submit the confirmation to LT xhr.onloadstart = function () { // disable the buttons after "Combine" has been clicked, to prevent multiple submissions, and since post-sumbission Cancel doesn't accomplish anything confirmInputs = confirmForm.getElementsByTagName("input"); for (var i=0; i<2; i++) { // Just disable the first two inputs (Confirm and Cancel buttons), not the hidden ones confirmInputs[i].disabled = true; } addSpinner(confirmForm.firstElementChild); // Originally was going to do the rest in onload or onloadend, but since that will wait for the responseText // which might be take a while for a popular author with lots of works, just create the illusion with a timeout setTimeout(function(){ // freeze the combined works var currentlySelected = document.querySelectorAll(".lit"); // querySelectorAll instead of getelementsbyclass becuase else it breaks after first iteration of resetting the classes for (var j=0; j<currentlySelected.length; j++) { var currentWork = currentlySelected[j]; var currentCheckbox = currentWork.getElementsByTagName("input")[0]; currentCheckbox.checked = false; currentCheckbox.disabled = true; currentWork.getElementsByTagName("td")[1].removeAttribute("onclick"); currentWork.className = 'gm-frozen'; } // reset the "currently selected" list: currentCount.textContent = "(0)"; selectedList.innerHTML = noneSelected; // slide the box bax up (amongst other things): hideConfirmation(); }, 1500); } xhr.send(new FormData(confirmForm)); // send the appropriate input through with the POST } // Reset the confirmation box's HTML, send it back up offscreen function hideConfirmation() { // Empty the contents (to avoid accidental clicks if :focus is somehow there, and so it's fresh each time it comes down) while (confirmationContents.firstChild) { // apparently more performant than setting innerHTML = "" confirmationContents.removeChild(confirmationContents.lastChild); } confirmation.style.top = hideDistance; dim.style.display = "none"; }