Bangumi-History-Diff

compare two versions of subject history

  1. // ==UserScript==
  2. // @name Bangumi-History-Diff
  3. // @namespace BHD
  4. // @include /https?:\/\/(bgm|bangumi|chii)\.(tv|in)\/subject/\d+\/edit$/
  5. // @include /https?:\/\/(bgm|bangumi|chii)\.(tv|in)\/subject/\d+\/edit_detail/diff\/\d+\.\.\.\d+/
  6. // @version 0.0.6
  7. // @grant none
  8. // @require https://code.jquery.com/jquery-2.1.1.min.js
  9. // @description compare two versions of subject history
  10. // ==/UserScript==
  11.  
  12. var domain, func, uri, params = {};
  13. var version1, version2;
  14.  
  15. //----------------------------------------
  16. //---- Edit ------------------------------
  17. //----------------------------------------
  18. editController = function() {
  19. $('<div id="diff-launcher"><input type="number" name="diff-left" class="inputtext" placeholder="左对比(通常是老版本)"><input type="number" name="diff-right" class="inputtext" placeholder="右对比(通常是新版本)"><input type="button" class="inputBtn" value="对比" name="diff-launch"></div>').insertBefore($('#pagehistory'));
  20.  
  21. $('#pagehistory li a').each(function() {
  22. var hrefMatch = $(this).attr('href').match(/undo\/(\d+)/);
  23. if(hrefMatch == null) return;
  24. var version = hrefMatch[1];
  25. $('<span>| <a href="#" class="l diff-add2left" data-version="' + version + '">加到左边对比</a> | <a href="#" class="l diff-add2right" data-version="' + version + '">加到右边对比</a></span>').insertAfter(this);
  26. });
  27. //binding events
  28. $('input[name="diff-launch"]').click(function() {
  29. window.location.href = '/subject/' + params.subject + '/edit_detail/diff/' + $('input[name="diff-left"]').val() + '...' + $('input[name="diff-right"').val();
  30. });
  31. $('.diff-add2left').click(function() {
  32. $('input[name="diff-left"]').val($(this).attr('data-version'));
  33. });
  34.  
  35. $('.diff-add2right').click(function() {
  36. $('input[name="diff-right"]').val($(this).attr('data-version'));
  37. });
  38. }
  39.  
  40. //----------------------------------------
  41. //---- Diff ------------------------------
  42. //----------------------------------------
  43.  
  44. diffController = function() {
  45. //Request the first version and load insert it into document.
  46. $.get('/subject/' + params.subject + '/edit_detail/undo/' + params.ver1, function(data) {
  47. $('body').html(data);
  48. //Change the links in navigation bar.
  49. $('.navSubTabs .focus').removeClass('focus');
  50. $('.navSubTabs').append('<li><a class="focus" href="#">对比</a></li>');
  51. //Get all infomations we need.
  52. var info = {ver1: {}, ver2: {}};
  53. //条目标题
  54. info.ver1.title = $('input[name="subject_title"]').val();
  55. //Infobox
  56. info.ver1.infobox = $('#subject_infobox').val();
  57. //简介
  58. info.ver1.summary = $('#subject_summary').val();
  59. //Clean workspace
  60. $('#columnInSubjectA').html('<h2>/ 正在对比版本 <a href="/subject/' + params.subject + '/edit_detail/undo/' + params.ver1 + '">' + params.ver1 + '</a> 与 <a href="/subject/' + params.subject + '/edit_detail/undo/' + params.ver2 + '">' + params.ver2 + '</a>' +
  61. ' <a class="chiiBtn" href="/subject/' + params.subject + '/edit_detail/diff/' + params.ver2 + '...' + params.ver1 + '"><span>交换方向</span></a></small></h2>' +
  62. '<div id="diff-workspace">' +
  63. '<style>#diff-workspace h1 { margin: 6px; padding: 3px; font-size: 18px; border-bottom: 1px solid #777; } .diff-added { background: #e9efe9; } .diff-deleted { background: #feebeb; } .diff-modified { background: #faffd9; } .diff-inline-add { background: rgba(0, 0, 0, .1); padding: 9px 0; } .diff-inline-delete { text-decoration: line-through; } </style>' +
  64. '<h1>标题</h1> <table id="diff-title" class="settings" width="100%" cellpadding="5"><thead><tr><th>版本 #' + params.ver1 + '</th><th>版本 #' + params.ver2 + '</th></tr></thead><tbody></tbody></table>' +
  65. '<h1>Infobox</h1> <table id="diff-infobox" class="settings" width="100%" cellpadding="5"><thead><tr><th></td><th>版本 #' + params.ver1 + '</th><th></th><th>版本 #' + params.ver2 + '</th></tr></thead><tbody></tbody></table>' +
  66. '<h1>简介</h1> <table id="diff-summary" class="settings" width="100%" cellpadding="5"><thead><tr><th>版本 #' + params.ver1 + '</th><th>版本 #' + params.ver2 + '</th></tr></thead><tbody></tbody></table>' +
  67. '</div>');
  68.  
  69. //Loading another version and then diff them.
  70. $.get('/subject/' + params.subject + '/edit_detail/undo/' + params.ver2, function(data) {
  71. //Remove the input box, and add the table element for diff view.
  72. //[\S\s]* => See https://stackoverflow.com/questions/26929891/regex-to-match-a-multi-line-string
  73. info.ver2.title = data.match(/subject_title" class="inputtext" type="text" value="(.+?)" \/>/)[1];
  74. info.ver2.infobox = data.match(/subject_infobox"[^>]+>([\S\s]*?)<\/textarea>/m)[1];
  75. info.ver2.summary = data.match(/subject_summary"[^>]+>([\S\s]*?)<\/textarea>/m)[1];
  76.  
  77. var infobox = {ver1: {}, ver2: {}};
  78. //clean up
  79. infobox.ver1 = info.ver1.infobox.replace(/\r/g, "");
  80. infobox.ver2 = info.ver2.infobox.replace(/\r/g, "");
  81. //Diff - Title
  82. var titleCompare = inlineCompare(info.ver1.title, info.ver2.title);
  83. $('#diff-title tbody').append('<tr><td>' + titleCompare.left + '</td><td>' + titleCompare.right + '</td></tr>');
  84.  
  85. //Diff - Infobox
  86. infobox.ver1 = infobox.ver1.split(/\n/);
  87. infobox.ver2 = infobox.ver2.split(/\n/);
  88.  
  89. infoboxCompare = compare(infobox.ver1, infobox.ver2);
  90. console.log(infoboxCompare);
  91.  
  92. var leftPointer = 1, rightPointer = 1;
  93. for(i in infoboxCompare) {
  94. switch(infoboxCompare[i].act) {
  95. case 'match':
  96. $('#diff-infobox tbody').append('<tr class="diff-match"><td>' + (leftPointer++) + '</td><td>' + infoboxCompare[i].left.replace(/ /g, '&nbsp;') + '</td><td>' + (rightPointer++) + '</td><td>' + infoboxCompare[i].right.replace(/ /g, '&nbsp;') + '</td></tr>');
  97. break;
  98. case 'added':
  99. $('#diff-infobox tbody').append('<tr><td></td><td></td><td>' + (rightPointer++) + '</td><td class="diff-added">' + infoboxCompare[i].right.replace(/ /g, '&nbsp;') + '</td></tr>');
  100. break;
  101. case 'deleted':
  102. $('#diff-infobox tbody').append('<tr><td>' + (leftPointer++) + '</td><td class="diff-deleted">' + infoboxCompare[i].left.replace(/ /g, '&nbsp;') + '</td><td></td><td></td></tr>');
  103. break;
  104. case 'modified':
  105. var compareResult = inlineCompare(infoboxCompare[i].left, infoboxCompare[i].right);
  106. $('#diff-infobox tbody').append('<tr><td>' + (leftPointer++) + '</td><td class="diff-deleted">' + compareResult.left + '</td><td>' + (rightPointer++) + '</td><td class="diff-added">' + compareResult.right + '</td></tr>');
  107. break;
  108. }
  109. }
  110. //Diff - Summary
  111. var summaryCompare = inlineCompare(info.ver1.summary, info.ver2.summary);
  112. $('#diff-summary tbody').append('<tr><td>' + summaryCompare.left + '</td><td>' + summaryCompare.right + '</td></tr>');
  113.  
  114. }); //$.get('/subj...ver2...
  115. }); //$.get('/subj...ver1...
  116. }
  117. //----------------------------------------
  118. //---- Functions -------------------------
  119. //----------------------------------------
  120. compare = function(left, right) {
  121. var leftPointer = 0, rightPointer = 0;
  122. var retval = [];
  123.  
  124. while(leftPointer < left.length) {
  125. //May be they are the same...
  126. if(left[leftPointer] == right[rightPointer]) {
  127. retval.push({act: 'match', left: left[leftPointer], right: right[rightPointer]});
  128. leftPointer++;
  129. rightPointer++;
  130. continue;
  131. }
  132. //May be they are the same but there are some spaces...
  133. if(left[leftPointer].trim() == right[rightPointer].trim()) {
  134. retval.push({act: 'modified', left: left[leftPointer], right: right[rightPointer]});
  135. leftPointer++;
  136. rightPointer++;
  137. continue;
  138. }
  139.  
  140. var matchShift = 0;
  141. var leftShift = 0, rightShift = 0;
  142. var leftTitleMatch = false, rightTitleMatch = false;
  143. while((rightPointer + rightShift) < right.length) {
  144. //for the line start with "|", we just compare their title("|title=", "|title =")
  145. if((left[leftPointer].length > 0 && left[leftPointer][0] == '|') &&
  146. (right[rightPointer + rightShift].length > 0 && right[rightPointer + rightShift][0] == '|')) {
  147. var leftTitle = left[leftPointer].match(/\|(.+?)=/)[1];
  148. var rightTitle = right[rightPointer + rightShift].match(/\|(.+?)=/)[1];
  149. if(leftTitle == rightTitle) {
  150. rightTitleMatch = true;
  151. break;
  152. }
  153. } else if(left[leftPointer].trim() == right[rightPointer + rightShift].trim()) break;
  154. rightShift++;
  155. } //while rightShift...
  156. matchShift = rightShift;
  157.  
  158. var leftShift = 0, rightShift = 0;
  159. while((leftShift + leftPointer) < left.length) {
  160. //for the line start with "|", we just compare their title("|title=", "|title =")
  161. if((left[leftPointer + leftShift].length > 0 && left[leftPointer + leftShift][0] == '|') &&
  162. (right[rightPointer].length > 0 && right[rightPointer][0] == '|')) {
  163. var leftTitle = left[leftPointer + leftShift].match(/\|(.+?)=/)[1];
  164. var rightTitle = right[rightPointer].match(/\|(.+?)=/)[1];
  165. if(leftTitle == rightTitle) {
  166. leftTitleMatch = true;
  167. break;
  168. }
  169. } else if(left[leftPointer + leftShift].trim() == right[rightPointer].trim()) break;
  170. leftShift++;
  171. } //while leftShift...
  172. rightShift = matchShift;
  173.  
  174. //Comparing...
  175. //Modified
  176. if(((leftPointer + leftShift) >= left.length - 1) && ((rightPointer + rightShift) >= right.length - 1)) {
  177. retval.push({act: 'deleted', left: left[leftPointer]});
  178. retval.push({act: 'added', right: right[rightPointer]});
  179. leftPointer++;
  180. rightPointer++;
  181. continue;
  182. }
  183. //Delete
  184. if(leftShift < rightShift) {
  185. for(var i = 0; i < leftShift; i++) {
  186. retval.push({act: 'deleted', left: left[leftPointer + i]});
  187. }
  188. retval.push({act: (left[leftPointer + leftShift] == right[rightPointer] ? 'match' : 'modified'), left: left[leftPointer + leftShift], right: right[rightPointer]});
  189. leftPointer += leftShift + 1;
  190. rightPointer++;
  191. continue;
  192. }
  193.  
  194. //Add
  195. if(leftShift > rightShift) {
  196. for(var i = 0; i < rightShift; i++) {
  197. retval.push({act: 'added', right: right[rightPointer + i]});
  198. }
  199. retval.push({act: (left[leftPointer] == right[rightPointer + rightShift] ? 'match' : 'modified'), left: left[leftPointer], right: right[rightPointer + rightShift]});
  200. leftPointer++;
  201. rightPointer += rightShift + 1;
  202. continue;
  203. }
  204. //Else... => the title of two lines are the same
  205. retval.push({act: 'modified', left: left[leftPointer], right: right[rightPointer]});
  206. leftPointer++;
  207. rightPointer++;
  208. continue;
  209. } //while leftPointer...
  210. return retval;
  211. }
  212.  
  213. inlineCompare = function(left, right) {
  214. var retval = {left: '', right: ''};
  215. var leftPointer = 0, rightPointer = 0;
  216. var modOpen = false;
  217. var cleanSp = function(char) {
  218. return (char == ' ') ? '&nbsp;' : char;
  219. }
  220.  
  221. while(leftPointer < left.length) {
  222. if(left[leftPointer] == right[rightPointer]) {
  223. if(modOpen) { retval.left += '</span>'; retval.right += '</span>'; modOpen = false; }
  224. retval.left += cleanSp(left[leftPointer]);
  225. retval.right += cleanSp(right[rightPointer]);
  226. leftPointer++;
  227. rightPointer++;
  228. continue;
  229. }
  230. if(rightPointer >= right.length) {
  231. if(modOpen) { retval.left += '</span>'; retval.right += '</span>'; modOpen = false; }
  232. retval.left += '<span class="diff-inline-delete">';
  233. for(var i = 0; leftPointer + i < left.length; i++) retval.left += cleanSp(left[leftPointer + i]);
  234. retval.left += '</span>';
  235. break;
  236. }
  237. var leftShift = 0, rightShift = 0;
  238. while(rightPointer + rightShift < right.length) {
  239. if(left[leftPointer] == right[rightPointer + rightShift]) break;
  240. rightShift++;
  241. }
  242.  
  243. while(leftPointer + leftShift < left.length) {
  244. if(left[leftPointer + leftShift] == right[rightPointer]) break;
  245. leftShift++;
  246. }
  247. console.log(leftPointer, leftShift, rightPointer, rightShift);
  248. //Modified
  249. if((leftPointer + leftShift) >= left.length && (rightPointer + rightShift) >= right.length) {
  250. if(!modOpen) {
  251. retval.left += '<span class="diff-inline-delete">';
  252. retval.right += '<span class="diff-inline-add">';
  253. }
  254. retval.left += cleanSp(left[leftPointer]);
  255. retval.right += cleanSp(right[rightPointer]);
  256. modOpen = true;
  257. leftPointer++;
  258. rightPointer++;
  259. continue;
  260. }
  261. //Add or Delete
  262. if(rightShift < leftShift) {
  263. if(modOpen) { retval.left += '</span>'; retval.right += '</span>'; modOpen = false; }
  264. retval.right += '<span class="diff-inline-add">';
  265. for(var i = 0; i < rightShift; i++) retval.right += cleanSp(right[rightPointer + i]);
  266. retval.right += '</span>';
  267. rightPointer += rightShift;
  268. continue;
  269. } else {
  270. if(modOpen) { retval.left += '</span>'; retval.right += '</span>'; modOpen = false; }
  271. retval.left += '<span class="diff-inline-delete">';
  272. for(var i = 0; i < leftShift; i++) retval.left += cleanSp(left[leftPointer + i]);
  273. retval.left += '</span>';
  274. leftPointer += leftShift;
  275. continue;
  276. }
  277. }
  278. if(rightPointer < right.length) {
  279. if(modOpen) { retval.left += '</span>'; retval.right += '</span>'; modOpen = false; }
  280. retval.right += '<span class="diff-inline-add">';
  281. while(rightPointer < right.length) {
  282. retval.right += cleanSp(right[rightPointer++]);
  283. }
  284. retval.right += '</span>';
  285. }
  286.  
  287. return retval;
  288. }
  289.  
  290. //----------------------------------------
  291. //---- Routing & Bootstrap ---------------
  292. //----------------------------------------
  293.  
  294. $(function() {
  295. var urlMatch = window.location.href.match(/\/\/(bgm|bangumi|chii).(tv|in)(\/.+)/);
  296. uri = urlMatch[3];
  297.  
  298. //Domain
  299. switch(urlMatch[1]) {
  300. case 'bgm':
  301. domain = 'bgm.tv';
  302. break;
  303. case 'bangumi':
  304. domain = 'bangumi.tv';
  305. break;
  306. case 'chii':
  307. domain = 'chii.in';
  308. break;
  309. }
  310. //URI & Params
  311. switch(true) {
  312. case (urlMatch[3].search('diff') >= 0):
  313. func = 'diff';
  314.  
  315. var matchParams = urlMatch[3].match(/\/subject\/(\d+)\/edit_detail\/diff\/(\d+)...(\d+)/);
  316. params = {
  317. subject: matchParams[1],
  318. ver1: matchParams[2],
  319. ver2: matchParams[3]
  320. };
  321. diffController();
  322. break;
  323. case (urlMatch[3].search('edit') >= 0):
  324. func = 'edit';
  325.  
  326. var matchParams = urlMatch[3].match(/\/subject\/(\d+)\/edit/);
  327. params = {
  328. subject: matchParams[1]
  329. };
  330. editController();
  331. break;
  332. }
  333. });