A Chrome extension that gives you finer control over MyAnimeList.net scores
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 line
13 KiB

  1. /* -------------------------------- Globals -------------------------------- */
  2. var MAX_BUCKETS = 256;
  3. var LOADING_IMG = '<img src="http://cdn.myanimelist.net/images/xmlhttp-loader.gif" align="center">';
  4. var should_sort = window.location.href.indexOf("order=4") != -1;
  5. /* ------------------------ Miscellaneous functions ------------------------ */
  6. function get_anime_id_from_href(href) {
  7. var anime_id;
  8. if (href.indexOf("/anime/") != -1)
  9. anime_id = href.substr(href.indexOf("/anime/") + "/anime/".length);
  10. else
  11. anime_id = href.substr(href.indexOf("id=") + "id=".length);
  12. if (anime_id.indexOf("/") != -1)
  13. anime_id = anime_id.substr(0, anime_id.indexOf("/"));
  14. if (anime_id.indexOf("&") != -1)
  15. anime_id = anime_id.substr(0, anime_id.indexOf("&"));
  16. return anime_id;
  17. }
  18. function get_edit_id_from_href(href) {
  19. var anime_id = href.substr(href.indexOf("id=") + "id=".length);
  20. if (anime_id.indexOf("&") != -1)
  21. anime_id = anime_id.substr(0, anime_id.indexOf("&"));
  22. return anime_id;
  23. }
  24. function get_scores_from_element(elem) {
  25. var score_100 = parseInt(elem.val());
  26. var score_10 = Math.round(score_100 / 10.);
  27. if (isNaN(score_100) || score_100 < 1 || score_100 > 100) {
  28. alert("Invalid score: must be an integer between 1 and 100.");
  29. return null;
  30. }
  31. return [score_100, score_10];
  32. }
  33. /* --------------------------- Storage functions --------------------------- */
  34. function save_score(anime_id, score) {
  35. var bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  36. chrome.storage.sync.get(bucket_id, function(data) {
  37. var bucket = data[bucket_id];
  38. if (bucket === undefined)
  39. bucket = data[bucket_id] = {};
  40. bucket[anime_id] = score;
  41. chrome.storage.sync.set(data);
  42. });
  43. }
  44. function retrieve_scores(anime_id, callback) {
  45. var bucket_id = null;
  46. if (anime_id !== null)
  47. bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  48. chrome.storage.sync.get(bucket_id, function(data) {
  49. if (anime_id !== null) {
  50. var bucket = data[bucket_id];
  51. if (bucket !== undefined && bucket[anime_id] !== undefined)
  52. callback(bucket[anime_id]);
  53. else
  54. callback(null);
  55. }
  56. else
  57. callback(data);
  58. });
  59. }
  60. function remove_score(anime_id) {
  61. var bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  62. chrome.storage.sync.get(bucket_id, function(data) {
  63. var bucket = data[bucket_id];
  64. if (bucket === undefined || bucket[anime_id] === undefined)
  65. return;
  66. delete bucket[anime_id];
  67. if ($.isEmptyObject(bucket))
  68. chrome.storage.sync.remove(bucket_id);
  69. else
  70. chrome.storage.sync.set(data);
  71. });
  72. }
  73. /* ----------------------- Event patches/injections ------------------------ */
  74. function update_list_score(anime_id) {
  75. var new_scores = get_scores_from_element($("#scoretext" + anime_id));
  76. if (new_scores === null)
  77. return;
  78. var new_score_100 = new_scores[0], new_score_10 = new_scores[1];
  79. var payload = {id: anime_id, score: new_score_10};
  80. $("#scorebutton" + anime_id).prop("disabled", true);
  81. $.post("/includes/ajax.inc.php?t=63", payload, function(data) {
  82. $("#scoreval" + anime_id).text(new_score_100);
  83. $("#scoretext" + anime_id).val("");
  84. $("#scorediv" + anime_id).css("display", "none");
  85. $("#scorebutton" + anime_id).prop("disabled", false);
  86. if (should_sort)
  87. sort_list();
  88. });
  89. save_score(anime_id, new_score_100);
  90. }
  91. function update_anime_score(anime_id, is_new) {
  92. var new_scores = get_scores_from_element($("#myinfo_score"));
  93. if (new_scores === null)
  94. return;
  95. var new_score_100 = new_scores[0], new_score_10 = new_scores[1];
  96. var t_id, payload = {score: new_score_10};
  97. payload["status"] = $("#myinfo_status").val();
  98. payload["epsseen"] = $("#myinfo_watchedeps").val();
  99. if (is_new) {
  100. payload["aid"] = anime_id;
  101. t_id = "61";
  102. }
  103. else {
  104. payload["alistid"] = anime_id;
  105. payload["aid"] = $("#myinfo_anime_id").val();
  106. payload["astatus"] = $("#myinfo_curstatus").val();
  107. t_id = "62";
  108. }
  109. $("#myinfoDisplay").html(LOADING_IMG);
  110. $.post("/includes/ajax.inc.php?t=" + t_id, payload, function(data) {
  111. if (is_new) {
  112. document.getElementById("myinfoDisplay").innerHTML = "";
  113. document.getElementById("addtolist").innerHTML = data;
  114. }
  115. else
  116. document.getElementById("myinfoDisplay").innerHTML = data;
  117. });
  118. save_score(anime_id, new_score_100);
  119. }
  120. function submit_add_form(submit_button) {
  121. var anime_id = $("input[name='series_title']").val();
  122. if (!anime_id)
  123. return submit_button[0].click();
  124. var new_scores = get_scores_from_element($("#score_input"));
  125. if (new_scores === null)
  126. return;
  127. var new_score_100 = new_scores[0], new_score_10 = new_scores[1];
  128. $("select[name='score']").val(new_score_10);
  129. save_score(anime_id, new_score_100);
  130. submit_button[0].click();
  131. }
  132. function submit_edit_form(anime_id, submit_type, submit_button) {
  133. if (submit_type == 2) {
  134. var new_scores = get_scores_from_element($("#score_input"));
  135. if (new_scores === null)
  136. return;
  137. var new_score_100 = new_scores[0], new_score_10 = new_scores[1];
  138. $("select[name='score']").val(new_score_10);
  139. save_score(anime_id, new_score_100);
  140. }
  141. else if (submit_type == 3)
  142. remove_score(anime_id);
  143. submit_button[0].click();
  144. }
  145. /* -------------------------------- Sorting -------------------------------- */
  146. function compare(row1, row2) {
  147. var r1 = $(row1).find("span[id^='scoreval']").text(),
  148. r2 = $(row2).find("span[id^='scoreval']").text();
  149. if (r1 == "-" && r2 == "-")
  150. return 0;
  151. if (r1 == "-")
  152. return 1;
  153. if (r2 == "-")
  154. return -1;
  155. return r2 - r1;
  156. }
  157. function setup_sortable_list() {
  158. var headers = [".header_cw", ".header_completed", ".header_onhold",
  159. ".header_dropped", ".header_ptw"];
  160. $.each(headers, function(i, header) {
  161. $(header).next()
  162. .nextUntil($(".category_totals").closest("table"))
  163. .wrapAll('<div class="list-chart-group"/>');
  164. });
  165. $(".list-chart-group table").each(function(i, row) {
  166. $(row).add($(row).next())
  167. .wrapAll('<div class="list-chart-row"/>');
  168. });
  169. }
  170. function sort_list() {
  171. $(".list-chart-group").each(function(i, group) {
  172. $(group).find(".list-chart-row").sort(compare).each(function(i, row) {
  173. $(group).append(row);
  174. });
  175. $(group).find(".list-chart-row").each(function(i, row) {
  176. $(row).find("tr").first().children().first().text(i + 1);
  177. $(row).find((i % 2) ? ".td1" : ".td2").toggleClass("td1 td2");
  178. });
  179. });
  180. }
  181. /* ---------------------------- Extension hooks ---------------------------- */
  182. function hook_list() {
  183. retrieve_scores(null, function(data) {
  184. $("span[id^='scoreval']").each(function(i, elem) {
  185. var anime_id = elem.id.split("scoreval")[1];
  186. var bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  187. var bucket = data[bucket_id];
  188. if (bucket !== undefined && bucket[anime_id] !== undefined)
  189. $(elem).text(bucket[anime_id]);
  190. else {
  191. var current = parseInt($(elem).text());
  192. if (!isNaN(current))
  193. $(elem).text(current * 10);
  194. }
  195. $("#scorediv" + anime_id)
  196. .after($("<div>")
  197. .attr("id", "scorediv" + anime_id)
  198. .css("display", "none")
  199. .append($('<input>')
  200. .attr("type", "text")
  201. .attr("id", "scoretext" + anime_id)
  202. .attr("size", "2")
  203. .keydown(function(a_id) {
  204. return function(ev) {
  205. if ((window.event ? window.event.keyCode : ev.which) == 13)
  206. update_list_score(a_id);
  207. else
  208. return true;
  209. }
  210. }(anime_id)))
  211. .append($("<input>")
  212. .attr("type", "button")
  213. .attr("id", "scorebutton" + anime_id)
  214. .attr("value", "Go")
  215. .click(function(a_id) {
  216. return function() { return update_list_score(a_id); }
  217. }(anime_id))))
  218. .remove();
  219. });
  220. if (should_sort) {
  221. setup_sortable_list();
  222. sort_list();
  223. }
  224. });
  225. }
  226. function hook_anime(anime_id) {
  227. retrieve_scores(anime_id, function(score) {
  228. var old_input = $("#myinfo_score");
  229. var old_button = $("input[name='myinfo_submit']");
  230. var is_new = old_button.attr("value") == "Add";
  231. if (!is_new && score === null) {
  232. var old_score = parseInt(old_input.val());
  233. score = old_score == 0 ? "" : old_score * 10;
  234. }
  235. old_input.after($("<span> / 100</span>"))
  236. .after($("<input>")
  237. .attr("type", "text")
  238. .attr("id", "myinfo_score")
  239. .attr("name", "myinfo_score")
  240. .attr("class", "inputtext")
  241. .attr("value", (score === null) ? "" : score)
  242. .attr("size", "3"))
  243. .remove();
  244. old_button.after($("<input>")
  245. .attr("type", "button")
  246. .attr("name", "myinfo_submit")
  247. .attr("value", old_button.attr("value"))
  248. .attr("class", "inputButton")
  249. .click(function(a_id, is_new) {
  250. return function() { return update_anime_score(a_id, is_new); }
  251. }(anime_id, is_new)))
  252. .remove();
  253. });
  254. }
  255. function hook_add() {
  256. var old_input = $("select[name='score']");
  257. var old_submit = $("input[type='button'][onclick='checkValidSubmit(1)']");
  258. old_input.after($("<span> / 100</span>"))
  259. .after($("<input>")
  260. .attr("type", "text")
  261. .attr("id", "score_input")
  262. .attr("class", "inputtext")
  263. .attr("size", "3"))
  264. .hide();
  265. old_submit.after($("<input>")
  266. .attr("type", "button")
  267. .attr("class", "inputButton")
  268. .attr("style", old_submit.attr("style"))
  269. .attr("value", old_submit.attr("value"))
  270. .click(function(button) {
  271. return function() { return submit_add_form(button); }
  272. }(old_submit)))
  273. .hide();
  274. }
  275. function hook_edit(anime_id) {
  276. retrieve_scores(anime_id, function(score) {
  277. var old_input = $("select[name='score']");
  278. var old_edit = $("input[type='button'][onclick='checkValidSubmit(2)']");
  279. var old_delete = $("input[type='button'][onclick='checkValidSubmit(3)']");
  280. if (score === null) {
  281. var old_score = parseInt(old_input.val());
  282. score = old_score == 0 ? "" : old_score * 10;
  283. }
  284. old_input.after($("<span> / 100</span>"))
  285. .after($("<input>")
  286. .attr("type", "text")
  287. .attr("id", "score_input")
  288. .attr("class", "inputtext")
  289. .attr("value", score)
  290. .attr("size", "3"))
  291. .hide();
  292. old_edit.after($("<input>")
  293. .attr("type", "button")
  294. .attr("class", "inputButton")
  295. .attr("style", old_edit.attr("style"))
  296. .attr("value", old_edit.attr("value"))
  297. .click(function(a_id, button) {
  298. return function() { return submit_edit_form(a_id, 2, button); }
  299. }(anime_id, old_edit)))
  300. .hide();
  301. old_delete.after($("<input>")
  302. .attr("type", "button")
  303. .attr("class", "inputButton")
  304. .attr("value", old_delete.attr("value"))
  305. .click(function(a_id, button) {
  306. return function() { return submit_edit_form(a_id, 3, button); }
  307. }(anime_id, old_delete)))
  308. .hide();
  309. });
  310. }
  311. function hook_addtolist() {
  312. /* TODO: this entry point is unimplemented - it's rarely used and difficult
  313. to inject into, so I'm avoiding it for now. */
  314. $("<p><b>Note:</b> For the time being, anime added through this " +
  315. "interface cannot be given scores on the 100-point scale (the old " +
  316. "10-point system is used).</p><p>To give a more specific number, " +
  317. "simply add the anime here, then go to its own page or to your list " +
  318. "page, and update the score.</p>").insertAfter($("#stype").parent());
  319. }
  320. /* ------------------------------- Main hook ------------------------------- */
  321. $(document).ready(function() {
  322. var href = window.location.href;
  323. if (href.indexOf("/animelist/") != -1)
  324. hook_list();
  325. else if (href.indexOf("/anime/") != -1 || href.indexOf("/anime.php") != -1)
  326. hook_anime(get_anime_id_from_href(href));
  327. else if (href.indexOf("/panel.php") != -1 && href.indexOf("go=add") != -1)
  328. hook_add();
  329. else if (href.indexOf("/editlist.php") != -1 && href.indexOf("type=anime") != -1)
  330. hook_edit(get_edit_id_from_href(href));
  331. else if (href.indexOf("/addtolist.php") != -1)
  332. hook_addtolist();
  333. });