// ==UserScript==
// @name Bangumi-History-Diff
// @namespace BHD
// @include /https?:\/\/(bgm|bangumi|chii)\.(tv|in)\/subject/\d+\/edit$/
// @include /https?:\/\/(bgm|bangumi|chii)\.(tv|in)\/subject/\d+\/edit_detail/diff\/\d+\.\.\.\d+/
// @version 0.0.6
// @grant none
// @require https://code.jquery.com/jquery-2.1.1.min.js
// @description compare two versions of subject history
// @downloadURL https://update.greasyfork.icu/scripts/11877/Bangumi-History-Diff.user.js
// @updateURL https://update.greasyfork.icu/scripts/11877/Bangumi-History-Diff.meta.js
// ==/UserScript==
var domain, func, uri, params = {};
var version1, version2;
//----------------------------------------
//---- Edit ------------------------------
//----------------------------------------
editController = function() {
$('
').insertBefore($('#pagehistory'));
$('#pagehistory li a').each(function() {
var hrefMatch = $(this).attr('href').match(/undo\/(\d+)/);
if(hrefMatch == null) return;
var version = hrefMatch[1];
$('| 加到左边对比 | 加到右边对比').insertAfter(this);
});
//binding events
$('input[name="diff-launch"]').click(function() {
window.location.href = '/subject/' + params.subject + '/edit_detail/diff/' + $('input[name="diff-left"]').val() + '...' + $('input[name="diff-right"').val();
});
$('.diff-add2left').click(function() {
$('input[name="diff-left"]').val($(this).attr('data-version'));
});
$('.diff-add2right').click(function() {
$('input[name="diff-right"]').val($(this).attr('data-version'));
});
}
//----------------------------------------
//---- Diff ------------------------------
//----------------------------------------
diffController = function() {
//Request the first version and load insert it into document.
$.get('/subject/' + params.subject + '/edit_detail/undo/' + params.ver1, function(data) {
$('body').html(data);
//Change the links in navigation bar.
$('.navSubTabs .focus').removeClass('focus');
$('.navSubTabs').append('对比');
//Get all infomations we need.
var info = {ver1: {}, ver2: {}};
//条目标题
info.ver1.title = $('input[name="subject_title"]').val();
//Infobox
info.ver1.infobox = $('#subject_infobox').val();
//简介
info.ver1.summary = $('#subject_summary').val();
//Clean workspace
$('#columnInSubjectA').html('' +
'' +
'' +
'
标题
版本 #' + params.ver1 + ' | 版本 #' + params.ver2 + ' |
---|
' +
'
Infobox
| 版本 #' + params.ver1 + ' | | 版本 #' + params.ver2 + ' |
---|
' +
'
简介
版本 #' + params.ver1 + ' | 版本 #' + params.ver2 + ' |
---|
' +
'
');
//Loading another version and then diff them.
$.get('/subject/' + params.subject + '/edit_detail/undo/' + params.ver2, function(data) {
//Remove the input box, and add the table element for diff view.
//[\S\s]* => See https://stackoverflow.com/questions/26929891/regex-to-match-a-multi-line-string
info.ver2.title = data.match(/subject_title" class="inputtext" type="text" value="(.+?)" \/>/)[1];
info.ver2.infobox = data.match(/subject_infobox"[^>]+>([\S\s]*?)<\/textarea>/m)[1];
info.ver2.summary = data.match(/subject_summary"[^>]+>([\S\s]*?)<\/textarea>/m)[1];
var infobox = {ver1: {}, ver2: {}};
//clean up
infobox.ver1 = info.ver1.infobox.replace(/\r/g, "");
infobox.ver2 = info.ver2.infobox.replace(/\r/g, "");
//Diff - Title
var titleCompare = inlineCompare(info.ver1.title, info.ver2.title);
$('#diff-title tbody').append('' + titleCompare.left + ' | ' + titleCompare.right + ' |
');
//Diff - Infobox
infobox.ver1 = infobox.ver1.split(/\n/);
infobox.ver2 = infobox.ver2.split(/\n/);
infoboxCompare = compare(infobox.ver1, infobox.ver2);
console.log(infoboxCompare);
var leftPointer = 1, rightPointer = 1;
for(i in infoboxCompare) {
switch(infoboxCompare[i].act) {
case 'match':
$('#diff-infobox tbody').append('' + (leftPointer++) + ' | ' + infoboxCompare[i].left.replace(/ /g, ' ') + ' | ' + (rightPointer++) + ' | ' + infoboxCompare[i].right.replace(/ /g, ' ') + ' |
');
break;
case 'added':
$('#diff-infobox tbody').append(' | | ' + (rightPointer++) + ' | ' + infoboxCompare[i].right.replace(/ /g, ' ') + ' |
');
break;
case 'deleted':
$('#diff-infobox tbody').append('' + (leftPointer++) + ' | ' + infoboxCompare[i].left.replace(/ /g, ' ') + ' | | |
');
break;
case 'modified':
var compareResult = inlineCompare(infoboxCompare[i].left, infoboxCompare[i].right);
$('#diff-infobox tbody').append('' + (leftPointer++) + ' | ' + compareResult.left + ' | ' + (rightPointer++) + ' | ' + compareResult.right + ' |
');
break;
}
}
//Diff - Summary
var summaryCompare = inlineCompare(info.ver1.summary, info.ver2.summary);
$('#diff-summary tbody').append('' + summaryCompare.left + ' | ' + summaryCompare.right + ' |
');
}); //$.get('/subj...ver2...
}); //$.get('/subj...ver1...
}
//----------------------------------------
//---- Functions -------------------------
//----------------------------------------
compare = function(left, right) {
var leftPointer = 0, rightPointer = 0;
var retval = [];
while(leftPointer < left.length) {
//May be they are the same...
if(left[leftPointer] == right[rightPointer]) {
retval.push({act: 'match', left: left[leftPointer], right: right[rightPointer]});
leftPointer++;
rightPointer++;
continue;
}
//May be they are the same but there are some spaces...
if(left[leftPointer].trim() == right[rightPointer].trim()) {
retval.push({act: 'modified', left: left[leftPointer], right: right[rightPointer]});
leftPointer++;
rightPointer++;
continue;
}
var matchShift = 0;
var leftShift = 0, rightShift = 0;
var leftTitleMatch = false, rightTitleMatch = false;
while((rightPointer + rightShift) < right.length) {
//for the line start with "|", we just compare their title("|title=", "|title =")
if((left[leftPointer].length > 0 && left[leftPointer][0] == '|') &&
(right[rightPointer + rightShift].length > 0 && right[rightPointer + rightShift][0] == '|')) {
var leftTitle = left[leftPointer].match(/\|(.+?)=/)[1];
var rightTitle = right[rightPointer + rightShift].match(/\|(.+?)=/)[1];
if(leftTitle == rightTitle) {
rightTitleMatch = true;
break;
}
} else if(left[leftPointer].trim() == right[rightPointer + rightShift].trim()) break;
rightShift++;
} //while rightShift...
matchShift = rightShift;
var leftShift = 0, rightShift = 0;
while((leftShift + leftPointer) < left.length) {
//for the line start with "|", we just compare their title("|title=", "|title =")
if((left[leftPointer + leftShift].length > 0 && left[leftPointer + leftShift][0] == '|') &&
(right[rightPointer].length > 0 && right[rightPointer][0] == '|')) {
var leftTitle = left[leftPointer + leftShift].match(/\|(.+?)=/)[1];
var rightTitle = right[rightPointer].match(/\|(.+?)=/)[1];
if(leftTitle == rightTitle) {
leftTitleMatch = true;
break;
}
} else if(left[leftPointer + leftShift].trim() == right[rightPointer].trim()) break;
leftShift++;
} //while leftShift...
rightShift = matchShift;
//Comparing...
//Modified
if(((leftPointer + leftShift) >= left.length - 1) && ((rightPointer + rightShift) >= right.length - 1)) {
retval.push({act: 'deleted', left: left[leftPointer]});
retval.push({act: 'added', right: right[rightPointer]});
leftPointer++;
rightPointer++;
continue;
}
//Delete
if(leftShift < rightShift) {
for(var i = 0; i < leftShift; i++) {
retval.push({act: 'deleted', left: left[leftPointer + i]});
}
retval.push({act: (left[leftPointer + leftShift] == right[rightPointer] ? 'match' : 'modified'), left: left[leftPointer + leftShift], right: right[rightPointer]});
leftPointer += leftShift + 1;
rightPointer++;
continue;
}
//Add
if(leftShift > rightShift) {
for(var i = 0; i < rightShift; i++) {
retval.push({act: 'added', right: right[rightPointer + i]});
}
retval.push({act: (left[leftPointer] == right[rightPointer + rightShift] ? 'match' : 'modified'), left: left[leftPointer], right: right[rightPointer + rightShift]});
leftPointer++;
rightPointer += rightShift + 1;
continue;
}
//Else... => the title of two lines are the same
retval.push({act: 'modified', left: left[leftPointer], right: right[rightPointer]});
leftPointer++;
rightPointer++;
continue;
} //while leftPointer...
return retval;
}
inlineCompare = function(left, right) {
var retval = {left: '', right: ''};
var leftPointer = 0, rightPointer = 0;
var modOpen = false;
var cleanSp = function(char) {
return (char == ' ') ? ' ' : char;
}
while(leftPointer < left.length) {
if(left[leftPointer] == right[rightPointer]) {
if(modOpen) { retval.left += ''; retval.right += ''; modOpen = false; }
retval.left += cleanSp(left[leftPointer]);
retval.right += cleanSp(right[rightPointer]);
leftPointer++;
rightPointer++;
continue;
}
if(rightPointer >= right.length) {
if(modOpen) { retval.left += ''; retval.right += ''; modOpen = false; }
retval.left += '';
for(var i = 0; leftPointer + i < left.length; i++) retval.left += cleanSp(left[leftPointer + i]);
retval.left += '';
break;
}
var leftShift = 0, rightShift = 0;
while(rightPointer + rightShift < right.length) {
if(left[leftPointer] == right[rightPointer + rightShift]) break;
rightShift++;
}
while(leftPointer + leftShift < left.length) {
if(left[leftPointer + leftShift] == right[rightPointer]) break;
leftShift++;
}
console.log(leftPointer, leftShift, rightPointer, rightShift);
//Modified
if((leftPointer + leftShift) >= left.length && (rightPointer + rightShift) >= right.length) {
if(!modOpen) {
retval.left += '';
retval.right += '';
}
retval.left += cleanSp(left[leftPointer]);
retval.right += cleanSp(right[rightPointer]);
modOpen = true;
leftPointer++;
rightPointer++;
continue;
}
//Add or Delete
if(rightShift < leftShift) {
if(modOpen) { retval.left += ''; retval.right += ''; modOpen = false; }
retval.right += '';
for(var i = 0; i < rightShift; i++) retval.right += cleanSp(right[rightPointer + i]);
retval.right += '';
rightPointer += rightShift;
continue;
} else {
if(modOpen) { retval.left += ''; retval.right += ''; modOpen = false; }
retval.left += '';
for(var i = 0; i < leftShift; i++) retval.left += cleanSp(left[leftPointer + i]);
retval.left += '';
leftPointer += leftShift;
continue;
}
}
if(rightPointer < right.length) {
if(modOpen) { retval.left += ''; retval.right += ''; modOpen = false; }
retval.right += '';
while(rightPointer < right.length) {
retval.right += cleanSp(right[rightPointer++]);
}
retval.right += '';
}
return retval;
}
//----------------------------------------
//---- Routing & Bootstrap ---------------
//----------------------------------------
$(function() {
var urlMatch = window.location.href.match(/\/\/(bgm|bangumi|chii).(tv|in)(\/.+)/);
uri = urlMatch[3];
//Domain
switch(urlMatch[1]) {
case 'bgm':
domain = 'bgm.tv';
break;
case 'bangumi':
domain = 'bangumi.tv';
break;
case 'chii':
domain = 'chii.in';
break;
}
//URI & Params
switch(true) {
case (urlMatch[3].search('diff') >= 0):
func = 'diff';
var matchParams = urlMatch[3].match(/\/subject\/(\d+)\/edit_detail\/diff\/(\d+)...(\d+)/);
params = {
subject: matchParams[1],
ver1: matchParams[2],
ver2: matchParams[3]
};
diffController();
break;
case (urlMatch[3].search('edit') >= 0):
func = 'edit';
var matchParams = urlMatch[3].match(/\/subject\/(\d+)\/edit/);
params = {
subject: matchParams[1]
};
editController();
break;
}
});