Advertisement
clikengle

AO3 Floaty Text Box + Last Chapter Shortcut

Nov 20th, 2016 (edited)
74,349
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.90 KB | None | 1 0
  1. // ==UserScript==
  2. // @name AO3 Review + Last Chapter Shortcut + Kudos-sortable Bookmarks
  3. // @namespace saxamaphone
  4. // @version 2.5
  5. // @description Update: stops floaty review box from appearing in certain listing pages, removes last chapter
  6. // @author You
  7. // @match http://archiveofourown.org/*
  8. // @match https://archiveofourown.org/*
  9. // @match http://www.archiveofourown.org/*
  10. // @match https://www.archiveofourown.org/*
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. // Change here to pick what filetype you want the default download to be
  15. var sTypeWanted = 'epub';
  16.  
  17. var oTypeMapping = {
  18. 'mobi': 1,
  19. 'epub': 2,
  20. 'pdf': 3,
  21. 'html': 4
  22. };
  23.  
  24. // Change this to add the download links on the listing page. Starting June 23rd, 2022, it'll be turned off by default, AO3 limits how
  25. // many pageloads you do in a short while and if you do too many, you'll get the "Retry later" for a few minutes until you can use the site again
  26. // If you want it to be on, change this value to true
  27. var bListingShortcuts = false;
  28.  
  29. // From http://stackoverflow.com/a/1909997/584004
  30. (function (jQuery, undefined) {
  31. jQuery.fn.getCursorPosition = function() {
  32. var el = jQuery(this).get(0);
  33. var pos = 0;
  34. if('selectionStart' in el) {
  35. pos = el.selectionStart;
  36. } else if('selection' in document) {
  37. el.focus();
  38. var Sel = document.selection.createRange();
  39. var SelLength = document.selection.createRange().text.length;
  40. Sel.moveStart('character', -el.value.length);
  41. pos = Sel.text.length - SelLength;
  42. }
  43. return pos;
  44. };
  45. })(jQuery);
  46.  
  47. // From http://stackoverflow.com/a/841121/584004
  48. (function (jQuery, undefined) {
  49. jQuery.fn.selectRange = function(start, end) {
  50. if(end === undefined) {
  51. end = start;
  52. }
  53. return this.each(function() {
  54. if('selectionStart' in this) {
  55. this.selectionStart = start;
  56. this.selectionEnd = end;
  57. } else if(this.setSelectionRange) {
  58. this.setSelectionRange(start, end);
  59. } else if(this.createTextRange) {
  60. var range = this.createTextRange();
  61. range.collapse(true);
  62. range.moveEnd('character', end);
  63. range.moveStart('character', start);
  64. range.select();
  65. }
  66. });
  67. };
  68. })(jQuery);
  69.  
  70. // From http://stackoverflow.com/questions/11582512/how-to-get-url-parameters-with-javascript/11582513#11582513, modified to allow [] in params
  71. function getURLParameter(name) {
  72. return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search.replace(/\[/g, '%5B').replace(/\]/g, '%5D')) || [null, ''])[1].replace(/\+/g, '%20')) || null;
  73. }
  74.  
  75. function getStoryId()
  76. {
  77. var aMatch = window.location.pathname.match(/works\/(\d+)/);
  78. if(aMatch !== null)
  79. return aMatch[1];
  80. else
  81. return jQuery('#chapter_index li form').attr('action').match(/works\/(\d+)/)[1];
  82. }
  83.  
  84. function getBookmarks(sNextPath, aBookmarks, oDeferred) {
  85. jQuery.get(sNextPath, function(oData) {
  86. aBookmarks = jQuery.merge(aBookmarks, jQuery(oData).find('li.bookmark'));
  87. if(jQuery(oData).find('.next a').length)
  88. getBookmarks(jQuery(oData).find('.next').first().find('a').attr('href'), aBookmarks, oDeferred);
  89. else
  90. oDeferred.resolve();
  91. });
  92. }
  93.  
  94. jQuery(window).ready(function() {
  95. // Process bookmarks first because of extra sorting steps. Once this is done, handle everything else
  96. var oBookmarksProcessed = jQuery.Deferred();
  97.  
  98. // If on the bookmarks page, add option to sort by kudos
  99. if(window.location.pathname.indexOf('/bookmarks') != -1)
  100. {
  101. // Wait to handle the bookmarks after they're loaded
  102. var oBookmarksLoaded = jQuery.Deferred();
  103.  
  104. var bKudos = false, bComplete = false;
  105.  
  106. // Add options for Kudos sorting and Complete works only
  107. jQuery('#bookmark_search_sort_column').append('<option value="kudos_count">Kudos</option>');
  108. jQuery('#bookmark_search_with_notes').parent().after('<dt>Status</dt><dd><input id="work_search_complete" name="work_search[complete]" type="checkbox" value="1"/><label for="work_search_complete">Complete only</label></dd>');
  109.  
  110. if(getURLParameter('bookmark_search%5Bsort_column%5D') == 'kudos_count')
  111. {
  112. jQuery('#bookmark_search_sort_column').val('kudos_count');
  113. bKudos = true;
  114. }
  115.  
  116. if(getURLParameter('work_search%5Bcomplete%5D') == '1')
  117. {
  118. jQuery('#work_search_complete').attr('checked', 'checked');
  119. bComplete = true;
  120. }
  121.  
  122. // If either option has been selected, we perform our own process
  123. if(bKudos || bComplete)
  124. {
  125. // Get bookmarks, this takes at least a few seconds so we have to wait for that to finish
  126. var aBookmarks = [];
  127. getBookmarks(window.location.href.replace(/&page=\d+/, ''), aBookmarks, oBookmarksLoaded);
  128.  
  129. jQuery.when(oBookmarksLoaded).done(function () {
  130. if(bKudos)
  131. {
  132. aBookmarks.sort(function(oA, oB) {
  133. return (parseInt(jQuery(oB).find('dd.kudos').find('a').html()) || 0) - (parseInt(jQuery(oA).find('dd.kudos').find('a').html()) || 0);
  134. });
  135. }
  136.  
  137. if(bComplete)
  138. {
  139. jQuery.each(aBookmarks, function(iArrayIndex) {
  140. var sChapters = jQuery(this).find('dd.chapters').html();
  141. if(sChapters !== undefined)
  142. {
  143. var aChapters = sChapters.split('\/');
  144. if(aChapters[0] != aChapters[1])
  145. aBookmarks.splice(iArrayIndex, 1);
  146. }
  147. else if (jQuery(this).find('.stats').length === 0)
  148. aBookmarks.splice(iArrayIndex, 1);
  149. });
  150. }
  151.  
  152. var iPage = getURLParameter('page');
  153. if(iPage === null)
  154. iPage = 1;
  155.  
  156. jQuery('li.bookmark').remove();
  157.  
  158. var iIndex;
  159. var iNumBookmarks = aBookmarks.length;
  160. for(iIndex = (iPage-1) * 20; iIndex < (iPage*20) && iIndex < iNumBookmarks; iIndex++)
  161. {
  162. jQuery('ol.bookmark').append(aBookmarks[iIndex]);
  163. }
  164.  
  165. // If bookmarks are limited by Complete, change the number displayed
  166. if(bComplete)
  167. {
  168. var sPrevHeading = jQuery('h2.heading').html();
  169. jQuery('h2.heading').html(sPrevHeading.replace(/\d+ - \d+ of \d+/, (iPage-1)*20+1 + ' - ' + iIndex + ' of ' + aBookmarks.length));
  170.  
  171. // Repaginate if necessary
  172. var iFinalPage = jQuery('ol.pagination').first().find('li').not('.previous, .next').last().text();
  173. var iNewFinalPage = Math.ceil(iNumBookmarks/20);
  174. if(iFinalPage > iNewFinalPage)
  175. {
  176. // Rules for AO3 pagination are way too complicated for me to bother replicating, so just going to remove extra pages
  177. var aPageLinks = jQuery('ol.pagination').first().find('li');
  178. jQuery('ol.pagination').find('li a').each(function () {
  179. if(jQuery.isNumeric(jQuery(this).text()) && jQuery(this).text() > iNewFinalPage)
  180. jQuery(this).parent().remove();
  181. });
  182.  
  183. // Deactivate the last Next link if necessary
  184. if(iPage == iNewFinalPage)
  185. jQuery('ol.pagination').find('li.next').html('<li class="next" title="next"><span class="disabled">Next →</span></li>');
  186. }
  187. }
  188.  
  189. oBookmarksProcessed.resolve();
  190. });
  191. }
  192. else
  193. oBookmarksProcessed.resolve();
  194. }
  195. else
  196. oBookmarksProcessed.resolve();
  197.  
  198. jQuery.when(oBookmarksProcessed).done(function() {
  199. // Check if you're on a story or a list
  200. // If not a story page, presume an index page (tags, collections, author, bookmarks, series) and process each work individually
  201. if(bListingShortcuts && jQuery('.header h4.heading').length)
  202. {
  203. // Near as I can figure, the best way of identifying actual stories in an index page is with the h4 tag with class 'heading' within a list of type 'header'
  204. jQuery('.header h4.heading').each(function() {
  205. var sStoryPath = jQuery(this).find('a').first().attr('href');
  206. var oHeader = this;
  207.  
  208. // If link is from collections, get proper link
  209. var aMatch = sStoryPath.match(/works\/(\d+)/);
  210. if(aMatch !== null)
  211. {
  212. var iStoryId = aMatch[1];
  213. console.log('hi');
  214. // Access first chapter of story to get download links
  215. jQuery.get('https://archiveofourown.org/works/' + iStoryId, function(oData) {
  216. //console.log(oData);
  217. // No need to add the link to the last chapter as it's already part of AO3 functionality
  218. //var iLastChapterId = jQuery(oData).find('#selected_id option').last().val();
  219. //jQuery(oHeader).append(' <a href="/works/' + iStoryId + '/chapters/' + iLastChapterId +'" title="Jump to last chapter">»</a>');
  220.  
  221. // Use the chosen filetype from the beginning
  222. var sDownloadLink = jQuery(oData).find('.download ul li:nth-child(' + oTypeMapping[sTypeWanted] + ') a').attr('href');
  223. console.log('link: '+sDownloadLink);
  224. jQuery(oHeader).append(' <a href="' + sDownloadLink + '" title="Download work">↡</a>');
  225. }).fail(function() {
  226. console.log('failed');
  227. });
  228. }
  229. });
  230. }
  231. // Review box and last chapter buttons are story-specific
  232. else if(jQuery('ul.work').length && !jQuery('ul.index').length)
  233. {
  234. // HTML to define layout of popup box
  235. // Include x button to close box
  236. var sHtml = '<p class="close actions" id="close_floaty"><a aria-label="cancel" style="display: inline-block;">×</a></p>';
  237. // Button to insert highlighted text and for a help list
  238. sHtml += '<ul class="actions" style="float: left; margin-top: 10px;"><li id="insert_floaty_text"><a>Insert</a></li><li id="pop_up_review_tips"><a>Review Tips</a></li></ul>';
  239. // Textarea
  240. sHtml += '<textarea style="margin: 5px; width: 99%;" id="floaty_textarea"></textarea>';
  241.  
  242. // Create popup box
  243. jQuery("<div/>", {
  244. id: "reviewTextArea",
  245. width:600, // Change for dimensions
  246. height:300, // Change for dimensions
  247. css: {
  248. backgroundColor:"#ffffff",
  249. opacity: 0.75,
  250. border: "thin solid black",
  251. display: "inline-block",
  252. "padding-right": 10,
  253. position: "fixed",
  254. top: 150,
  255. right: 5
  256. },
  257. html: sHtml
  258. }).resizable().draggable().appendTo("body");
  259.  
  260. // Hide the popup box by default (comment out line below if you want it to always appear by adding // before it)
  261. jQuery('#reviewTextArea').hide();
  262.  
  263. // To close the box
  264. jQuery('#close_floaty').click(function() {
  265. jQuery('#reviewTextArea').hide();
  266. });
  267.  
  268. // Anything you type in the box gets inserted into the real comment box below
  269. jQuery('#floaty_textarea').on('input', function() {
  270. jQuery('.comment_form').val(jQuery('#floaty_textarea').val());
  271. });
  272.  
  273. // Add Float review box button to the top
  274. jQuery('ul.work').prepend('<li id="floaty_review_box"><a>Floaty Review Box</a></li>');
  275.  
  276. // If the above button is clicked, display the review box
  277. jQuery('#floaty_review_box').click(function() {
  278. jQuery('#reviewTextArea').show();
  279. });
  280.  
  281. // Insert highlighted/selected text into textarea when Insert button is clicked
  282. jQuery('#insert_floaty_text').click(function() {
  283. var sInitialText = jQuery('#floaty_textarea').val();
  284. var iPosition = jQuery('#floaty_textarea').getCursorPosition();
  285.  
  286. var sHighlightedText = window.getSelection().toString();
  287.  
  288. var sNewText = sInitialText.substr(0, iPosition) + '<i>"' + sHighlightedText + '"</i>\n' + sInitialText.substr(iPosition);
  289. jQuery('#floaty_textarea').val(sNewText);
  290. jQuery('#floaty_textarea').focus();
  291. jQuery('#floaty_textarea').selectRange(iPosition+sHighlightedText.length+10);
  292.  
  293. // Copy into real comment box
  294. jQuery('.comment_form').val(jQuery('#floaty_textarea').val());
  295. });
  296.  
  297. // Create the review tips box
  298. sReviewTipsHtml = '<p class="close actions" id="close_review_tips"><a aria-label="cancel" style="display: inline-block;">×</a></p>' +
  299. 'Writers will love any love you give them. If you&#39;re looking for things to help jumpstart a review, there are lots of different things you could focus on.<br />' +
  300. '<ul><li>Quotes you liked</li><li>Scenes you liked</li><li>What&#39;s your feeling at the end of the chapter (did it move you?)</li><li>What are you most looking forward to next?</li>' +
  301. '<li>Do you have any predictions for the next chapters you want to share?</li><li>Did this chapter give you any questions you can&#39;t wait to find out the answers for?</li>' +
  302. '<li>How would you describe the fic to a friend if you were recommending it?</li><li>Is there something unique about the story that you like?</li><li>Does the author have a style that really works for you?</li>' +
  303. '<li>Did the author leave any comments in the notes that said what they wanted feedback on?</li>' +
  304. '<li>Even if all you have are &quot;incoherent screams of delight&quot;, and can&#39;t come up with a real comment at the moment, authors love to hear that as well</li></ul>';
  305. jQuery("<div/>", {
  306. id: "reviewTips",
  307. width:600, // Change for dimensions
  308. height:300, // Change for dimensions
  309. css: {
  310. backgroundColor:"#ffffff",
  311. border: "thin solid black",
  312. 'font-size': '80%',
  313. padding: '10px 10px 0 10px',
  314. position: "fixed",
  315. top: 150,
  316. right: 620
  317. },
  318. html: sReviewTipsHtml
  319. }).resizable().draggable().appendTo("body");
  320. jQuery('#reviewTips li').css('list-style', 'circle inside none');
  321. jQuery('#reviewTips').hide();
  322.  
  323. // Pop up list of review tips
  324. jQuery('#pop_up_review_tips').click(function() {
  325. jQuery('#reviewTips').show();
  326. });
  327.  
  328. jQuery('#close_review_tips').click(function() {
  329. jQuery('#reviewTips').hide();
  330. });
  331.  
  332. // Before adding button for Last Chapter, make sure we're not on the last (or only) chapter already
  333. if(jQuery('.next').length)
  334. {
  335. // Add button for Last Chapter
  336. jQuery('ul.work').prepend('<li id="go_to_last_chap"><a>Last Chapter</a></li>');
  337.  
  338. // If the above button is clicked, go to last chapter
  339. jQuery('#go_to_last_chap').click(function() {
  340. window.location.href = '/works/' + getStoryId() + '/chapters/' + jQuery('#selected_id option').last().val();
  341. });
  342. }
  343.  
  344. // Adding a First Chapter button
  345. if(jQuery('.previous').length)
  346. {
  347. // Add button for First Chapter
  348. jQuery('ul.work').prepend('<li id="go_to_first_chap"><a>First Chapter</a></li>');
  349.  
  350. // If the above button is clicked, go to first chapter
  351. jQuery('#go_to_first_chap').click(function() {
  352. window.location.href = '/works/' + getStoryId();
  353. });
  354. }
  355. }
  356. });
  357. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement