Benutzer:Jah/histfilter.js
Erscheinungsbild
Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
#!/usr/bin/perl
use CGI qw(:standard);
use Time::HiRes qw(time);
use DB_File;
use Compress::Zlib;
use Digest::MD5 qw(md5 md5_base64);
$datadir = "data";
$lws = 5;
require "msg";
$requested_language = http('HTTP_ACCEPT_LANGUAGE');
if($requested_language =~ /^(\w+)/ && defined $Msg{$1}) {
%msg = %{$Msg{$1}};
} else {
%msg = %{$Msg{"en"}};
}
import_names;
$Q::actionSections = $msg{"select"} unless defined $Q::actionSections;
$Q::actionText = $msg{"select"} unless defined $Q::actionText;
$Q::actionReverts = $msg{"delete"} unless defined $Q::actionReverts;
$Q::revertingUser = $msg{"anyone"} unless defined $Q::revertingUser;
$Q::revertedUser = $msg{"anonymous or logged-in (50- edits)"} unless defined $Q::revertedUser;
$Q::actionMultipleEdits = $msg{"combine"} unless defined $Q::actionMultipleEdits;
$Q::actionAuthors = $msg{"select"} unless defined $Q::actionAuthors;
$Q::offset = 0 unless defined $Q::offset;
$Q::limit = 100 unless defined $Q::limit;
$langProject = $Q::lang . $Q::project;
print header(-charset => 'utf-8');
print start_html(
-style => "/histfilter.css",
-script => [
{ -language=>"JavaScript", -src=>"/histfilter.js" },
{ -language=>"JavaScript", -code=>
'msgTable="'.$msg{"table"}.'";' .
'msgForm="'.$msg{"form"}.'";'.
'tooltips = new Array();'
}
],
-title => "history filter"
);
print div({ -id=>"tooltipDiv" }, ""), "\n";
controlDiv();
showForm();
if(defined $Q::page) {
%var = defined($Var{$Q::lang})?%{$Var{$Q::lang}}:%{$Var{"en"}};
$Q::offset = 0 if $Q::limit==0;
$time0a = time;
$time0b = (times())[0];
$SIG{ALRM} = sub { print div({-id=>"tableDiv"}, b($msg{"timeout"}))."\n"; print end_html; exit; };
alarm 10;
loadHistory();
filterHistory();
alarm 0;
showHistory();
}
print end_html;
sub controlDiv {
my $control = "";
if(defined $Q::page) {
$control .= div({-id=>"compareDiv", -style=>"display:none" },
a({-id=>"compareLink"}, $msg{"compare"}), " – ") . " ";
my ($link, $linkText);
if($Q::filterSections && !$Q::reverseSections && length($Q::section)>0) {
$link = sectionLink($Q::section);
$linkText = "$Q::page#$Q::section";
} else {
$link = pageLink();
$linkText = $Q::page;
}
$linkText = substr($linkText, 0, 48)."..." if length($linkText)>50;
$control .= b($msg{"page history of"}, " ", a({ -id=>"pageLink", -href=>$link }, $linkText));
$control .= " – " . a({ -href=>"#form", -id=>"formTableLink", -onclick=>"toggleFormTable()" },
$msg{"form"});
$control .= div({ -id=>"statistics" }, " ");
if($Q::limit>0) {
$control .= " – " .
a({-id=>"backLink", -href=>"#back", -onclick=>"back()" }, "<<") .
div({-id=>"fromTo", -style=>"display:inline" }, " ") .
a({-id=>"forwardLink", -href=>"#forward" }, ">>")
}
} else {
$control .= b("History Filter")."\n";
}
print div({-id=>"controlDiv"}, $control) . "\n";
}
sub loadHistory {
my $page = $Q::page;
$page =~ s/ /_/g;
tie %idx, "DB_File", "$datadir/$langProject.idx", O_RDONLY;
($offset, $len, $cm, $posSeq, $nSeq) = split / /, $idx{$page};
untie %idx;
my $pid = open HIST, '-|';
if(!$pid) {
open REV, "$datadir/$langProject.rev";
seek REV, $offset, 0;
if($cm==0) {
read REV, $buf, $len;
print $buf;
} elsif($cm==1) {
($refinf, $status) = inflateInit(-WindowBits => 0 - MAX_WBITS);
for(my $bufsize = 16*1024; $len>0; $len -= $bufsize) {
read REV, $buf, $len<$bufsize?$len:$bufsize;
($xml, $status) = $refinf->inflate($buf);
print $xml;
}
}
close REV;
exit;
}
my $secMd50 = ""; my $diffId = -1;
while(<HIST>) {
$len += do { use bytes; length };
if($inRev) {
if(/^\s*<\/revision/) {
if(!$Q::filterSections || $Q::actionSections eq $msg{"mark"} || ($secMd5 ne $secMd50 &&
($length>0 || $secMd50 ne ""))) {
if($Q::filterSections && !$Q::reverseSections &&
$Q::actionSections eq $msg{"select"} && length($Q::section)>0) {
$comment =~ s/\/\* \Q$Q::section\E \*\///
}
$md5 = $secMd5 if $Q::filterSections && $Q::actionSections eq $msg{"select"};
push @$revs, {
revId => $revId,
diffId => $diffId,
user => $user,
timestamp => $timestamp,
comment => $comment,
length => $length,
lDiff => $length - (@$revs>0?$revs->[-1]->{"length"}:0),
md5 => $md5,
sgr => $sgr,
secNrs => $secNrs
};
if(($Q::actionSections eq $msg{"mark"}) && ($secMd5 ne $secMd50)) {
$revs->[-1]->{"mark"} = "section";
}
$secMd50 = $secMd5;
}
$comment = "";
$inRev = 0;
} elsif(/^\s*<timestamp>(.*?)<\/timestamp>/) {
$timestamp = $1;
} elsif(/\s*<id>(\d+)<\/id>/) {
if(!$inContributor) {
($revId, $diffId) = ($1, $revId);
}
} elsif(/^\s*<comment>(.*?)<\/comment>/) {
$comment = $1;
} elsif(/^\s*<contributor>/) {
$inContributor = 1;
} elsif(/^\s*<\/contributor/) {
$inContributor = 0;
} elsif(/^\s*<(username|ip)>(.*?)<\/\1>/) {
$user = $2;
} elsif(/^\s*<text type="sectionlist" length="(\d+)" md5="(.*?)">(.*?)<\/text>/) {
if(!$Q::filterSections) {
$secNrs = [ split(/ /, $3) ];
$length = $1;
$md5 = $2;
} else {
my $secNrs0 = [ split(/ /, $3) ];
my $length0 = $1;
$secNrs = [];
$md5 = $2;
$length = 0;
my @secMd5 = ();
for(my ($i, $level)=(0, 1000); $i<@$secNrs0; $i++) {
my $sec = $sgr->[$secNrs0->[$i]];
if($sec->{"title"} eq $Q::section ||
($Q::includeSubsections && $sec->{"level"}>$level)) {
if(!$Q::reverseSections) {
push @$secNrs, $secNrs0->[$i];
push @secMd5, $sec->{"md5"};
$length += $sec->{"length"};
}
$level = $sec->{"level"} if $sec->{"title"} eq $Q::section;
} else {
if($Q::reverseSections) {
push @$secNrs, $secNrs0->[$i];
push @secMd5, $sec->{"md5"};
$length += $sec->{"length"};
}
$level = 1000;
}
}
$secMd5 = md5_base64 join(" ", @secMd5);
if($Q::actionSections eq $msg{"mark"}) {
$length = $length0;
$secNrs = $secNrs0;
}
}
} elsif(/^\s*<text offset=\"(\d+?)\"(?: lengthGz=\"(\d+)\")? length=\"(\d+)\" md5=\"(.*?)\" \/>/) {
$length = $3;
$md5 = $4;
$secNrs = [];
}
} elsif($inSgr) {
if(/^\s*<section offset="(\d+)" length="(\d+)" md5="(.*?)" title="(\d+),(.*?)" \/>/) {
push @$sgr, {
offset => $1,
length => $2,
md5 => $3,
level => $4,
title => $5
};
} elsif(/^\s*<\/sectiongroup>/) {
$inSgr = 0;
}
} else {
if(/^\s*<revision/) {
$inRev = 1;
} elsif(/^\s*<sectiongroup offset="(\d+)" length="(\d+)">/) {
$inSgr = 1;
$sgr = [];
}
}
}
close HIST;
$lastRevId = $revId;
}
sub debugMsg {
my $msg = shift;
if(!defined $Debug) {
open DEBUG, ">/tmp/histfilter.debug";
$Debug = 1;
}
print DEBUG "$msg\n";
}
sub canRevert {
my $user = $_[0];
return 1 if $Q::revertingUser eq $msg{"anyone"};
return 1 if $Q::revertingUser eq $msg{"logged-in"} && $user !~ /\d+\.\d+\.\d+\.\d+/;
return 1 if $Q::revertingUser eq $msg{"logged-in (200+ edits)"} && $nEdits{$user}>=200;
0;
}
sub canBeReverted {
my $user = $_[0];
return 1 if $Q::revertedUser eq $msg{"anyone"};
return 1 if $Q::revertedUser eq $msg{"anonymous"} && $user =~ /\d+\.\d+\.\d+\.\d+/;
return 1 if $Q::revertedUser eq $msg{"anonymous or logged-in (50- edits)"} && $nEdits{$user}<=50;
0;
}
use Inline C => <<'END';
#include "unicodeAttributes.h"
void DJBHashes(unsigned char* sec, int lws) {
Inline_Stack_Vars;
Inline_Stack_Reset;
int wStart[lws], wEnd[lws]; // ring buffers: start and end(excl) of words
int pos=0, wNr=0;
int b1, b2, b3, b4, c, inWord=0, inCJK=0;
do {
int pos0 = pos;
b1=sec[pos++];
if (b1<128) {
c = b1;
} else if(b1<192) {
continue;
} else if(b1<224) {
b2 = sec[pos++];
c = ((b1&31)<<6) | (b2&63);
} else if(b1<240) {
b2 = sec[pos++];
b3 = sec[pos++];
c = ((b1&15)<<12) | ((b2&63)<<6) | (b3&63);
} else if(b1<248) {
b2 = sec[pos++];
b3 = sec[pos++];
b4 = sec[pos++];
c = ((b1&7)<<18) | ((b2&63)<<12) | ((b3&63)<<6) | (b4&63);
} else {
continue;
}
if(c>=17*65536)
continue;
unsigned char generalCategory = unicodeGC[c] & 0x7f;
unsigned char isCJK = unicodeGC[c] & 0x80;
int isAlNum =
generalCategory == unicode_Lu ||
generalCategory == unicode_Ll ||
generalCategory == unicode_Lt ||
generalCategory == unicode_Lm ||
generalCategory == unicode_Lo ||
generalCategory == unicode_Nd ||
generalCategory == unicode_Nl ||
generalCategory == unicode_No;
int newWord = 0;
if(inCJK) {
wEnd[wNr++%lws]=pos0;
inCJK=0;
newWord=1;
} else if(inWord && (!isAlNum || isCJK)) {
wEnd[wNr++%lws]=pos0;
inWord=0;
newWord=1;
}
if(newWord && wNr>=lws) {
unsigned int hash=5381;
int j;
for(j=wNr-lws; j<wNr; j++) {
int k;
for(k=wStart[j%lws]; k<wEnd[j%lws]; k++)
hash = 33*hash+sec[k];
if(j<wNr-1)
hash = 33*hash+' ';
}
Inline_Stack_Push(sv_2mortal(newSViv(hash)));
}
if(isCJK) {
wStart[wNr%lws] = pos0;
inCJK=1;
} else if(isAlNum && !inWord) {
wStart[wNr%lws] = pos0;
inWord=1;
}
} while(c!=0);
Inline_Stack_Done;
}
END
sub filterText {
my ($revs, $revs0) = ([], $_[0]);
$revs = $revs0 if $Q::actionText eq $msg{"mark"};
my $nRevs = @$revs0;
my $text = $Q::text;
map { $text{$_} = 1 } DJBHashes($text, $lws);
my $seqs;
open SEQ, "$datadir/$langProject.seq";
seek SEQ, $posSeq, 0;
read SEQ, $seqs, 12*$nSeq;
close SEQ;
for(my $i=0; $i<$nSeq; $i++) {
my ($hash, $first, $last) = unpack("N3", do { use bytes; substr($seqs, 12*$i, 12) });
if(defined $text{$hash}) {
$first{$first}++;
$last{$last}++;
}
}
my $rev0;
for(my $i=0; $i<$nRevs; $i++) {
my $rev = $revs0->[$i];
if(defined $first{$rev->{"revId"}} || ($i>0 && defined $last{$rev0->{"revId"}})) {
if($Q::actionText eq $msg{"select"}) {
push @$revs, $rev;
} else {
$rev->{"mark"} = "text";
}
$rev->{"textPlus"} = 0+$first{$rev->{"revId"}};
$rev->{"textMinus"} = 0+$last{$rev0->{"revId"}};
}
$rev0 = $rev;
}
$revs;
}
sub filterReverts {
my ($revs, $revs0) = ([], $_[0]);
my $nRevs = @$revs0;
for(my $revNr=0; $revNr<$nRevs; $revNr++) {
my $rev = $revs0->[$revNr];
if(defined $revNrByMd5{$rev->{"md5"}}) {
$firstOccurrence[$revNr] = $revNrByMd5{$rev->{"md5"}};
} else {
$revNrByMd5{$rev->{"md5"}} = $revNr;
$firstOccurrence[$revNr] = $revNr;
}
}
if($Q::revertingUser eq $msg{"logged-in (200+ edits)"} || $Q::revertedUser eq
$msg{"anonymous or logged-in (50- edits)"}) {
tie %nEdits, "DB_File", "$datadir/$langProject.nEdits", O_RDONLY;
}
for(my $revNr=$nRevs-1; $revNr>=0; $revNr--) {
my $rev = $revs0->[$revNr];
if($firstOccurrence[$revNr]!=$revNr && $revNr-$firstOccurrence[$revNr]<100 &&
canRevert($rev->{"user"})) {
my $revertOk = -1;
for(my $revNr2 = $revNr-1; $revNr2>=$firstOccurrence[$revNr]; $revNr2--) {
my $rev2 = $revs0->[$revNr2];
if($firstOccurrence[$revNr2]==$firstOccurrence[$revNr]) {
$revertOk = $revNr2;
} else {
last unless $rev2->{"user"} eq $rev->{"user"} ||
canBeReverted($rev2->{"user"});
}
}
if($revertOk>=0) {
if($Q::actionReverts eq $msg{"delete"}) {
$revNr = $revertOk+1;
} elsif($Q::actionReverts eq $msg{"mark"}) {
for(my $revNr2 = $revNr; $revNr2>$revertOk; $revNr2--) {
my $rev2 = $revs0->[$revNr2];
$rev2->{"mark"} = "revert";
push @$revs, $rev2;
}
$revNr = $revertOk+1;
}
} else {
push @$revs, $rev;
}
} else {
push @$revs, $rev;
}
}
untie %nEdits if defined %nEdits;
@$revs = reverse @$revs;
$revs;
}
sub filterMultipleEdits {
my ($revs, $revs0) = ([], $_[0]);
my $nRevs = @$revs0;
my ($rev0, $comments, $commentLength, $revsTmp);
for(my $i=0; $i<$nRevs; $i++) {
my $rev = $revs0->[$i];
if($i>0 && ($rev->{"user"} ne $rev0->{"user"} ||
($Q::multipleEditsCondition eq $msg{"if the comments are short"} && $commentLength>80))) {
$rev0->{"comment"} = join("; ", @$comments);
if($Q::actionMultipleEdits eq $msg{"combine"}) {
push @$revs, $rev0;
} else {
map { $_->{"mark"} = "multipleEdits" } @$revsTmp if @$revsTmp>1;
push @$revs, @$revsTmp;
$revsTmp = [];
}
$comments = [];
$commentLength = 0;
} elsif($i>0 && $Q::actionMultipleEdits eq $msg{"combine"}) {
$rev->{"diffId"} = $rev0->{"diffId"};
$rev->{"lDiff"} += $rev0->{"lDiff"};
if(defined $rev->{"textPlus"} && defined $rev0->{"textPlus"}) {
$rev->{"textPlus"} += $rev0->{"textPlus"};
}
if(defined $rev->{"textMinus"} && defined $rev0->{"textMinus"}) {
$rev->{"textMinus"} += $rev0->{"textMinus"};
}
$rev->{"mark"} = $rev0->{"mark"} unless defined $rev->{"mark"};
}
push @$revsTmp, $rev if $Q::actionMultipleEdits eq $msg{"mark"};
if($rev->{"comment"} ne "" && (@$comments==0 || $rev->{"comment"} ne $comments->[-1])) {
push @$comments, $rev->{"comment"};
$commentLength += length $rev->{"comment"};
}
$rev0 = $rev;
}
$rev0->{"comment"} = join("; ", @$comments);
push @$revs, $rev0;
$revs;
}
sub filterAuthors {
my ($revs, $revs0) = ([], $_[0]);
$revs = $revs0 if $Q::actionAuthors eq $msg{"mark"};
my $nRevs = @$revs0;
if($Q::authorGroup eq $msg{"logged-in (200+ edits)"} || $Q::authorGroup eq
$msg{"anonymous or logged-in (50- edits)"}) {
tie %nEdits, "DB_File", "$datadir/$langProject.nEdits", O_RDONLY;
}
for(my $i=0; $i<$nRevs; $i++) {
my $rev = $revs0->[$i];
my $ok = 0;
if(length($Q::authors)>0) {
if($Q::regEx) {
$ok = $rev->{"user"} =~ /$Q::authors/;
} else {
my @authors = split(/,/, $Q::authors);
$ok |= $rev->{"user"} =~ /^$_$/ for @authors;
}
} else {
if($Q::authorGroup eq $msg{"anyone"}) {
$ok = 1;
} elsif($Q::authorGroup eq $msg{"anonymous"}) {
$ok = $rev->{"user"} =~ /^\d+\.\d+\.\d+\.\d+$/;
} elsif($Q::authorGroup eq $msg{"anonymous or logged-in (50- edits)"}) {
$ok = $rev->{"user"} =~ /^\d+\.\d+\.\d+\.\d+$/ || $nEdits{$rev->{"user"}}<=50;
} elsif($Q::authorGroup eq $msg{"logged-in"}) {
$ok = $rev->{"user"} !~ /^\d+\.\d+\.\d+\.\d+$/;
} elsif($Q::authorGroup eq $msg{"logged-in (200+ edits)"}) {
$ok = $rev->{"user"} !~ /^\d+\.\d+\.\d+\.\d+$/ && $nEdits{$rev->{"user"}}>=200;
}
}
$ok = !$ok if $Q::reverseAuthors;
if($ok) {
if($Q::actionAuthors eq $msg{"select"}) {
push @$revs, $rev;
} else {
$rev->{"mark"} = "author";
}
}
}
untie %nEdits if defined %nEdits;
$revs;
}
sub filterHistory {
$nRevs0 = @$revs;
$revs = filterText($revs) if $Q::filterText;
$revs = filterReverts($revs) if $Q::filterReverts;
$revs = filterMultipleEdits($revs) if $Q::filterMultipleEdits;
$revs = filterAuthors($revs) if $Q::filterAuthors;
$nRevs = @$revs;
for(my $i=0; $i<$nRevs; $i++) {
$nMarked++ if defined $revs->[$i]->{"mark"};
}
$From = $Q::offset;
$To = $Q::offset+$Q::limit-1;
$To = $nRevs-1 if $To>$nRevs-1;
$To = $Q::offset if $To<$Q::offset;
}
sub pageLink {
my ($revId, $diffId) = @_;
wikiLink($Q::page, $revId, $diffId);
}
sub sectionLink {
my $section = $_[0];
$section =~ s/\s/_/g;
"http://$Q::lang.$Q::project.org/w/index.php?title=$Q::page#$section";
}
sub userLink {
my $user = $_[0];
if($user =~ /\d+\.\d+\.\d+\.\d+/) {
wikiLink($var{"Special"}.":Contributions/$user");
} else {
wikiLink($var{"User"}.":".$user);
}
}
sub wikiLink {
my ($page, $revId, $diffId) = @_;
"http://$Q::lang.$Q::project.org/w/index.php?title=$page".
(defined $revId?"&oldid=$revId":"") . (defined $diffId?"&diff=$diffId":"");
}
sub comment {
my $comment = $_[0];
$comment =~ s/\/\* (.*?) \*\/([^;]*)/span({-style=>"color:#606060"},$1.(length($2)>0?" - ":"")).$2/eg;
$comment =~ s/\[\[(?:(.*?)\|)?([^\|]*?)\]\]/a({-href=>wikiLink(defined $1?$1:$2)},$2)/eg;
$comment;
}
sub convertTimestamp {
my $timestamp = @_[0];
$timestamp =~ s/(....)-(..)-(..)T(..):(..):(..)Z/$1-$2-$3 $4:$5/;
$timestamp;
}
sub chopName {
my ($name, $maxlen)=@_;
$name = substr($name, 0, $maxlen-2)."..." if length($name)>$maxlen;
$name;
}
sub showHistory {
my $selectId = 0;
my ($start, $incr, $curArrows, $lastArrows);
my $lowerLimit = 0;
my $upperLimit = $nRevs-1;
if($Q::recentFirst) {
$start = $nRevs-1-$Q::offset;
$lowerLimit = $start-$Q::limit+1 if $Q::limit>0 && $start-$Q::limit+1>$lowerLimit;
$incr = -1;
$curArrows = "↑↑";
$lastArrows = "↓↓";
} else {
$start = $Q::offset;
$upperLimit = $start+$Q::limit-1 if $Q::limit>0 && $start+$Q::limit-1<$upperLimit;
$incr = 1;
$curArrows = "↓↓";
$lastArrows = "↑↑";
}
my $showDeltaW = $Q::filterText && $Q::actionText eq $msg{"select"};
my $header .= Tr({-valign=>"top", -align=>"left", -class=>"header"},
th({-colspan=>"3"}, $msg{"diff"}),
th($msg{"date/time"}),
th("#S"),
th({-align=>"right"}, "ΔL"),
$showDeltaW?th({-align=>"right"}, "ΔW"):"",
th($msg{"author"}),
th({-width=>"100%"}, $msg{"comment"})."\n"
);
my $tooltipNr = 0;
my $tooltipJs = "";
for(my $i=$start; $i>=$lowerLimit && $i<=$upperLimit; $i+=$incr) {
$rev0 = $revs->[$i-1] if $i>0;
$rev = $revs->[$i];
@secTitleChanges = ();
if(!$Q::filterText) {
if(!defined $rev->{"secTitleMd5"}) {
my $secNrs = $rev->{"secNrs"};
my $sgr = $rev->{"sgr"};
$rev->{"secTitleMd5"} = md5_base64(
join("#", map { $sgr->[$_]->{"level"}.",".$sgr->[$_]->{"title"} } @$secNrs));
}
if($i>0 && !defined $rev0->{"secTitleMd5"}) {
my $secNrs = $rev0->{"secNrs"};
my $sgr = $rev0->{"sgr"};
$rev0->{"secTitleMd5"} = md5_base64(
join("#", map { $sgr->[$_]->{"level"}.",".$sgr->[$_]->{"title"} } @$secNrs));
}
if($i>0 && $rev->{"secTitleMd5"} ne $rev0->{"secTitleMd5"}) {
my $secNrs = $rev->{"secNrs"};
my $sgr = $rev->{"sgr"};
my $secNrs0 = $rev0->{"secNrs"};
my $sgr0 = $rev0->{"sgr"};
my %secLevelByTitle = map { $sgr->[$_]->{"title"} => $sgr->[$_]->{"level"} } @$secNrs;
my %secLevelByTitle0 = map { $sgr0->[$_]->{"title"} => $sgr0->[$_]->{"level"} } @$secNrs0;
my $nChanges = 0;
foreach $title0 (keys %secLevelByTitle0) {
my $eq = "=" x $secLevelByTitle0{$title0};
if(!defined $secLevelByTitle{$title0} ||
$secLevelByTitle{$title0}!=$secLevelByTitle0{$title0}) {
if($nChanges++>10) {
push @secTitleChanges, "...";
last;
} else {
push @secTitleChanges, "- $eq$title0$eq";
}
}
}
$nChanges = 0;
foreach $title (keys %secLevelByTitle) {
my $eq = "=" x $secLevelByTitle{$title};
if(!defined $secLevelByTitle0{$title} ||
$secLevelByTitle0{$title}!=$secLevelByTitle{$title}) {
if($nChanges++>10) {
push @secTitleChanges, "...";
last;
} else {
push @secTitleChanges, "+ $eq$title$eq";
}
}
}
if(@secTitleChanges == 0) {
my $N = @$secNrs;
my $N0 = @$secNrs0;
my $n = keys %secLevelByTitle;
my $n0 = keys %secLevelByTitle0;
if($n==$n0) {
if($N==$N0) {
push @secTitleChanges, $msg{"section sequence changed"};
} elsif($N<$N0) {
push @secTitleChanges, ($N0-$N)." ".$msg{"section(s) deleted"};
} else {
push @secTitleChanges, ($N-$N0)." ".$msg{"section(s) added"};
}
} elsif($n<$n0) {
push @secTitleChanges, ($n0-$n)." ".$msg{"section(s) deleted"};
} else {
push @secTitleChanges, ($n-$n0)." ".$msg{"section(s) added"};
}
}
$tooltipJs .= 'tooltips['.$tooltipNr.'] = new Array("'.join('","', @secTitleChanges).'");'."\n";
}
}
my $deltaL = "";
$deltaL = $rev->{"lDiff"};
$deltaL = "+$deltaL" if $deltaL>0;
my @deltaW = (); my $deltaW = "";
if($showDeltaW) {
push @deltaW, "-".$rev->{"textMinus"} if $rev->{"textMinus"}>0;
push @deltaW, "+".$rev->{"textPlus"} if $rev->{"textPlus"} >0;
$deltaW = join(",", @deltaW);
}
$rows .= Tr({-valign=>"top", -class=>(defined $rev->{"mark"}?$rev->{"mark"}:($i%2==0?"rowEven":"rowOdd"))},
td(checkbox({-id=>"cb$selectId", -onclick=>"sel(".$selectId++.",".$rev->{"revId"}.")"})),
td("(".a({-href=>pageLink($rev->{"revId"}, "0")}, $curArrows).")"),
td("(".($rev->{"diffId"}>0?a({-href=>pageLink($rev->{"diffId"},
$rev->{"revId"})}, $lastArrows):$lastArrows).")"),
td(a({-href=>pageLink($rev->{"revId"})}, convertTimestamp($rev->{"timestamp"}))),
td(@secTitleChanges>0?{-class=>"secTitleChanges", -onmouseover=>'tooltip('.$tooltipNr++.')',
-onmouseout=>'hideTooltip()' }:{}, int(@{$rev->{"secNrs"}})),
td({-align=>"right"}, $deltaL),
$showDeltaW?td({-align=>"right"}, $deltaW):"",
td("<nobr>".a({-href=>userLink($rev->{"user"})},chopName($rev->{"user"},20)."</nobr>")),
td(($Q::nobr?"<nobr>":"").comment($rev->{"comment"}).
($Q::nobr?"</nobr>":""))) . "\n";
}
navigationAndStatistics();
print script({ -language=>"JavaScript" }, $tooltipJs), "\n";
print div({-id=>"tableDiv"},
table({-id=>"historyTable", -border=>"0", -cellspacing=>"1", -cellpadding=>"2"},
div({-id=>"rows" }, $header, $rows)
)
);
}
sub navigationAndStatistics {
$time1a = time - $time0a;
$time1b = (times())[0] - $time0b;
print script({ -language=>"JavaScript" },
'document.getElementById("statistics").firstChild.nodeValue="' .
"$nRevs/$nMarked/$nRevs0 ".$msg{"revisions"}.' ('.sprintf("%.2f/%.2f", $time1b, $time1a).'s)";' .
'document.getElementById("forwardLink").setAttribute("onclick", "forward('.$nRevs.')");' .
'document.getElementById("fromTo").firstChild.nodeValue = "('.$From.'...'.$To.')"'
), "\n";
}
sub showForm {
my $q = defined $Q::page;
my $args = {-id=>"formDiv"};
$args->{"style"} = "display:none" if $q;
my $rows = Tr({ -class=>"header" }, th($msg{"Filter"}), th($msg{"Condition(s)"}), th($msg{"Action"}));
$rows .= Tr({ -class=>"rowFilter" },
td(checkbox('filterSections', $Q::filterSections?'checked':'', 'ON', $msg{"Sections"})),
td($msg{"Section"}.": ".
textfield(-onChange=>"document.filterForm.offset.value=0",
-name=>'section', -value=>'', -size=>"50"), br, "\n",
checkbox('includeSubsections', !$q||$Q::includeSubsections?'checked':'', 'ON',
$msg{"include subsections"}) . " " .
checkbox('reverseSections', $Q::reverseSections?'checked':'', 'ON',
$msg{"reverse conditions"})),
td(popup_menu(-name=>'actionSections',
-values=>[ $msg{"select"}, $msg{"mark"} ]))
);
$rows .= Tr({ -class=>"rowFilter" },
td(checkbox('filterText', $Q::filterText?'checked':'', 'ON', $msg{"Text"})),
td($msg{"Edits inserting/altering/removing the following text"}.": ". br .
textarea(-name=>'text', -value=>'', -rows=>"2", -columns=>"60"), br, "\n"),
td(popup_menu(-name=>'actionText',
-values=>[ $msg{"select"}, $msg{"mark"} ]))
);
$rows .= Tr({ -class=>"rowFilter" },
td(checkbox('filterReverts', !$q||$Q::filterReverts?'checked':'', 'ON', $msg{"Reverts"})),
td(table(
Tr(
td($msg{"reverting user"}.": "),
td(popup_menu(-name=>'revertingUser', -values=>[
$msg{"anyone"},
$msg{"logged-in"},
$msg{"logged-in (200+ edits)"},
]))
), Tr(
td($msg{"reverted user"}.": "),
td(popup_menu(-name=>'revertedUser', -values=>[
$msg{"anonymous or logged-in (50- edits)"},
$msg{"anonymous"},
$msg{"anyone"}
]))
)
)),
td(popup_menu(-name=>'actionReverts', -values=>[
$msg{"delete"},
$msg{"mark"}
]))
);
$rows .= Tr({ -class=>"rowFilter" },
td(checkbox('filterMultipleEdits', !$q||$Q::filterMultipleEdits?'checked':'', 'ON',
$msg{"Multiple edits"})),
td(popup_menu(-name=>'multipleEditsCondition', -values=>[
$msg{"always"},
$msg{"if the comments are short"}
])),
td(popup_menu(-name=>'actionMultipleEdits', -values=>[
$msg{"combine"},
$msg{"mark"}
]))
);
$rows .= Tr({ -class=>"rowFilter" },
td(checkbox('filterAuthors', !$q||$Q::filterAuthors?'checked':'', 'ON',
$msg{"Authors"})), "\n",
td(table(
Tr(
td($msg{"user name(s)"}.": "),
td(textfield(-name=>'authors', -value=>'', -size=>"35"),
checkbox('regEx', !$q||$Q::regEx?'checked':'', 'ON', "regEx")
)
), Tr(
td($msg{"user group"}.": "),
td(popup_menu(-name=>'authorGroup', -values=>[
$msg{"anyone"},
$msg{"anonymous"},
$msg{"anonymous or logged-in (50- edits)"},
$msg{"logged-in"},
$msg{"logged-in (200+ edits)"}
]))
), Tr(
td({-colspan=>"2"}, checkbox('reverseAuthors',
$Q::reverseAuthors?'checked':'', 'ON', $msg{"reverse conditions"}))
)
)), "\n",
td(popup_menu(-name=>'actionAuthors', -values=>[
$msg{"select"},
$msg{"mark"}
]))
);
print div($args, start_form({-name=>"filterForm"}), "\n",
$msg{"language/project"}, ": ", popup_menu(-name=>'lang',
-values=>['de','en','fr']), "\n",
popup_menu(-name=>'project',
-values=>['wikipedia','wiktionary','wikibooks']), "\n",
$msg{"page"}, ": ", textfield(-onChange=>"document.filterForm.offset.value=0",
-name=>'page', -value=>'', -size=>"50"), "\n",
p, table({ -id=>"formTable", -border=>"0", -cellspacing=>"1", -cellpadding=>"2" }, $rows), br,
checkbox('recentFirst','checked','ON', $msg{"show recent edits first"}), "\n",
checkbox('nobr','','ON', $msg{"prevent line breaks"}), p, "\n",
$msg{"first revision"}, ": ", textfield('offset', '0', 5),
" ", $msg{"maximum number of revisions"},": ", textfield('limit', '100', 5), p, "\n",
submit(-label=>$msg{"submit"}), "\n",
end_form, "\n"), "\n";
}