🏠 Home 

Bangumi-History-Diff

compare two versions of subject history


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