Advertisement
Guest User

script

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