forked from github/kensanata.oddmuse
Compare commits
123 Commits
2.3.1
...
new-file-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03111c7f58 | ||
|
|
b1d39e3195 | ||
|
|
48a00a6ff6 | ||
|
|
a3ee3c60ce | ||
|
|
d69063599e | ||
|
|
0146225c4f | ||
|
|
50fca72f82 | ||
|
|
1a4e6aa527 | ||
|
|
cdf8b561a6 | ||
|
|
905d8c930e | ||
|
|
f64c6d470b | ||
|
|
0657d84769 | ||
|
|
0181d8b944 | ||
|
|
fae5f1e345 | ||
|
|
81b179acac | ||
|
|
bc810ee0ce | ||
|
|
3a4236bc45 | ||
|
|
8642ae63a2 | ||
|
|
3c5373f76b | ||
|
|
58e297b092 | ||
|
|
cd4f6dc64c | ||
|
|
5a112b64b3 | ||
|
|
a7b0c661c8 | ||
|
|
b2f9a0044b | ||
|
|
d0cdd451e4 | ||
|
|
86334d6532 | ||
|
|
733752727d | ||
|
|
6635803807 | ||
|
|
7f3488baaa | ||
|
|
dcc318f34e | ||
|
|
17edc1c523 | ||
|
|
85912f211b | ||
|
|
52d7239400 | ||
|
|
9c691e5b9b | ||
|
|
0e45ea2e99 | ||
|
|
8773242dba | ||
|
|
267cd53adb | ||
|
|
ce82a328b6 | ||
|
|
b925805800 | ||
|
|
c8c50b4e81 | ||
|
|
4a976278d5 | ||
|
|
1255fe8168 | ||
|
|
081e8243d7 | ||
|
|
a20fc60617 | ||
|
|
770de2986a | ||
|
|
0551018de1 | ||
|
|
aa77f2ce2f | ||
|
|
471994f7b1 | ||
|
|
9d2c0216f6 | ||
|
|
82d888f0ea | ||
|
|
ccaf283204 | ||
|
|
3fb5319562 | ||
|
|
76c92f027c | ||
|
|
d828454511 | ||
|
|
34c6e93780 | ||
|
|
0a6f473098 | ||
|
|
4d67f9bfd2 | ||
|
|
6e80adc293 | ||
|
|
dc3fb65317 | ||
|
|
6a652de193 | ||
|
|
4747235fe7 | ||
|
|
0ecbeeb2c4 | ||
|
|
65378d91cb | ||
|
|
cb6a6bf4a6 | ||
|
|
db67c34203 | ||
|
|
ccf8fe2314 | ||
|
|
e336086cf0 | ||
|
|
5315b3f6ad | ||
|
|
e31abd57bc | ||
|
|
5bf60bb5d8 | ||
|
|
ad672aff28 | ||
|
|
e49af47d30 | ||
|
|
ecbe6a859a | ||
|
|
413228c56c | ||
|
|
a905de7ab5 | ||
|
|
a09409f375 | ||
|
|
764c0ffcf1 | ||
|
|
5962745937 | ||
|
|
d380062ec6 | ||
|
|
cd8066233c | ||
|
|
6f04d2044f | ||
|
|
f41ded592b | ||
|
|
8a2c9eca9c | ||
|
|
d712a17f82 | ||
|
|
504190b752 | ||
|
|
56e515a791 | ||
|
|
36feb62052 | ||
|
|
239f15cdbc | ||
|
|
f39cfd3235 | ||
|
|
329699a6aa | ||
|
|
28965bdaa6 | ||
|
|
ed42d2dad5 | ||
|
|
45b21cbdb8 | ||
|
|
b540093c2c | ||
|
|
d164d47e24 | ||
|
|
a56b92ecb3 | ||
|
|
114d914754 | ||
|
|
875051ea84 | ||
|
|
003357acad | ||
|
|
e1c77c4ba6 | ||
|
|
88475c3e41 | ||
|
|
e80f05301d | ||
|
|
a624e78975 | ||
|
|
8cd869f0f9 | ||
|
|
b09b3f8f8e | ||
|
|
b9043ffd98 | ||
|
|
54d3dc400a | ||
|
|
3962068385 | ||
|
|
a0b74ac3c6 | ||
|
|
870d75ac64 | ||
|
|
42d8260ce4 | ||
|
|
bfda4abe54 | ||
|
|
4b0d411564 | ||
|
|
6790de2d6a | ||
|
|
2784628544 | ||
|
|
dd8c687b2b | ||
|
|
9f4ceb2d72 | ||
|
|
0f8a4fa1df | ||
|
|
3b16b58880 | ||
|
|
192a902932 | ||
|
|
aedf77cff8 | ||
|
|
8ec456ed41 | ||
|
|
020df9098d |
315
contrib/add-link.pl
Normal file
315
contrib/add-link.pl
Normal file
@@ -0,0 +1,315 @@
|
||||
#! /usr/bin/perl
|
||||
|
||||
# Copyright (C) 2011–2015 Alex Schroeder <alex@gnu.org>
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package OddMuse;
|
||||
|
||||
use LWP::UserAgent;
|
||||
use HTML::TreeBuilder;
|
||||
use JSON::PP;
|
||||
use utf8;
|
||||
|
||||
# load Oddmuse core
|
||||
$RunCGI = 0;
|
||||
do "wiki.pl";
|
||||
|
||||
# globals depending on the name of the script
|
||||
my ($self, $name, $wiki);
|
||||
if ($0 eq '/home/alex/campaignwiki.org/add-link.pl') {
|
||||
$self = "https://campaignwiki.org/add-link";
|
||||
$name = "OSR Links to Wisdom";
|
||||
$wiki = 'LinksToWisdom';
|
||||
} elsif ($0 eq '/home/alex/campaignwiki.org/add-adventure.pl') {
|
||||
$self = "https://campaignwiki.org/add-adventure";
|
||||
$name = "OSR Links to Adventures";
|
||||
$wiki = 'Adventures';
|
||||
} else {
|
||||
ReportError('Cannot determine wiki!', '500 INTERNAL SERVER ERROR');
|
||||
}
|
||||
|
||||
# derived variables
|
||||
my $site = "https://campaignwiki.org/wiki/$wiki";
|
||||
# my $site = "http://localhost/wiki.pl";
|
||||
my $home = "$site/$HomePage";
|
||||
# http://www.emacswiki.org/pics/star.png
|
||||
my $stardata = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEUAAHkAAACzdRTapx3twwD/9qb////1YCa0AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfXAQYCJAu+WhwbAAAAKnRFWHRDb21tZW50AGJ5IFJhZG9taXIgJ1RoZSBTaGVlcCcgRG9waWVybGFza2kVfTXbAAAAYElEQVQI12NgQAKMMIaYAFTAzRDKCHOEMETCnEFyjIJhYS6OggwMoqGhaS7GRgIMjC6uYc5GikA5YRcXIyWwotBgJUWw7lAXsAyDaIihMlhK1FFA0AjEEAESQgJQu4EYAPAPC2XcokgQAAAAAElFTkSuQmCC';
|
||||
|
||||
main();
|
||||
|
||||
sub toc {
|
||||
# start with the homepage
|
||||
my @values;
|
||||
my %labels;
|
||||
for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
|
||||
push @values, $id;
|
||||
for my $item (GetPageContent(FreeToNormal($id)) =~ /(\*+ [^][\n]*)$/mg) {
|
||||
my $value = $item;
|
||||
my $label = $item;
|
||||
$value =~ s/\* *//g;
|
||||
push @values, $value;
|
||||
$label =~ s/\* */ /g; # EM SPACE
|
||||
$labels{$value} = $label;
|
||||
}
|
||||
}
|
||||
return \@values, \%labels;
|
||||
}
|
||||
|
||||
sub top {
|
||||
# start with the homepage
|
||||
my %blog;
|
||||
my $n;
|
||||
for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
|
||||
for my $item (GetPageContent(FreeToNormal($id)) =~ /^\*+\s+\[(https?:\/\/[^\/\n\t ]+)/mg) {
|
||||
$n++;
|
||||
# handle blogspot domain munging
|
||||
$item =~ s/blogspot(\.[a-z]+)+/blogspot.com/;
|
||||
$blog{$item}++;
|
||||
}
|
||||
}
|
||||
print $q->p("Total links counted: $n.");
|
||||
my @list = sort { $blog{$b} <=> $blog{$a} } keys %blog;
|
||||
# my $max = scalar @list;
|
||||
# $max = 20 if $max > 20;
|
||||
# @list = @list[0 .. $max -1];
|
||||
@list = map {
|
||||
my $domain = substr($_, index($_, '://') + 3);
|
||||
my $term = quotemeta($domain);
|
||||
# handle blogspot domain munging
|
||||
$term =~ s/blogspot\\\.com/blogspot(\\.[a-z]+)+/;
|
||||
$term = QuoteHtml($term);
|
||||
$q->a({-href => $_}, $domain)
|
||||
. " (" . $q->a({-href => "$self/match/$term"}, $blog{$_}) . ")";
|
||||
} @list;
|
||||
return \@list;
|
||||
}
|
||||
|
||||
sub match {
|
||||
my $term = shift;
|
||||
# start with the homepage
|
||||
my @list;
|
||||
my $title;
|
||||
for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
|
||||
for my $line (split /\n/, GetPageContent(FreeToNormal($id))) {
|
||||
if ($line =~ /^\*+\s+([^][\n]*)$/) {
|
||||
$title = $1;
|
||||
} elsif ($line =~ /$term/o) {
|
||||
if ($line =~ /^\*+\s+\[(https?:\S+)\s+([^]]+)\]/) {
|
||||
push (@list, $q->a({-href => $1}, $2) . " (" . $title . ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return \@list;
|
||||
}
|
||||
|
||||
sub html_toc {
|
||||
my ($values, $labels) = toc();
|
||||
return $q->radio_group(-name =>'toc',
|
||||
-values => $values,
|
||||
-labels => $labels,
|
||||
-linebreak=>'true');
|
||||
}
|
||||
|
||||
sub default {
|
||||
print $q->p("Add a link to the " . $q->a({-href=>$home}, $name) . ".");
|
||||
print $q->start_multipart_form(-method=>'get', -class=>'submit');
|
||||
print $q->p($q->label({-for=>'url'}, T('URL:')) . ' '
|
||||
. $q->textfield(-name=>'url', -id=>'url', -size=>80));
|
||||
print $q->p({-style=>'font-size: 10pt'},
|
||||
"(Drag this bookmarklet to your bookmarks bar for easy access:",
|
||||
$q->a({-href=>q{javascript:location='}
|
||||
. $q->url()
|
||||
. qq{?url='+encodeURIComponent(window.location.href)}},
|
||||
"Submit $name") . ".)");
|
||||
print html_toc();
|
||||
print $q->submit('go', 'Add!');
|
||||
print $q->end_form();
|
||||
}
|
||||
|
||||
sub confirm {
|
||||
my ($url, $name, $toc) = @_;
|
||||
print $q->p("Please confirm that you want to add "
|
||||
. GetUrl($url, $name)
|
||||
. " to the section “$toc”.");
|
||||
print $q->start_form(-method=>'get');
|
||||
print $q->p($q->label({-for=>'name', -style=>'display: inline-block; width: 15em'},
|
||||
T('Use a different link name:')) . ' '
|
||||
. $q->textfield(-style=>'display: inline-block; width:50ex',
|
||||
-name=>'name', -id=>'name', -size=>50, -default=>$name)
|
||||
. $q->br()
|
||||
. $q->label({-for=>'summary', -style=>'display: inline-block; width:15em'},
|
||||
T('An optional short summary:')) . ' '
|
||||
. $q->textfield(-style=>'display: inline-block; width:50ex',
|
||||
-name=>'summary', -id=>'summary', -size=>50)
|
||||
. $q->br()
|
||||
. $q->label({-for=>'username', -style=>'display: inline-block; width:15em'},
|
||||
T('Your name for the log file:')) . ' '
|
||||
. $q->textfield(-style=>'display: inline-block; width:50ex',
|
||||
-name=>'username', -id=>'username', -size=>50));
|
||||
my $star = $q->img({-src=>$stardata, -class=>'smiley', -alt=>'☆'});
|
||||
print '<p>Optionally: Do you want to rate it?<br />';
|
||||
my $i = 0;
|
||||
foreach my $label ($q->span({-style=>'display: inline-block; width:3em'}, $star)
|
||||
. 'I might use this for my campaign',
|
||||
$q->span({-style=>'display: inline-block; width:3em'}, $star x 2)
|
||||
. 'I have used this in a campaign and it worked as intended',
|
||||
$q->span({-style=>'display: inline-block; width:3em'}, $star x 3)
|
||||
. 'I have used this in a campaign and it was ' . $q->em('great')) {
|
||||
$i++;
|
||||
print qq{<label><input type="radio" name="stars" value="$i" $checked/>$label</label><br />};
|
||||
}
|
||||
print '</p>';
|
||||
print $q->hidden('url', $url);
|
||||
print $q->hidden('toc', $toc);
|
||||
print $q->hidden('confirm', 1);
|
||||
print $q->submit('go', 'Continue');
|
||||
print $q->end_form();
|
||||
}
|
||||
|
||||
# returns unquoted html
|
||||
sub get_name {
|
||||
my $url = shift;
|
||||
my $tree = HTML::TreeBuilder->new_from_content(GetRaw($url));
|
||||
my $h = $tree->look_down('_tag', 'title');
|
||||
$h = $tree->look_down('_tag', 'h1') unless $h;
|
||||
$h = $h->as_text if $h;
|
||||
return $h;
|
||||
}
|
||||
|
||||
sub post_addition {
|
||||
my ($url, $name, $toc, $summary) = @_;
|
||||
my $id = FreeToNormal($name);
|
||||
my $display = $name;
|
||||
utf8::decode($display); # we're dealing with user input
|
||||
utf8::decode($summary); # we're dealing with user input
|
||||
print $q->p("Adding ", GetUrl($url, $display), " to “$toc”.");
|
||||
# start with the homepage
|
||||
my @pages = GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g;
|
||||
for my $id (@pages) {
|
||||
return post($id, undef, $name, $summary, $url, GetParam('stars', '')) if $id eq $toc;
|
||||
my $data = GetPageContent(FreeToNormal($id));
|
||||
while ($data =~ /(\*+ ([^][\n]*))$/mg) {
|
||||
return post($id, $1, $name, $summary, $url, GetParam('stars', '')) if $2 eq $toc;
|
||||
}
|
||||
}
|
||||
print $q->p("Whoops. I was unable to find “$toc” in the wiki. Sorry!");
|
||||
}
|
||||
|
||||
sub post {
|
||||
my ($id, $toc, $name, $summary, $url, $stars) = @_;
|
||||
my $data = GetPageContent(FreeToNormal($id));
|
||||
my $re = quotemeta($url);
|
||||
if ($data =~ /$re\s+(.*?)\]/) {
|
||||
my $display = $1;
|
||||
print $q->p($q->strong("Oops, we seem to have a problem!"));
|
||||
print $q->p(GetPageLink(NormalToFree($id)),
|
||||
" already links to the URL you submitted:",
|
||||
GetUrl($url, $display));
|
||||
return;
|
||||
}
|
||||
$stars = ' ' . (':star:' x $stars) if $stars;
|
||||
$summary = ': ' . $summary if $summary;
|
||||
if ($toc) {
|
||||
$toc =~ /^(\*+)/;
|
||||
my $depth = "*$1"; # one more!
|
||||
my $regexp = quotemeta($toc);
|
||||
$data =~ s/$regexp/$toc\n$depth \[$url $name\]$summary$stars/;
|
||||
} else {
|
||||
$data = "* [$url $name]$summary$stars\n" . $data;
|
||||
}
|
||||
my $ua = LWP::UserAgent->new;
|
||||
my %params = (text => $data,
|
||||
title => $id,
|
||||
summary => $name,
|
||||
username => GetParam('username'),
|
||||
pwd => GetParam('pwd'));
|
||||
# spam fighting modules
|
||||
$params{$QuestionaskerSecretKey} = 1 if $QuestionaskerSecretKey;
|
||||
$params{$HoneyPotOk} = time if $HoneyPotOk;
|
||||
my $response = $ua->post($site, \%params);
|
||||
if ($response->is_error) {
|
||||
print $q->p("The submission failed!");
|
||||
print $response->content;
|
||||
} else {
|
||||
print $q->p("See for yourself: ", GetPageLink($id));
|
||||
}
|
||||
}
|
||||
|
||||
sub print_end_of_page {
|
||||
print $q->p('Questions? Send mail to Alex Schroeder <'
|
||||
. $q->a({-href=>'mailto:kensanata@gmail.com'},
|
||||
'kensanata@gmail.com') . '>');
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
}
|
||||
|
||||
sub main {
|
||||
$ConfigFile = "$DataDir/config"; # read the global config file
|
||||
$DataDir = "$DataDir/$wiki"; # but link to the local pages
|
||||
Init(); # read config file (no modules!)
|
||||
$ScriptName = $site; # undo setting in the config file
|
||||
$FullUrl = $site; #
|
||||
binmode(STDOUT,':utf8');
|
||||
$q->charset('utf8');
|
||||
if ($q->path_info eq '/source') {
|
||||
seek DATA, 0, 0;
|
||||
print "Content-type: text/plain; charset=UTF-8\r\n\r\n", <DATA>;
|
||||
} elsif ($q->path_info eq '/structure') {
|
||||
my ($values, $labels) = toc();
|
||||
my @indented = map {
|
||||
($labels->{$_} || $_) =~ /^( *)/;
|
||||
[$_, length($1)]
|
||||
} @$values;
|
||||
print "Content-type: application/json; charset=UTF-8\r\n\r\n";
|
||||
binmode(STDOUT,':raw'); # because of encode_json
|
||||
print JSON::PP::encode_json(\@indented);
|
||||
} elsif ($q->path_info eq '/toc') {
|
||||
my ($values, $labels) = toc();
|
||||
print "Content-type: application/json; charset=UTF-8\r\n\r\n";
|
||||
binmode(STDOUT,':raw'); # because of encode_json
|
||||
print JSON::PP::encode_json($values);
|
||||
} elsif ($q->path_info eq '/top') {
|
||||
print GetHeader('', 'Top Blogs');
|
||||
print $q->start_div({-class=>'content top'});
|
||||
print $q->ol($q->li(top()));
|
||||
print_end_of_page();
|
||||
} elsif ($q->path_info =~ '^/match/(.*)') {
|
||||
my $term = $1;
|
||||
print GetHeader('', "Entries Matching '$term'");
|
||||
print $q->start_div({-class=>'content match'});
|
||||
print $q->ol($q->li(match($term)));
|
||||
print_end_of_page();
|
||||
} else {
|
||||
push(@UserGotoBarPages, 'Help');
|
||||
$UserGotoBar = $q->a({-href=>$q->url . '/source'}, 'Source');
|
||||
print GetHeader('', 'Submit a new link');
|
||||
print $q->start_div({-class=>'content index'});
|
||||
my $url = GetParam('url');
|
||||
my $name = UnquoteHtml(GetParam('name', get_name($url)));
|
||||
my $toc = GetParam('toc');
|
||||
my $confirm = GetParam('confirm');
|
||||
my $summary = GetParam('summary');
|
||||
if (not $url or not $toc) {
|
||||
default();
|
||||
} elsif (not $confirm) {
|
||||
confirm($url, $name, $toc);
|
||||
} else {
|
||||
post_addition($url, $name, $toc, $summary);
|
||||
}
|
||||
print_end_of_page();
|
||||
}
|
||||
}
|
||||
|
||||
__DATA__
|
||||
@@ -812,7 +812,8 @@ Use a prefix argument to force a reload of the page."
|
||||
;; fix it for VC in the new buffer because this is not a vc-checkout
|
||||
(vc-mode-line buffer-file-name 'oddmuse)
|
||||
(pop-to-buffer (current-buffer))
|
||||
(oddmuse-mode))))
|
||||
(oddmuse-mode)
|
||||
(write-file (buffer-file-name)))))
|
||||
|
||||
(defalias 'oddmuse-go 'oddmuse-edit)
|
||||
|
||||
@@ -851,7 +852,9 @@ Use a prefix argument to override this."
|
||||
(puthash oddmuse-wiki (cons oddmuse-page-name list) oddmuse-pages-hash)))
|
||||
(and buffer-file-name (basic-save-buffer))
|
||||
(oddmuse-run "Posting" oddmuse-post-command nil nil
|
||||
(get-buffer-create " *oddmuse-response*") t 302))
|
||||
(get-buffer-create " *oddmuse-response*") t 302)
|
||||
(oddmuse-revision-put oddmuse-wiki oddmuse-page-name
|
||||
(oddmuse-get-latest-revision oddmuse-wiki oddmuse-page-name)))
|
||||
|
||||
;;;###autoload
|
||||
(defun oddmuse-preview (&optional arg)
|
||||
@@ -993,6 +996,21 @@ With universal argument, reload."
|
||||
(goto-char (point-min))
|
||||
(oddmuse-mode)))
|
||||
|
||||
(defun oddmuse-history (wiki pagename)
|
||||
"Show the history for PAGENAME on WIKI.
|
||||
Compared to `vc-oddmuse-print-log' this only prints the revisions
|
||||
that can actually be retrieved (for diff and rollback)."
|
||||
(interactive (oddmuse-pagename-if-missing))
|
||||
(let ((name (concat "*" wiki ": history for " pagename "*")))
|
||||
(if (and (get-buffer name)
|
||||
(not current-prefix-arg))
|
||||
(pop-to-buffer (get-buffer name))
|
||||
(set-buffer (get-buffer-create name))
|
||||
(erase-buffer)
|
||||
(oddmuse-run "History" oddmuse-get-history-command wiki pagename)
|
||||
(oddmuse-mode)
|
||||
(set (make-local-variable 'oddmuse-wiki) wiki))))
|
||||
|
||||
;;;###autoload
|
||||
(defun emacswiki-post (&optional pagename summary)
|
||||
"Post the current buffer to the EmacsWiki.
|
||||
@@ -1031,6 +1049,6 @@ PAGENAME is the pagename of the page you want to browse."
|
||||
(interactive)
|
||||
(kill-new (oddmuse-url oddmuse-wiki oddmuse-page-name)))
|
||||
|
||||
(provide 'oddmuse)
|
||||
(provide 'oddmuse-curl)
|
||||
|
||||
;;; oddmuse-curl.el ends here
|
||||
|
||||
108
contrib/oddmuse_stats
Executable file
108
contrib/oddmuse_stats
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/perl -w
|
||||
# -*- perl -*-
|
||||
|
||||
=head1 NAME
|
||||
|
||||
oddmuse-stats - Plugin to monitor Oddmuse edits
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
Set env.parent_dirs in the config file. The directories in this list
|
||||
are searched for data directories containing rc.log files. No
|
||||
whitespace in the directory names, sorry.
|
||||
|
||||
Example:
|
||||
|
||||
[oddmuse_stats]
|
||||
user www-data
|
||||
env.parent_dirs /home/alex /home/alex/campaignwiki
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Original Author: Alex Schroeder
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
GPLv3
|
||||
|
||||
=head1 MAGIC MARKERS
|
||||
|
||||
#%# family=auto
|
||||
#%# capabilities=autoconf
|
||||
|
||||
=cut
|
||||
|
||||
use Munin::Plugin;
|
||||
use File::Basename;
|
||||
|
||||
# The wiki directories may not contain any spaces.
|
||||
# Use the config file to set the environment variable!
|
||||
my @parent_dirs = ();
|
||||
my %logfiles = ();
|
||||
my %names = ();
|
||||
my $debug = $ENV{MUNIN_DEBUG};
|
||||
|
||||
if ($ENV{'parent_dirs'}) {
|
||||
@parent_dirs = split(/ /, $ENV{'parent_dirs'});
|
||||
} else {
|
||||
die "The parent_dirs environment variable must be set.\n";
|
||||
}
|
||||
|
||||
for my $parent_dir (@parent_dirs) {
|
||||
warn "opening $parent_dir\n" if $debug;
|
||||
if (opendir(my $dh, $parent_dir)) {
|
||||
while(readdir $dh) {
|
||||
next if $_ eq '.' or $_ eq '..';
|
||||
if (-r "$parent_dir/$_/rc.log") {
|
||||
my $basename = basename($_);
|
||||
$names{clean_fieldname($basename)}
|
||||
= $basename;
|
||||
$logfiles{clean_fieldname($basename)}
|
||||
= "$parent_dir/$_/rc.log";
|
||||
} else {
|
||||
warn "discarding $_\n" if $debug;
|
||||
}
|
||||
}
|
||||
closedir $dh;
|
||||
}
|
||||
}
|
||||
|
||||
my $yesterday = time() - 86400;
|
||||
|
||||
if ($ARGV[0]) {
|
||||
if ($ARGV[0] eq 'autoconf') {
|
||||
if (keys %logfiles) {
|
||||
print "yes\n";
|
||||
exit 0;
|
||||
} else {
|
||||
print "no (no logfiles found in " . join(", ", @parent_dirs) . ")\n";
|
||||
exit 0;
|
||||
}
|
||||
} elsif ($ARGV[0] eq 'config') {
|
||||
print "graph_title Oddmuse Wikis\n";
|
||||
print "graph_category wikis\n";
|
||||
print "graph_info This graph shows how many edits the wiki had in the last 24h.\n";
|
||||
print "graph_vlabel edits/day\n";
|
||||
print "graph_order";
|
||||
for my $wiki (sort keys %logfiles) {
|
||||
print " $wiki";
|
||||
};
|
||||
print "\n";
|
||||
for my $wiki (sort keys %logfiles) {
|
||||
my $name = $names{$wiki};
|
||||
print "$wiki.label $name\n";
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
|
||||
for my $wiki (sort keys %logfiles) {
|
||||
open (my $fh, '<', $logfiles{$wiki})
|
||||
or die "cannot open " . $logfiles{$wiki} . ": $!";
|
||||
my $value = 0;
|
||||
while (<$fh>) {
|
||||
my ($ts) = split(/\x1e/);
|
||||
$value++ if $ts and $ts >= $yesterday;
|
||||
}
|
||||
print "$wiki.value $value\n";
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
(add-to-list 'vc-handled-backends 'oddmuse)
|
||||
|
||||
(require 'oddmuse)
|
||||
(require 'oddmuse-curl)
|
||||
(require 'diff)
|
||||
|
||||
(defun vc-oddmuse-revision-granularity () 'file)
|
||||
@@ -110,11 +110,11 @@ It must print the page to stdout.
|
||||
|
||||
(defun oddmuse-revision-filename (rev)
|
||||
"Return filename for revision REV.
|
||||
This uses `oddmuse-directory', `oddmuse-wiki' and
|
||||
`oddmuse-page-name'."
|
||||
This uses `oddmuse-directory', `wiki' and `pagename' as bound by
|
||||
`with-oddmuse-file'."
|
||||
(concat oddmuse-directory
|
||||
"/" oddmuse-wiki
|
||||
"/" oddmuse-page-name
|
||||
"/" wiki
|
||||
"/" pagename
|
||||
".~" rev "~"))
|
||||
|
||||
(defun vc-oddmuse-diff (files &optional rev1 rev2 buffer)
|
||||
@@ -126,13 +126,12 @@ This uses `oddmuse-directory', `oddmuse-wiki' and
|
||||
(dolist (rev (list rev1 rev2))
|
||||
(when (and rev (not (file-readable-p (oddmuse-revision-filename rev))))
|
||||
(let* ((oddmuse-revision rev)
|
||||
(command (oddmuse-format-command
|
||||
vc-oddmuse-get-revision-command))
|
||||
(command vc-oddmuse-get-revision-command)
|
||||
(filename (oddmuse-revision-filename rev)))
|
||||
(with-temp-buffer
|
||||
(oddmuse-run
|
||||
(concat "Downloading revision " rev)
|
||||
command wiki)
|
||||
command wiki pagename)
|
||||
(write-file filename)))))
|
||||
(diff-no-select
|
||||
(if rev1 (oddmuse-revision-filename rev1) file)
|
||||
|
||||
@@ -100,6 +100,25 @@ h1 a:visited, h2 a:visited, h3 a:visited {
|
||||
|
||||
/* links */
|
||||
|
||||
a.pencil {
|
||||
padding-left: 1ex;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
visible: hidden;
|
||||
transition: visibility 0s 1s, opacity 1s linear;
|
||||
opacity: 0;
|
||||
}
|
||||
*:hover > a.pencil {
|
||||
visible: visible;
|
||||
transition: opacity .5s linear;
|
||||
opacity: 1;
|
||||
}
|
||||
@media print {
|
||||
a.pencil {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.number {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -140,6 +159,15 @@ pre, code, tt {
|
||||
line-height: 110%;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow:hidden;
|
||||
white-space: pre-wrap; /* CSS 3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
|
||||
/* styling for divs that will be invisible when printing
|
||||
when printing. */
|
||||
|
||||
@@ -366,6 +394,10 @@ div.image span.caption {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.left { float:left; margin-right: 1em; }
|
||||
.right { float:right; margin-left: 1em; }
|
||||
.half a img { height: 50%; width: 50%; }
|
||||
|
||||
632
css/alex-2014.css
Normal file
632
css/alex-2014.css
Normal file
@@ -0,0 +1,632 @@
|
||||
/* font-face includes TTF for PDF generation */
|
||||
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff') url('/fonts/NoticiaText-Regular.ttf') format('truetype');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff') url('/fonts/NoticiaText-Regular.ttf') format('truetype');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff') url('/fonts/NoticiaText-Regular.ttf') format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff') url('/fonts/NoticiaText-Bold.ttf') format('truetype');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff') url('/fonts/NoticiaText-Bold.ttf') format('truetype');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff') url('/fonts/NoticiaText-Bold.ttf') format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff') url('/fonts/NoticiaText-Italic.ttf') format('truetype');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff') url('/fonts/NoticiaText-Italic.ttf') format('truetype');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff') url('/fonts/NoticiaText-Italic.ttf') format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff') url('/fonts/NoticiaText-BoldItalic.ttf') format('truetype');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff') url('/fonts/NoticiaText-BoldItalic.ttf') format('truetype');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff') url('/fonts/NoticiaText-BoldItalic.ttf') format('truetype');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Symbola';
|
||||
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff') url('/fonts/Symbola.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body, rss {
|
||||
font-family: "Noticia Text", Symbola, serif;
|
||||
font-style: normal;
|
||||
font-size: 14pt;
|
||||
margin: 1em 3em;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
font-size: 12pt;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* hide all the crap */
|
||||
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
|
||||
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
|
||||
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone, div.content a.feed {
|
||||
display:none;
|
||||
}
|
||||
div.content a.book,
|
||||
div.content a.movie {
|
||||
text-decoration: none;
|
||||
}
|
||||
a cite {
|
||||
font-style: italic;
|
||||
}
|
||||
img[alt="RSS"] { display: none }
|
||||
a.rss { font-size: 8pt }
|
||||
}
|
||||
|
||||
/* headings: we can use larger sizes if we use a lighter color.
|
||||
we cannot inherit the font-family because header and footer use a narrow font. */
|
||||
|
||||
h1, h2, h3, title {
|
||||
font-family: inherit;
|
||||
font-weight: normal;
|
||||
}
|
||||
h1, channel title {
|
||||
font-size: 32pt;
|
||||
margin: 1em 0 0.5em 0;
|
||||
padding: 0.4em 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18pt;
|
||||
margin: 2em 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
h3 {
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
padding: 0;
|
||||
margin: 1em 0 0 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* headers in the journal are smaller */
|
||||
|
||||
div.journal h1, item title {
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
clear: both;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
div.journal h2 {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
div.journal h3 {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
font-style: italic;
|
||||
}
|
||||
div.journal hr {
|
||||
visibility: hidden;
|
||||
}
|
||||
p.more {
|
||||
margin-top: 3em;
|
||||
}
|
||||
/* Links in headings appear on journal pages. */
|
||||
|
||||
h1 a, h2 a, h3 a {
|
||||
color:inherit;
|
||||
text-decoration:none;
|
||||
font-weight: normal;
|
||||
}
|
||||
h1 a:visited, h2 a:visited, h3 a:visited {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* for download buttons and the like */
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
font-size: 120%;
|
||||
cursor: pointer;
|
||||
padding: 0.4em 0.6em;
|
||||
text-shadow: 0px -1px 0px #ccc;
|
||||
background-color: #cfa;
|
||||
border: 1px solid #9d8;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 1px 3px white inset, 0px 1px 3px black;
|
||||
}
|
||||
|
||||
.button .icon {
|
||||
color: #363;
|
||||
text-shadow: 0px -1px 1px white, 0px 1px 3px #666;
|
||||
}
|
||||
|
||||
.button a {
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* links */
|
||||
|
||||
a.pencil {
|
||||
padding-left: 1ex;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
visibility: hidden;
|
||||
transition: visibility 0s 1s, opacity 1s linear;
|
||||
opacity: 0;
|
||||
}
|
||||
*:hover > a.pencil {
|
||||
visibility: visible;
|
||||
transition: opacity .5s linear;
|
||||
opacity: 1;
|
||||
}
|
||||
@media print {
|
||||
a.pencil {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.number {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* stop floating content from flowing over the footer */
|
||||
|
||||
hr {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* the distance between links in the navigation bars */
|
||||
|
||||
span.bar a {
|
||||
margin-right: 1ex;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* search box in the top bar */
|
||||
|
||||
.header form, .header p {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
label[for="searchlang"], #searchlang, .header input[type="submit"] {
|
||||
/* don't use display: none! http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
|
||||
visibility: hidden; position: absolute;
|
||||
}
|
||||
/* wrap on the iphone */
|
||||
@media media only screen and (max-device-width: 480px) {
|
||||
}
|
||||
|
||||
.header input {
|
||||
width: 10ex;
|
||||
}
|
||||
|
||||
/* other form fields */
|
||||
|
||||
input[type="text"] {
|
||||
padding: 0;
|
||||
font-size: 80%;
|
||||
line-height: 125%;
|
||||
}
|
||||
|
||||
/* code */
|
||||
|
||||
textarea, pre, code, tt {
|
||||
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow:hidden;
|
||||
white-space: pre-wrap; /* CSS 3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
|
||||
/* styling for divs that will be invisible when printing
|
||||
when printing. */
|
||||
|
||||
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
|
||||
|
||||
font-size: 14pt;
|
||||
}
|
||||
@media print {
|
||||
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
|
||||
font-size: 8pt;
|
||||
}
|
||||
}
|
||||
|
||||
div.footer form.search {
|
||||
display: none;
|
||||
}
|
||||
div.rc li + li {
|
||||
margin-top: 1em;
|
||||
}
|
||||
div.rc li strong, table.history strong, strong.description {
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
div.diff {
|
||||
padding-left: 5%;
|
||||
padding-right: 5%;
|
||||
font-size: 12pt;
|
||||
color: #000;
|
||||
|
||||
}
|
||||
div.old {
|
||||
background-color: #ffffaf;
|
||||
}
|
||||
div.new {
|
||||
background-color: #cfffcf;
|
||||
}
|
||||
|
||||
div.refer {
|
||||
padding-left: 5%;
|
||||
padding-right: 5%;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
div.message {
|
||||
background-color:#fee;
|
||||
color:#000;
|
||||
}
|
||||
|
||||
img.xml {
|
||||
border:none;
|
||||
padding:1px;
|
||||
}
|
||||
a.small img {
|
||||
max-width:300px;
|
||||
}
|
||||
a.large img {
|
||||
max-width:600px;
|
||||
}
|
||||
div.sister {
|
||||
margin-right:1ex;
|
||||
background-color:inherit;
|
||||
}
|
||||
div.sister p {
|
||||
margin-top:0;
|
||||
}
|
||||
div.sister hr {
|
||||
display:none;
|
||||
}
|
||||
div.sister img {
|
||||
border:none;
|
||||
}
|
||||
|
||||
div.near, div.definition {
|
||||
background-color:#efe;
|
||||
}
|
||||
|
||||
div.sidebar {
|
||||
float:right;
|
||||
border:1px dotted #000;
|
||||
padding:0 1em;
|
||||
}
|
||||
div.sidebar ul {
|
||||
padding-left:1em;
|
||||
}
|
||||
|
||||
/* replacements, features */
|
||||
|
||||
ins {
|
||||
color: #b33;
|
||||
text-decoration: none;
|
||||
}
|
||||
acronym, abbr {
|
||||
letter-spacing:0.1em;
|
||||
font-variant:small-caps;
|
||||
}
|
||||
|
||||
/* Interlink prefix not shown */
|
||||
a .site, a .separator {
|
||||
display: none;
|
||||
}
|
||||
a cite { font:inherit; }
|
||||
/* browser borkage */
|
||||
textarea[name="text"] { width:97%; height:80%; }
|
||||
textarea[name="summary"] { width:97%; height:3em; }
|
||||
/* comments */
|
||||
textarea[name="aftertext"] { width:97%; height:10em; }
|
||||
div.commentshown {
|
||||
font-size: 12pt;
|
||||
padding: 2em 0;
|
||||
}
|
||||
div.commenthidden {
|
||||
display:none;
|
||||
}
|
||||
div.commentshown {
|
||||
display:block;
|
||||
}
|
||||
p.comment {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
div.comment {
|
||||
font-size: 14pt;
|
||||
}
|
||||
div.comment h2 {
|
||||
margin-top: 5em;
|
||||
}
|
||||
/* comment pages with username, homepage, and email subscription */
|
||||
.comment form span { display: block; }
|
||||
.comment form span label { display: inline-block; width: 10em; }
|
||||
/* IE sucks */
|
||||
.comment input#username,
|
||||
.comment input#homepage,
|
||||
.comment input#mail { width: 20em; }
|
||||
|
||||
/* cal */
|
||||
div.month { padding:0; margin:0 2ex; }
|
||||
body > div.month {
|
||||
float:right;
|
||||
background-color: inherit;
|
||||
border:solid thin;
|
||||
padding:0 1ex;
|
||||
}
|
||||
div.year > div.month {
|
||||
float:left;
|
||||
}
|
||||
div.footer {
|
||||
clear:both;
|
||||
}
|
||||
div.content div.month a.edit {
|
||||
color:inherit;
|
||||
font-weight:inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* history tables and other tables */
|
||||
table.history {
|
||||
border: none;
|
||||
}
|
||||
td.history {
|
||||
border: none;
|
||||
}
|
||||
|
||||
table.user {
|
||||
border: none;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 1em;
|
||||
margin: 1em 2em;
|
||||
}
|
||||
table.user tr td, table.user tr th {
|
||||
border: none;
|
||||
padding: 0.2em 0.5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.arab tr th {
|
||||
font-weight:normal;
|
||||
text-align:left;
|
||||
vertical-align:top;
|
||||
}
|
||||
table.arab, table.arab tr th, table.arab tr td {
|
||||
border:none;
|
||||
}
|
||||
th.nobreak {
|
||||
white-space:nowrap;
|
||||
}
|
||||
table.full { width:99%; margin-left:1px; }
|
||||
table.j td, table.j th, table tr td.j, table tr th.j, .j { text-align:justify; }
|
||||
table.l td, table.l th, table tr td.l, table tr th.l, .l { text-align:left; }
|
||||
table.r td, table.r th, table tr td.r, table tr th.r, .r { text-align:right; }
|
||||
table.c td, table.c th, table tr td.c, table tr th.c, .c { text-align:center; }
|
||||
table.t td { vertical-align: top; }
|
||||
td.half { width:50%; }
|
||||
td.third { width:33%; }
|
||||
|
||||
form table td { padding:5px; }
|
||||
|
||||
/* lists */
|
||||
dd { padding-bottom:0.5ex; }
|
||||
dl.inside dt { float:left; }
|
||||
/* search */
|
||||
div.search span.result { font-size:larger; }
|
||||
div.search span.info { font-size:smaller; font-style:italic; }
|
||||
div.search p.result { display:none; }
|
||||
|
||||
img.logo {
|
||||
float: right;
|
||||
margin: 0 0 0 1ex;
|
||||
padding: 0;
|
||||
border: 1px solid #000;
|
||||
opacity: 0.3;
|
||||
background-color:#ffe;
|
||||
}
|
||||
|
||||
/* images */
|
||||
|
||||
div.content a.feed img, div.journal a.feed img,
|
||||
div.content a img.smiley, div.journal a img.smiley, img.smiley,
|
||||
div.content a.inline img, div.journal a.inline img,
|
||||
div.content li a.image img, div.journal li a.image img {
|
||||
margin: 0; padding: 0; border: none;
|
||||
}
|
||||
div.image a img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
div.image span.caption {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.left { float:left; margin-right: 1em; }
|
||||
.right { float:right; margin-left: 1em; }
|
||||
.half a img { height: 50%; width: 50%; }
|
||||
div.left .left, div.right .right {
|
||||
float:none;
|
||||
}
|
||||
.center { text-align:center; }
|
||||
table.aside {
|
||||
float:right;
|
||||
width:40%;
|
||||
margin-left: 1em;
|
||||
padding: 1ex;
|
||||
border: 1px dotted #666;
|
||||
}
|
||||
table.aside td {
|
||||
text-align:left;
|
||||
}
|
||||
div.sidebar {
|
||||
float:right; width: 250px;
|
||||
text-align: right;
|
||||
border: none;
|
||||
margin: 1ex;
|
||||
}
|
||||
|
||||
.bigsidebar {
|
||||
float:right;
|
||||
width: 500px;
|
||||
border: none;
|
||||
margin-left: 1ex;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
dl.irc dt { width:20ex; float:left; text-align:right; clear:left; }
|
||||
dl.irc dt span.time { float:left; }
|
||||
dl.irc dd { margin-left:22ex; }
|
||||
|
||||
/* portrait */
|
||||
|
||||
div.footer, div.comment, hr { clear: both; }
|
||||
.portrait { float: left; font-size: small; margin-right: 1em; }
|
||||
.portrait a { color: #999; }
|
||||
|
||||
div.left { float:left; margin:1em; padding: 0.5em; }
|
||||
div.left p { display:table-cell; }
|
||||
div.left p + p { display:table-caption; caption-side:bottom; }
|
||||
|
||||
p.table a { float:left; width:20ex; }
|
||||
p.table + p { clear:both; }
|
||||
|
||||
/* no bleeding
|
||||
@media screen {
|
||||
div.content, div.rc {
|
||||
overflow:hidden;
|
||||
}
|
||||
} */
|
||||
|
||||
/* rss */
|
||||
|
||||
channel * { display: block; }
|
||||
|
||||
channel title {
|
||||
margin-top: 30pt;
|
||||
}
|
||||
copyright {
|
||||
font-size: 14pt;
|
||||
margin-top: 1em;
|
||||
}
|
||||
channel > link:before {
|
||||
font-size: 18pt;
|
||||
display: block;
|
||||
margin: 1em;
|
||||
padding: 0.5em;
|
||||
content: "This is an RSS feed, designed to be read in a feed reader.";
|
||||
color: red;
|
||||
border: 1px solid red;
|
||||
}
|
||||
link, license {
|
||||
font-size: 11pt;
|
||||
margin-bottom: 9pt;
|
||||
}
|
||||
username:before { content: "Last edited by "; }
|
||||
username:after { content: "."; }
|
||||
generator:before { content: "Feed generated by "; }
|
||||
generator:after { content: "."; }
|
||||
channel description {
|
||||
font-weight: bold;
|
||||
}
|
||||
item description {
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
docs, language,
|
||||
pubDate, lastBuildDate, ttl, guid, category, comments,
|
||||
docs, image title, image link,
|
||||
status, version, diff, history, importance {
|
||||
display: none;
|
||||
}
|
||||
347
css/bootstrap.css
vendored
Normal file
347
css/bootstrap.css
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
/* Public Domain
|
||||
Written by Alex Schroeder and Evgkeni Sampelnikof */
|
||||
textarea { width:100%; }
|
||||
h1 a { color: inherit }
|
||||
div.journal h1 { font-size:large; }
|
||||
table { margin-bottom: 1em; }
|
||||
|
||||
div.diff { padding-left:5%; padding-right:5%; }
|
||||
div.old { background-color:#FFFFAF; }
|
||||
div.new { background-color:#CFFFCF; }
|
||||
|
||||
img.portrait { float: left; clear: left; margin: 1ex; border:#999 1px solid; }
|
||||
div.footer, div.comment, hr { clear: both; }
|
||||
div.portrait { float: left; clear: left; font-size: xx-small; margin-right: 1em; }
|
||||
div.portrait img.portrait { float: none; margin: 0; }
|
||||
div.portrait a { text-decoration: none; color: #999; }
|
||||
div.color {
|
||||
clear: both;
|
||||
padding: 1ex 2em;
|
||||
margin: 0 -1em;
|
||||
box-shadow: inset 40px 0px 20px -20px #EEEEEE,
|
||||
inset -40px 0px 20px -20px #EEEEEE;
|
||||
}
|
||||
|
||||
.left { float:left; margin-right:1em; }
|
||||
.right { float:right; margin-left:1em; }
|
||||
|
||||
div.two, div.one {
|
||||
color: #444;
|
||||
background-color: #f8f8f8;
|
||||
margin: 7px -1em;
|
||||
box-shadow: inset 40px 0px 20px -20px #EEEEEE,
|
||||
inset -40px 0px 20px -20px #EEEEEE,
|
||||
0px 8px 4px -8px #ccc,
|
||||
0px -6px 4px -8px #ccc;
|
||||
}
|
||||
|
||||
.irc .time { display: none; }
|
||||
dl.irc dt { float: left; text-align: right; width: 13ex; }
|
||||
dl.irc dd { margin-left: 15ex; display: block; }
|
||||
|
||||
div.toc {
|
||||
background-color: #FAFAFA;
|
||||
border: 1px solid #dddddd;
|
||||
font-family: sans-serif;
|
||||
font-size: 80%;
|
||||
line-height: 90%;
|
||||
margin: 3em 0 1em;
|
||||
padding: 1em 0px 0px 1em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
div.toc li {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.ell .toc li {
|
||||
display: inline;
|
||||
padding-right: 1em;
|
||||
}
|
||||
div.letter { column-count: 3; -webkit-column-count: 3; -moz-column-count: 3 }
|
||||
|
||||
.footer .edit.bar {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.specialdays {
|
||||
line-height: 1em; /* has no effect: set for div.header instead? */
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.footer .time {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #888;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.footer .legal {
|
||||
text-align: justify;
|
||||
-moz-text-align-last: center;
|
||||
text-align-last: center;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4em;
|
||||
margin: 0 120px 0;
|
||||
padding: 1em 0 0;
|
||||
}
|
||||
.footer .legal a {
|
||||
color: #888;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.translation.bar {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* .include.WikiLanguageMenu could share those styles,
|
||||
(altough it might be better to leave it left-aligned) */
|
||||
|
||||
.translation.bar a:nth-child(n+2) {
|
||||
border-left: 1px solid #999;
|
||||
}
|
||||
|
||||
.translation.bar a {
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.navbar .nav > li > a.brand {
|
||||
color: #C76A0D;
|
||||
padding: 5px 8px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #C76A0D;
|
||||
}
|
||||
a:hover {
|
||||
color: #8F3E0F;
|
||||
}
|
||||
|
||||
body {
|
||||
word-wrap: break-word;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
background-color: #EEEEEE;
|
||||
color: #000;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: #ccc 1px solid;
|
||||
border-bottom: #fff 1px solid;
|
||||
}
|
||||
|
||||
.footer_wrapper {
|
||||
background: #EEEEEE;
|
||||
background: linear-gradient(to bottom, #EEEEEE, #CCCCCC);
|
||||
padding-bottom: 20px;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.footer.container hr:first-child {
|
||||
display: none;
|
||||
}
|
||||
.footer hr {
|
||||
margin: 10px 100px 0;
|
||||
}
|
||||
.footer.container {
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
background: radial-gradient(
|
||||
50% 8px at top,
|
||||
rgba(0, 0, 0, 0.3) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
box-shadow: 0 -1px 2px -2px white;
|
||||
/* border-top: 1px #ccc solid; */
|
||||
}
|
||||
body, li {
|
||||
line-height: 2em;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
text-shadow: 1px 1px white;
|
||||
}
|
||||
.navbar-inner {
|
||||
border-radius: 0 0 4px 4px;
|
||||
border-width: 0 1px 1px;
|
||||
}
|
||||
.navbar .nav > li > a {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
label[for="searchlang"], input#searchlang {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width:480px){
|
||||
h1 { font-size: 1.8em; }
|
||||
.navbar .nav > li > a {
|
||||
padding: 0px 2px;
|
||||
line-height: 10px;
|
||||
}
|
||||
/* hide CC logo */
|
||||
.footer .licence {
|
||||
display: none;
|
||||
}
|
||||
/* make legal foo*/
|
||||
.footer .legal {
|
||||
margin: 5px 5px 5px;
|
||||
}
|
||||
.footer .bar a {
|
||||
margin: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Right-alignment. Will make it harder to achieve responsive behaviour:
|
||||
|
||||
twitter.github.com/bootstrap/components.html#navbar
|
||||
Look for "Responsive navbar" heading.
|
||||
|
||||
If this is undesirable, remove the lines with the "RA" comment;
|
||||
*/
|
||||
|
||||
.navbar .nav {
|
||||
text-align: right; /* RA */
|
||||
*text-align: left; /* RA */
|
||||
width: 100%; /* RA */
|
||||
}
|
||||
|
||||
.navbar .nav > li:first-child {
|
||||
float: left; /* RA */
|
||||
}
|
||||
|
||||
.navbar .nav > li {
|
||||
display: inline-block; /* RA */
|
||||
float: none; /* RA */
|
||||
*float: left;
|
||||
*display: inline;
|
||||
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
textarea:focus,
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="datetime"]:focus,
|
||||
input[type="datetime-local"]:focus,
|
||||
input[type="date"]:focus,
|
||||
input[type="month"]:focus,
|
||||
input[type="time"]:focus,
|
||||
input[type="week"]:focus,
|
||||
input[type="number"]:focus,
|
||||
input[type="email"]:focus,
|
||||
input[type="url"]:focus,
|
||||
input[type="search"]:focus,
|
||||
input[type="tel"]:focus,
|
||||
input[type="color"]:focus,
|
||||
.uneditable-input:focus {
|
||||
border-color: rgba(236,160,73,.8);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),
|
||||
0 0 8px rgba(236,160,73,.6);
|
||||
}
|
||||
|
||||
/* IE7/8 Flexibility */
|
||||
|
||||
.container,
|
||||
.navbar-static-top .container,
|
||||
.navbar-fixed-top .container,
|
||||
.navbar-fixed-bottom .container {
|
||||
width: auto;
|
||||
max-width: 940px;
|
||||
}
|
||||
|
||||
/* Don't widen the layout past 940 */
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container,
|
||||
.navbar-static-top .container,
|
||||
.navbar-fixed-top .container,
|
||||
.navbar-fixed-bottom .container {
|
||||
width: 940px;
|
||||
}
|
||||
}
|
||||
|
||||
div.comment {
|
||||
background: radial-gradient(
|
||||
50% 8px at top,
|
||||
rgba(0, 0, 0, 0.3) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: 0 -1px 2px -2px white;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
div.comment p:nth-child(2) {
|
||||
color: #666;
|
||||
/* line-height: 15px; */
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.comment p:nth-child(1) {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.comment textarea {
|
||||
width: 100%;
|
||||
*width: auto;
|
||||
resize: vertical;
|
||||
*resize: both;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* highlighting source code */
|
||||
|
||||
span.builtin { color: #483d8b; } /* DarkSlateBlue */
|
||||
span.comment { color: #b22222; } /* Firebrick */
|
||||
span.constant { color: #008b8b; } /* DarkCyan */
|
||||
span.function { color: #0000ff; } /* Blue1 */
|
||||
span.keyword { color: #7f007f; } /* Purple */
|
||||
span.string { color: #8b475d; } /* VioletRed4 */
|
||||
span.type { color: #228b22; } /* ForestGreen */
|
||||
span.warning { color: #ff0000; font-weight: bold; } /* Red1 */
|
||||
span.comment span,
|
||||
span.string span { color: inherit; }
|
||||
span.comment span.important.constant,
|
||||
span.string span.important.constant { color: #008b8b; }
|
||||
|
||||
/* old: Equivalent to Output::HTML */
|
||||
|
||||
span.linecomment { color: #b22222; } /* firebrick */
|
||||
span.blockcomment { color: #b22222; } /* firebrick */
|
||||
span.prepro { color: purple; }
|
||||
span.select { font-weight: bold; }
|
||||
span.quote { color: #8b475d; } /* VioletRed4 */
|
||||
span.category_1 { color: teal; }
|
||||
span.category_2 { color: blue; }
|
||||
span.category_3 { color: blue; }
|
||||
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Local Variables: */
|
||||
/* css-indent-offset: 4 */
|
||||
/* End: */
|
||||
@@ -115,6 +115,7 @@ div.toc h2 {
|
||||
|
||||
/* get rid of useless "10 results found" when using indexed search. */
|
||||
div.search p.result { display:none; }
|
||||
label[for="searchlang"], input#searchlang { display: none; }
|
||||
|
||||
form.tiny, form.tiny p {
|
||||
display:inline;
|
||||
|
||||
384
css/light.css
Normal file
384
css/light.css
Normal file
@@ -0,0 +1,384 @@
|
||||
/* This file is in the public domain. */
|
||||
|
||||
/* Esteban is nice, but bold is not so nice, and on Windows it suffers.
|
||||
@import url(http://fonts.googleapis.com/css?family=Esteban&subset=latin,latin-ext);
|
||||
|
||||
For campaignwiki.org, we need to use the same URL in the config file when
|
||||
calling wkhtmltopdf.
|
||||
|
||||
@import url(https://fonts.googleapis.com/css?family=Noticia+Text:400,400italic,700italic,700&subset=latin,latin-ext); */
|
||||
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Noticia Text';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Symbola';
|
||||
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff');
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Noticia Text", Symbola, serif;
|
||||
font-size: 14pt;
|
||||
color: #000;
|
||||
background-color: #eed;
|
||||
margin:1em 2em;
|
||||
}
|
||||
|
||||
textarea, pre, code, tt {
|
||||
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, Symbola;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background-color: white;
|
||||
font-family: Times, serif;
|
||||
font-size:10pt;
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone */
|
||||
|
||||
@media only screen and (max-device-width: 480px) {
|
||||
img { max-width: 480px !important; }
|
||||
}
|
||||
|
||||
/* iPad */
|
||||
|
||||
@media only screen and (min-device-width: 481px) and (max-device-width: 900px) {
|
||||
body { font-size: 150%; }
|
||||
textarea,input { font-size: 100%; }
|
||||
img { max-width: 550px !important; }
|
||||
}
|
||||
|
||||
/* general */
|
||||
.browse { min-height: 3em; }
|
||||
.header form, .header p { margin: 0; }
|
||||
/* hide the buttons but don't use display:none because of
|
||||
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
|
||||
.header input[type="submit"] { position: absolute; visibility: hidden; }
|
||||
.header input { width: 5em; font-size: 80%; }
|
||||
.footer { clear:both; font-size: 90%; }
|
||||
.content input { font-size: 80%; line-height: 125%; }
|
||||
|
||||
/* comments, footer */
|
||||
div.commentshown {
|
||||
padding-bottom: 1ex;
|
||||
padding-left: 2em;
|
||||
border-left: 2px solid black;
|
||||
font-size: smaller;
|
||||
}
|
||||
div.commenthidden { display:none; }
|
||||
div.commentshown { display:block; }
|
||||
/* comment pages with username, homepage, and email subscription */
|
||||
.comment span { display: block; }
|
||||
.comment span label {
|
||||
display: inline-block; width: 10em;
|
||||
}
|
||||
input#mail, input#homepage, input#username {
|
||||
display: inline-block; width: 20em;
|
||||
}
|
||||
|
||||
/* titles */
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
font-size: 150%;
|
||||
padding: 1em 0;
|
||||
}
|
||||
h1 a:link, h1 a:visited {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
font-size: 130%;
|
||||
padding: 1em 0;
|
||||
clear: both;
|
||||
}
|
||||
@media print {
|
||||
h1 a, h2 a, h3 a, h4 a { font-style: normal; }
|
||||
}
|
||||
|
||||
/* links */
|
||||
a:link {
|
||||
color: #851;
|
||||
background-color: inherit;
|
||||
}
|
||||
a:visited {
|
||||
color: #542;
|
||||
background-color: inherit;
|
||||
}
|
||||
a:active {
|
||||
color:#a41;
|
||||
background-color: inherit;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
font-size: 150%;
|
||||
cursor: pointer;
|
||||
padding: 0.3em 0.5em;
|
||||
text-shadow: 0px -1px 0px #ccc;
|
||||
background-color: #cfa;
|
||||
border: 1px solid #9d8;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 1px 3px white inset,
|
||||
0px 1px 3px black;
|
||||
}
|
||||
.button a {
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
.bar a { padding-right: 1em; }
|
||||
@media print {
|
||||
a, a:link, a:visited {
|
||||
color:#000;
|
||||
text-decoration:none;
|
||||
font-weight: normal;
|
||||
}
|
||||
a.edit, div.footer, form, span.gotobar, a.number span { display:none; }
|
||||
a[class="url number"]:after, a[class="inter number"]:after {
|
||||
content:"[" attr(href) "]";
|
||||
}
|
||||
a[class="local number"]:after { content:"[" attr(title) "]"; }
|
||||
img[smiley] { line-height: inherit; }
|
||||
}
|
||||
|
||||
/* edit paragraphs */
|
||||
a.pencil {
|
||||
padding-left: 1ex;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
visible: hidden;
|
||||
transition: visibility 0s 1s, opacity 1s linear;
|
||||
opacity: 0;
|
||||
}
|
||||
*:hover > a.pencil {
|
||||
visible: visible;
|
||||
transition: opacity .5s linear;
|
||||
opacity: 1;
|
||||
}
|
||||
@media print {
|
||||
a.pencil {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
table a.pencil {
|
||||
position: absolute;
|
||||
right: inherit;
|
||||
}
|
||||
|
||||
/* table of contents */
|
||||
.toc {
|
||||
font-size: smaller;
|
||||
border-left: 1em solid #886;
|
||||
}
|
||||
.toc ol {
|
||||
list-style-type: none;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.toc a {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* images with links, captions, etc */
|
||||
div.image { display: inline; margin: 1em; font-size: 90%; text-align: center; }
|
||||
.left { float: left; margin-right: 1em; }
|
||||
.right { float: right; margin-left: 1em; }
|
||||
div.right .right { float: none; }
|
||||
div.left .left { float: none; }
|
||||
.caption { padding: 0 1em; }
|
||||
.license { font-size: small; }
|
||||
.aside {
|
||||
font-size: small;
|
||||
width: 30%;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.aside img.smiley { height: 1em; }
|
||||
.narrow {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
a img { border: 1px solid #333; }
|
||||
.fit img { width: 80%; text-align: center; margin: 2em 8%; }
|
||||
.half img { width: 50%; height: 50%; text-align: center; margin: 2em 8%; }
|
||||
.noborder img { border: none; }
|
||||
.twenty img { max-width: 20em; }
|
||||
img.logo {
|
||||
float: right;
|
||||
clear: right;
|
||||
border-style:none;
|
||||
margin-left: 1em;
|
||||
margin-bottom: 1ex;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
/* fancy bold underline */
|
||||
em.underline { font-weight: bold; }
|
||||
|
||||
/* editing, previewing */
|
||||
textarea { width:100%; }
|
||||
div.edit { padding-right: 1em; }
|
||||
div.diff { padding-left:5%; padding-right:5%; }
|
||||
div.old { background-color:#FFFFAF; }
|
||||
div.new { background-color:#CFFFCF; }
|
||||
/* div.message { background-color:#FEE; } */
|
||||
div.message {
|
||||
background-color: inherit;
|
||||
font-size: smaller;
|
||||
}
|
||||
table.history { border-style:none; }
|
||||
td.history { border-style:none; }
|
||||
span.result { font-size:larger; }
|
||||
span.info { font-size:smaller; font-style:italic; }
|
||||
div.rc hr { display: none; }
|
||||
div.rc li { padding-bottom: 0.5em; }
|
||||
|
||||
/* Tables */
|
||||
table.user {
|
||||
margin: 1em 0;
|
||||
padding: 0 1em;
|
||||
border-top: 1px solid black;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
div.aside table.user {
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
table.user td, table.user th {
|
||||
border-style: none;
|
||||
padding:5px 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.user th { font-weight:bold; }
|
||||
table.user td.r { text-align:right; }
|
||||
table.user td.l { text-align:left; }
|
||||
table.user td.c { text-align:center; }
|
||||
table.user td.j { text-align:justify; }
|
||||
table.user td.mark { background-color:yellow; }
|
||||
tr:empty { display: block; height: 0.5em; }
|
||||
@media print {
|
||||
table {
|
||||
font-size: 9pt;
|
||||
margin: 0;
|
||||
}
|
||||
table.user td, table.user th {
|
||||
padding: 0 1ex;
|
||||
}
|
||||
}
|
||||
|
||||
/* Calendar */
|
||||
div.month { margin:0; padding:0; font-size:x-small; float:right; }
|
||||
div.content div.month { float:none; }
|
||||
div.year div.month { float:left; font-size:medium; padding:1ex; }
|
||||
div.month pre { margin:0; padding:0 0 0 1ex; }
|
||||
div.month a { text-decoration:none; font: inherit; }
|
||||
div.month span.title a { font: inherit; }
|
||||
/* no difference between a.exact and a.collection */
|
||||
div.month a.local { font-weight: bold; }
|
||||
div.month a.local:link { color: #562; }
|
||||
div.month a.local:visited { color: #542; }
|
||||
div.month a.today { background-color: #faa; }
|
||||
div.month span.title a.local { font-weight: normal; color: #842; }
|
||||
@media print {
|
||||
div.month { display: none; }
|
||||
div.year div.month { display: block; }
|
||||
div.year div.month a { display: inline; }
|
||||
}
|
||||
250
css/oddmuse-2014.css
Normal file
250
css/oddmuse-2014.css
Normal file
@@ -0,0 +1,250 @@
|
||||
@font-face {
|
||||
font-family: 'Gentium Basic';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Gentium Basic'), local('GentiumBasic'), url(/fonts/GenBasR.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gentium Basic';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Gentium Basic Bold'), local('GentiumBasic-Bold'), url(/fonts/GenBasB.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gentium Basic';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Gentium Basic Italic'), local('GentiumBasic-Italic'), url(/fonts/GenBasI.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gentium Basic';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Gentium Basic Bold Italic'), local('GentiumBasic-BoldItalic'), url(/fonts/GenBasBI.woff) format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Gentium Plus';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Gentium Plus'), local('GentiumPlus'), url(/fonts/GentiumPlus-R.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gentium Plus';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Gentium Plus Italic'), local('GentiumPlus-Italic'), url(/fonts/GentiumPlus-I.woff) format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Symbola';
|
||||
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff') url('/fonts/Symbola.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
background:#fff;
|
||||
padding:2% 5%;
|
||||
margin:0;
|
||||
font-family: "Gentium Basic", "Gentium Plus", "Symbola", serif;
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
div.header h1 {
|
||||
margin-top:2ex;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #d88;
|
||||
}
|
||||
|
||||
div.header h1 a:hover, h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover,
|
||||
a:hover, span.caption a.image:hover {
|
||||
background:#fee;
|
||||
}
|
||||
|
||||
img.logo {
|
||||
float: right;
|
||||
clear: right;
|
||||
border-style:none;
|
||||
background-color:#fff;
|
||||
}
|
||||
|
||||
img {
|
||||
padding: 0.5em;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
a.image:hover {
|
||||
background:inherit;
|
||||
}
|
||||
|
||||
a.image:hover img {
|
||||
background:#fee;
|
||||
}
|
||||
|
||||
/* a.definition soll aussehen wie h2 */
|
||||
h2, p a.definition {
|
||||
display:block;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
/* Such Link im h1 soll nicht auffallen. */
|
||||
h1, h2, h3, h4, h1 a, h1 a:visited, p a.definition {
|
||||
color:#666;
|
||||
font-size: 30pt;
|
||||
font-weight: normal;
|
||||
margin: 4ex 0 1ex 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
div.diff {
|
||||
padding: 1em 3em;
|
||||
}
|
||||
div.old {
|
||||
background-color:#FFFFAF;
|
||||
}
|
||||
div.new {
|
||||
background-color:#CFFFCF;
|
||||
}
|
||||
div.old p, div.new p {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
div.refer { padding-left:5%; padding-right:5%; font-size:smaller; }
|
||||
div[class="content refer"] p { margin-top:2em; }
|
||||
div.content div.refer hr { display:none; }
|
||||
div.content div.refer { padding:0; font-size:medium; }
|
||||
div.content div.refer p { margin:0; }
|
||||
div.refer a { display:block; }
|
||||
table.history { border-style:none; }
|
||||
td.history { border-style:none; }
|
||||
|
||||
table.user {
|
||||
border-style: none;
|
||||
margin-left: 3em;
|
||||
}
|
||||
table.user tr td {
|
||||
border-style: none;
|
||||
padding:0.5ex 1ex;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight:bold;
|
||||
}
|
||||
dd {
|
||||
margin-bottom:1ex;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width:100%;
|
||||
height:80%;
|
||||
font-size: 12pt;
|
||||
}
|
||||
textarea#summary { height: 3em; }
|
||||
input {
|
||||
font-size: 12pt;
|
||||
}
|
||||
div.image span.caption {
|
||||
margin: 0 1em;
|
||||
}
|
||||
li img, img.smiley, .noborder img {
|
||||
border:none;
|
||||
padding:0;
|
||||
margin:0;
|
||||
background:#fff;
|
||||
color:#000;
|
||||
}
|
||||
/* Google +1 */
|
||||
a#plus1 img {
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.header img, div.footer img { border:0; padding:0; margin:0; }
|
||||
/* No goto bar at the bottom. */
|
||||
.footer .gotobar, .footer .edit br { display: none; }
|
||||
|
||||
.left { float:left; }
|
||||
.right { float:right; }
|
||||
div.left .left, div.right .right {
|
||||
float:none;
|
||||
}
|
||||
.center { text-align:center; }
|
||||
|
||||
span.author {
|
||||
color: #501;
|
||||
}
|
||||
span.bar a {
|
||||
padding-right:1ex;
|
||||
}
|
||||
|
||||
.rc .author {
|
||||
color: #655;
|
||||
}
|
||||
|
||||
.rc strong {
|
||||
font-weight: normal;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.rc li {
|
||||
position:relative;
|
||||
padding: 1ex 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border:none;
|
||||
color:black;
|
||||
background-color:#000;
|
||||
height:2px;
|
||||
margin-top:2ex;
|
||||
}
|
||||
|
||||
div.footer hr {
|
||||
height:4px;
|
||||
margin: 2em 0 1ex 0;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
div.content > div.comment {
|
||||
border-top: none;
|
||||
padding-top: none;
|
||||
border-left: 1ex solid #bbb;
|
||||
padding-left: 1ex;
|
||||
}
|
||||
|
||||
div.wrapper > div.comment {
|
||||
border-top: 2px solid #000;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 0.5em;
|
||||
margin-left: 1em;
|
||||
margin-right: 2em;
|
||||
white-space: pre;
|
||||
overflow:hidden;
|
||||
white-space: pre-wrap; /* CSS 3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
|
||||
tt, pre, code {
|
||||
font-size: 80%;
|
||||
};
|
||||
@@ -17,12 +17,16 @@ package OddMuse;
|
||||
|
||||
AddModuleDescription('big-brother.pl', 'Big Brother Extension');
|
||||
|
||||
use vars qw($VisitorTime);
|
||||
use vars qw($VisitorTime @BigBrotherSecretParameters);
|
||||
|
||||
my $US = "\x1f";
|
||||
|
||||
$VisitorTime = 7200; # keep visitor data arround for 2 hours.
|
||||
|
||||
# normal password parameter from wiki.pl
|
||||
# password parameters from login.pl
|
||||
@BigBrotherSecretParameters = qw(pwd pwd1 pwd2 oldpwd);
|
||||
|
||||
push(@MyAdminCode, \&BigBrotherVisitors);
|
||||
|
||||
sub BigBrotherVisitors {
|
||||
@@ -47,6 +51,13 @@ sub AddRecentVisitor {
|
||||
$ts++ while $entries{$ts};
|
||||
my $action = GetParam('action', 'browse');
|
||||
my $id = GetId(); # script/p/q -> q
|
||||
my %params = map { $_ => 1 } $q->param;
|
||||
for $bad (@BigBrotherSecretParameters) {
|
||||
delete $params{$bad};
|
||||
}
|
||||
my $url = ScriptUrl(join(';', "action=$action;id=" . UrlEncode($id),
|
||||
map { $_ . '=' . UrlEncode(GetParam($_)) }
|
||||
keys %params));
|
||||
my $url = $q->url(-path_info=>1,-query=>1);
|
||||
my $download = GetParam('action', 'browse') eq 'download'
|
||||
|| GetParam('download', 0)
|
||||
|
||||
@@ -16,19 +16,23 @@ package OddMuse;
|
||||
|
||||
AddModuleDescription('div-foo.pl', 'Div Foo Extension');
|
||||
|
||||
use vars qw($DivFooPrefix);
|
||||
$DivFooPrefix = 'foo_';
|
||||
|
||||
push(@MyRules, \&DivFooRule);
|
||||
|
||||
sub DivFooRule {
|
||||
if (m/\G\<([\w ]+)\>\s*\n/cg) {
|
||||
return CloseHtmlEnvironment('p') . AddHtmlEnvironment('div', qq{class="$1"});
|
||||
if (m/\G \< ([a-z-_][a-z-_ ]+[a-z-_]) \> \s*\n /cgx) {
|
||||
return CloseHtmlEnvironment('p') . AddHtmlEnvironment('div', 'class="' . join(' ', map {"$DivFooPrefix$_"} split /\s+/, $1) . '"');
|
||||
}
|
||||
if (m/\G\<([\w ]+)\>/cg) {
|
||||
return AddHtmlEnvironment('span', qq{class="$1"});
|
||||
if (m/\G \< ([a-z-_][a-z-_ ]+[a-z-_]) (\?(.*?(?=\>)))? \> /cgx) {
|
||||
my $title = $3 ? ' title="' . QuoteHtml($3) . '"' : '';
|
||||
return AddHtmlEnvironment('span', 'class="' . join(' ', map {"$DivFooPrefix$_"} split /\s+/, $1) . '"' . $title);
|
||||
}
|
||||
if (m/\G\<\/\/\>/cg) {
|
||||
if (m/\G \< \/ \/ \> /cgx) {
|
||||
return CloseHtmlEnvironment('div') . (InElement('div') ? '' : AddHtmlEnvironment('p'));
|
||||
}
|
||||
if (m/\G\<\/\>/cg) {
|
||||
if (m/\G \< \/ \> /cgx) {
|
||||
return CloseHtmlEnvironment('span');
|
||||
}
|
||||
return undef;
|
||||
|
||||
75
modules/edit-paragraphs.js
Normal file
75
modules/edit-paragraphs.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Copyright 2014 Alex Schroeder <alex@gnu.org>
|
||||
based on http://git.savannah.gnu.org/cgit/oddmuse.git/plain/plinks.js
|
||||
for more information see http://oddmuse.org/wiki/Purple_Numbers_Extension
|
||||
based on http://simon.incutio.com/archive/2004/05/30/plinks#p-13
|
||||
Copyright 2004 Simon Willison
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function add_edit_links() {
|
||||
/* Only show edit links on ordinary pages: They either use
|
||||
* path_info or keywords in the URL, not parameters. */
|
||||
if (/=/.test(document.location.href)) {
|
||||
return;
|
||||
}
|
||||
// find all the pencil links
|
||||
var links = new Array;
|
||||
var elem = document.getElementsByTagName('a');
|
||||
for (var i = 0; i < elem.length; i++) {
|
||||
var atr = elem[i].getAttribute('class');
|
||||
if (atr != null) {
|
||||
var classes = atr.split(" ");
|
||||
for (var j = 0; j < classes.length; j++) {
|
||||
if (classes[j] == 'pencil') {
|
||||
links.push(elem[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// make them invisible
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
var func = function(thislink) {
|
||||
return function() {
|
||||
if (thislink.style.visibility == "visible") {
|
||||
thislink.style.transition = "visibility 0s 1s, opacity 1s linear";
|
||||
thislink.style.visibility = "hidden";
|
||||
thislink.style.opacity = "0";
|
||||
} else {
|
||||
thislink.style.transition = "opacity 1s linear";
|
||||
thislink.style.visibility = "visible";
|
||||
thislink.style.opacity = "1";
|
||||
};
|
||||
}
|
||||
};
|
||||
link.style.transition = "visibility 0s 1s, opacity 1s linear";
|
||||
link.style.visibility = "hidden";
|
||||
link.style.opacity = "0";
|
||||
link.parentNode.onclick = func(link);
|
||||
}
|
||||
}
|
||||
|
||||
function add_load_event(func) {
|
||||
var oldonload = window.onload;
|
||||
if (typeof window.onload != 'function') {
|
||||
window.onload = func;
|
||||
} else {
|
||||
window.onload = function() {
|
||||
oldonload();
|
||||
func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_load_event(add_edit_links);
|
||||
213
modules/edit-paragraphs.pl
Normal file
213
modules/edit-paragraphs.pl
Normal file
@@ -0,0 +1,213 @@
|
||||
# Copyright (C) 2014 Alex Schroeder <alex@gnu.org>
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package OddMuse;
|
||||
|
||||
AddModuleDescription('edit-paragraph.pl', 'Edit Paragraphs Extension');
|
||||
|
||||
# edit icon
|
||||
# http://publicdomainvectors.org/en/free-clipart/Pencil-vector-icon/9221.html
|
||||
# q{<img width="30" height="30" title="" alt="edit" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAE2UlEQVRIx53WW0sbaRwG8GcmSsxhdTKT5K60FFqa4CmKh96uq62gtR563butN1KNQSgESqFemBjr2mKjMRpBt1uln2FJe9ObHsyu3d2yyH6EFoTVhr7PXrQTJslMmuwLA0mYvL95/vOebPjSJJMLJZ+t7iu9pw9AHEAQwAGAE4v/Wnasf5dNHsQK/R7A37IsEwAB/ATAY9W/VUpjh9Wi7wHw5s2b4vz588KAKyb9fDMVqniwPgB/AWA0GhX5fF68fftWBAIBHV82wf83bET/AMDJyUnx4cMHwa/t1atXRry07AW0FlhvPwA4dDgclCRJtLe3ixcvXtDYXr9+bcRXjLhcQ+JS9LeGhgbeunVLXL58WQBgIBDg8+fPy/BgMKjjD3VcrrHUOppraGjg3NycWFpaEmfOnKGiKLTC37x5I1paWnT8US2w3voBHDgcDkYiEZFIJITP56PH42E0GmVLSwsB8NKlS8xms0V4JpMRdXV1+lSrWNbS3/sBHLhcLs7OzopEIiH8fj8VReHy8jIjkQhdLhfPnTtXhudyOdHV1UVJkgSAPVQxV4uSulwuhsNhsbi4KPx+P1VV5dLSEsPhMGVZZnNzMxcWFnj27FkCYDAY5P7+vmhra9OT/gLAWe0yOADgwO12c2ZmpoBqmsbFxUWGw2HabDY2Nzdza2uLN27coCzL7Ojo0BPq6C4Ad7Ur0gCAnNvtLkrq9XoZj8cLSVtbW7mxscHx8XEC4NDQEBcWFoSqqjq6DeC7ass7ACBXWl6v18tYLFZA29vbub6+zomJCQLg8PAwt7e3RW9vL7+u22kATWYrlxl6BcDvTqeTkUhExONx4fP5CklnZ2dps9kYCoWYTCYL6PXr15lOp0V3d7eeNGWxUVii7xwOB+fm5spQPWlHR0cROjY2xlQqJXp6enQ0CUD71u5kRP+02+28c+eOiMfj9Hq91DSNiUSCMzMzpuj4+DjX1tZEb2+vjq4C8FZ4pUXoVQDv6+vrGY1GRSwWE5qmUVVVPnjwoICGQiGura0VBtLExASTyaQRfQTAV+U4wlV9E797966IxWJUVZWKonBlZYXT09OUZZltbW1MpVKV0IeSJPnN3qkZPAjgCADv3bsnYrEYPR4PGxsbubq6ytu3b1OSJLa2tjKdTnNsbMyqvCsmqGWprwD4R9/E79+/z6amJrrdbq6vr3NqaqqwImUyGY6OjhIAR0dHSwfSyjeSlrV3dXV1nJ+fFx8/fuSTJ0/Y2NjIjY0NTk1NUZIkBgIB7uzscGRkhAA4MjLCzc1N45R5KEmSr8LZzbRRURTx6dMnkuTx8TGz2SwnJycJgBcvXuTTp0957dq1osWhq6vLOJAqjl6rB6CqqoIkhfhyasnn80yn07xw4QL39/c5PDxMABwcHOTu7q7o7Ow0Thmt2tFbBmuaRiMshODJyQlfvnzJoaEhAuDAwAD39vZEKBQyLg6eGk6h5XB9fT0zmUwRTpKnp6d8/Pgx+/v7+ezZM+PWlrI4sko1lfrre+bm5mYZ/vnzZ2azWREMBnU0XbLLSFVureawjm9tbRUdV3K5nAgEAgQgAGQAuGpALRNLBlgCAKfTib6+PtjtdpyenvLw8FA6OjoSAH4G8COAfyuVzyKYafvVmNrkygPYAWCvMWnFUv8Hwmcq2TCQ4MwAAAAASUVORK5CYII=" />};
|
||||
|
||||
our $EditParagraphPencil = '✎';
|
||||
|
||||
# Allow editing of substrings
|
||||
|
||||
$Action{'edit-paragraph'} = \&DoEditParagraph;
|
||||
|
||||
sub DoEditParagraph {
|
||||
my $id = UnquoteHtml(GetParam('title', ''));
|
||||
UserCanEditOrDie($id);
|
||||
|
||||
my $old = UnquoteHtml(GetParam('paragraph', ''));
|
||||
$old =~ s/\r//g;
|
||||
return DoEdit($id) unless $old;
|
||||
|
||||
my $around = GetParam('around', undef);
|
||||
|
||||
# Find text to edit
|
||||
my $new = GetParam('text', '');
|
||||
OpenPage($id);
|
||||
if ($new) {
|
||||
my $myoldtime = GetParam('oldtime', ''); # maybe empty!
|
||||
my $text;
|
||||
if ($myoldtime and $myoldtime != $LastUpdate) {
|
||||
($text) = GetTextAtTime($myoldtime);
|
||||
} else {
|
||||
$text = $Page{text};
|
||||
}
|
||||
|
||||
my $done;
|
||||
if ($around) {
|
||||
# The tricky part is that the numbers refer to the HTML quoted text. What a pain.
|
||||
my $qold = QuoteHtml($old);
|
||||
my $qtext = QuoteHtml($text);
|
||||
|
||||
if (substr($qtext, $around - length($qold), length($qold)) eq $qold) {
|
||||
$text = UnquoteHtml(substr($qtext, 0, $around - length($qold))
|
||||
. QuoteHtml($new) . substr($qtext, $around));
|
||||
$done = 1;
|
||||
}
|
||||
} else {
|
||||
# simple case, just do it
|
||||
my $search_term = quotemeta($old);
|
||||
$done = $text =~ s/$search_term/$new/;
|
||||
}
|
||||
|
||||
if ($done) {
|
||||
SetParam('text', UnquoteHtml($text));
|
||||
return DoPost($id);
|
||||
} else {
|
||||
$text = substr($text, 0, $around)
|
||||
. "\n### around here ###\n"
|
||||
. substr($text, $around)
|
||||
if $around;
|
||||
ReportError(T('Could not identify the paragraph you were editing'),
|
||||
'500 INTERNAL SERVER ERROR',
|
||||
undef,
|
||||
$q->p(T('This is the section you edited:'))
|
||||
. $q->pre(QuoteHtml($old))
|
||||
. $q->p(T('This is the current page:'))
|
||||
. $q->pre($text));
|
||||
}
|
||||
}
|
||||
print GetHeader('', Ts('Editing %s', NormalToFree($id)));
|
||||
print $q->start_div({-class=>'content edit paragraph'});
|
||||
my $form = GetEditForm($id, undef, $old);
|
||||
my $param = GetHiddenValue('paragraph', $old);
|
||||
$param .= GetHiddenValue('action', 'edit-paragraph'); # add action
|
||||
$param .= GetHiddenValue('around', $around); # add around position
|
||||
$form =~ s!</form>!$param</form>!;
|
||||
print $form;
|
||||
print $q->end_div();
|
||||
PrintFooter($id, 'edit');
|
||||
}
|
||||
|
||||
# When PrintWikiToHTML is called for the current revision of a page we
|
||||
# initialize our data structure. The data structure simply divides the
|
||||
# page up into blocks based on what one would like to edit. By
|
||||
# default, that's just paragraph breaks and list items. When using
|
||||
# Creole, ordered list items and table rows are added.
|
||||
|
||||
my @EditParagraphs = ();
|
||||
|
||||
*EditParagraphOldPrintWikiToHTML = *PrintWikiToHTML;
|
||||
*PrintWikiToHTML = *EditParagraphNewPrintWikiToHTML;
|
||||
|
||||
sub EditParagraphNewPrintWikiToHTML {
|
||||
my ($text, $is_saving_cache, $revision, $is_locked) = @_;
|
||||
# We need to use quoted HTML because that's what the rules will applied to!
|
||||
my $quoted_text = QuoteHtml($text);
|
||||
if ($quoted_text and not $revision) {
|
||||
my ($start, $end) = (0, 0);
|
||||
# This grouping with zero-width positive look-ahead assertion makes sure that this chunk of text does not include
|
||||
# markup need for the next chunk of text.
|
||||
if (grep { $_ eq \&CreoleRule } @MyRules) {
|
||||
$regexp = "\n+(\n|(?=[*#-=|]))";
|
||||
} else {
|
||||
$regexp = "\n+(\n|(?=[*]))";
|
||||
}
|
||||
while ($quoted_text =~ /$regexp/g) {
|
||||
$end = pos($quoted_text);
|
||||
push(@EditParagraphs,
|
||||
[$start, $end, substr($quoted_text, $start, $end - $start)]);
|
||||
$start = $end;
|
||||
}
|
||||
# Only do this if we have at least two paragraphs and the end isn't just some empty lines.
|
||||
if (@EditParagraphs and $start and $start < length($quoted_text)) {
|
||||
push(@EditParagraphs, [$start, length($quoted_text), substr($quoted_text, $start)]);
|
||||
}
|
||||
}
|
||||
# warn join('', '', map { $_->[0] . "-" . $_->[1] .": " . $_->[2]; } @EditParagraphs);
|
||||
return EditParagraphOldPrintWikiToHTML(@_);
|
||||
}
|
||||
|
||||
# Whenever an important element is closed, we try to add a link.
|
||||
|
||||
*EditParagraphOldCloseHtmlEnvironments = *CloseHtmlEnvironments;
|
||||
*CloseHtmlEnvironments = *EditParagraphNewCloseHtmlEnvironments;
|
||||
|
||||
sub EditParagraphNewCloseHtmlEnvironments {
|
||||
EditParagraph();
|
||||
return EditParagraphOldCloseHtmlEnvironments(@_);
|
||||
}
|
||||
|
||||
*EditParagraphOldCloseHtmlEnvironmentUntil = *CloseHtmlEnvironmentUntil;
|
||||
*CloseHtmlEnvironmentUntil = *EditParagraphNewCloseHtmlEnvironmentUntil;
|
||||
|
||||
sub EditParagraphNewCloseHtmlEnvironmentUntil {
|
||||
my $tag = $_[0];
|
||||
if ($tag =~ /^(p|li|table|h[1-6])$/i) {
|
||||
EditParagraph();
|
||||
}
|
||||
return EditParagraphOldCloseHtmlEnvironmentUntil(@_);
|
||||
}
|
||||
|
||||
sub EditParagraph {
|
||||
my $text;
|
||||
my $pos = pos; # pos is empty for the last link
|
||||
if (@EditParagraphs) {
|
||||
if ($pos) {
|
||||
while (@EditParagraphs and $EditParagraphs[0]->[1] <= $pos) {
|
||||
$pos = $EditParagraphs[0]->[1]; # just in case we're overshooting
|
||||
$text .= $EditParagraphs[0]->[2];
|
||||
shift(@EditParagraphs);
|
||||
}
|
||||
} else {
|
||||
# the last one
|
||||
$text = $EditParagraphs[-1]->[2];
|
||||
}
|
||||
}
|
||||
if ($text) {
|
||||
|
||||
# Huge Hack Alert: We are appending to $Fragment, which is what Clean appends to. We do this so that we can handle
|
||||
# headers and other block elements. Without this fix we'd see something like this:
|
||||
# <h2>...</h2><p><a ...>✎</a></p>
|
||||
# Usually this would look as follows:
|
||||
# <h2>...</h2><p></p>
|
||||
# This is eliminated in Dirty. But it won't be eliminated if we leave the link in there. What we want is this:
|
||||
# <h2>...<a ...>✎</a></h2><p></p>
|
||||
#
|
||||
# The same issue arises for other block level elements. What happens at the end of a table? Without this fix we'd
|
||||
# see something like this:
|
||||
# <table><tr><td>...</td></tr></table><p><a ...>✎</a></p>
|
||||
# What we want, I guess, is this:
|
||||
# <table><tr><td>...<a ...>✎</a></td></tr></table></p>
|
||||
|
||||
$pos = $pos || length(QuoteHtml($Page{text})); # make sure we have an around value
|
||||
my $title = UrlEncode($OpenPageName);
|
||||
my $paragraph = UrlEncode(UnquoteHtml($text));
|
||||
my $link = ScriptLink("action=edit-paragraph;title=$title;around=$pos;paragraph=$paragraph",
|
||||
$EditParagraphPencil, 'pencil');
|
||||
|
||||
if ($Fragment =~ s!((:?</h[1-6]>|</t[dh]></tr></table>|</pre>)<p>)$!!) {
|
||||
# $Fragment .= '<!-- moved inside -->';
|
||||
$Fragment .= $link . $1;
|
||||
} elsif ($Fragment =~ s!(</p>\s*</form>)$!!) {
|
||||
# $Fragment .= '<!-- HTML fixes for <html> -->';
|
||||
# Since anything can appear in raw HTML tags, there is no one-size fits all rule.
|
||||
# I usually use the <html> tags to embed forms, and forms need to contain a <p>.
|
||||
# so that's what I'm handling.
|
||||
$Fragment .= $link . $1;
|
||||
} elsif ($pos and $Fragment =~ /<(p|tr)>$/) {
|
||||
# Do nothing: this is either an empty paragraph and will be
|
||||
# eliminated, or an empty row which will not be shown.
|
||||
# $Fragment .= '<!-- empty -->';
|
||||
} else {
|
||||
# This is the default: add the link.
|
||||
# $Fragment .= '<!-- default -->';
|
||||
$Fragment .= $link;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (C) 2015 Matt Adams <opensource@radicaldynamic.com>
|
||||
# Copyright (C) 2006 Matthias Dietrich <md (at) plusw (.) de>
|
||||
# Copyright (C) 2005 Mathias Dahl <mathias . dahl at gmail dot com>
|
||||
# Copyright (C) 2005 Alex Schroeder <alex@emacswiki.org>
|
||||
@@ -67,11 +68,15 @@ sub NewOpenPage {
|
||||
$hidden = "adi";
|
||||
}
|
||||
|
||||
# Check the different levels of access
|
||||
if ($hidden eq "edi" && $HideEditorPages == 1 && (!UserIsEditor() && !UserIsAdmin())) {
|
||||
ReportError(T("Only Editors are allowed to see this hidden page."), "401 Not Authorized");
|
||||
} elsif ($hidden eq "adi" && $HideAdminPages == 1 && !UserIsAdmin()) {
|
||||
ReportError(T("Only Admins are allowed to see this hidden page."), "401 Not Authorized");
|
||||
my $action = lc(GetParam('action', ''));
|
||||
|
||||
if ($action ne 'rc' && $action ne 'search') {
|
||||
# Check the different levels of access
|
||||
if ($hidden eq "edi" && $HideEditorPages == 1 && (!UserIsEditor() && !UserIsAdmin())) {
|
||||
ReportError(T("Only Editors are allowed to see this hidden page."), "401 Not Authorized");
|
||||
} elsif ($hidden eq "adi" && $HideAdminPages == 1 && !UserIsAdmin()) {
|
||||
ReportError(T("Only Admins are allowed to see this hidden page."), "401 Not Authorized");
|
||||
}
|
||||
}
|
||||
|
||||
# Give control back to OpenPage()
|
||||
|
||||
@@ -72,7 +72,7 @@ sub ImageSupportRule {
|
||||
my $valRegex = qr/(([0-9.]+[a-z]*%?)\s+)/;
|
||||
if ($_ =~ /^\s*(([a-zA-Z ]+)\/)?$valRegex$valRegex$valRegex$valRegex(.*)$/) { # can't use {4} here? :(
|
||||
my $commentClass = $2 ? "imagecomment $2" : 'imagecomment';
|
||||
$result .= $q->div({-class=>$commentClass, -style=>"position: absolute; top: $6; left: $4; width: $8; height: $10"}, QuoteHtml($11));
|
||||
$result .= $q->div({-class=>$commentClass, -style=>"position: absolute; top: $6; left: $4; width: $8; height: $10"}, $11);
|
||||
}
|
||||
}
|
||||
$result = CloseHtmlEnvironments() . $q->div({-class=>"imageholder", -style=>"position: relative"}, $result);
|
||||
|
||||
@@ -37,6 +37,8 @@ my %library= ('bg' => 'bulgarian-utf8.pl',
|
||||
'se' => 'swedish-utf8.pl',
|
||||
'sr' => 'serbian-utf8.pl',
|
||||
'zh' => 'chinese-utf8.pl',
|
||||
'zh-cn' => 'chinese_cn-utf8.pl',
|
||||
'zh-tw' => 'chinese-utf8.pl',
|
||||
);
|
||||
|
||||
sub LoadLanguage {
|
||||
|
||||
@@ -109,11 +109,14 @@ has several unnerving effects:
|
||||
|
||||
=cut
|
||||
sub DoLogout {
|
||||
foreach my $cookieKey (keys %CookieParameters) { SetParam($cookieKey, ''); }
|
||||
my $id = shift;
|
||||
|
||||
SetParam('username', $CookieParameters{username});
|
||||
SetParam('pwd', $CookieParameters{pwd});
|
||||
|
||||
print
|
||||
GetHeader('', Ts('Logged out of %s', $SiteName), '').
|
||||
$q->div({-class=> 'content'}, T('You are now logged out.'));
|
||||
GetHeader('', Ts('Logged out of %s', $SiteName), '') .
|
||||
$q->div({-class=> 'content'}, $q->p(T('You are now logged out.'), $id ? $q->p(ScriptLink('action=browse;id=' . UrlEncode($id) . '&time=' . time, T('Return to ' . NormalToFree($id)))) : ''));
|
||||
PrintFooter();
|
||||
}
|
||||
|
||||
@@ -129,7 +132,7 @@ Appends a "Logout" link onto the edit bar in the footer of each page.
|
||||
|
||||
=cut
|
||||
sub GetFooterLinksLogout {
|
||||
my ($page_name, $page_rev) = @_;
|
||||
my ($id, $rev) = @_;
|
||||
my $footer_links = GetFooterLinksLogoutOld(@_);
|
||||
|
||||
if ($CommentsPrefix and $CommentsSuffix) {
|
||||
@@ -143,9 +146,18 @@ sub GetFooterLinksLogout {
|
||||
# in with some username or password.
|
||||
if (GetParam('username', '') ne '' or
|
||||
GetParam('pwd', '') ne '') {
|
||||
my $action = 'action=logout';
|
||||
$action .= ';id=' . UrlEncode($id) if $id;
|
||||
$footer_links =~ s
|
||||
/(.+)(<\/.+?>)$
|
||||
/$1.' '.ScriptLink('action=logout;id='.UrlEncode($id), T('Logout'), 'logout').$2
|
||||
/$1.' '.ScriptLink($action, T('Logout'), 'logout').$2
|
||||
/ex;
|
||||
} else {
|
||||
my $action = 'action=password';
|
||||
$action .= ';id=' . UrlEncode($id) if $id;
|
||||
$footer_links =~ s
|
||||
/(.+)(<\/.+?>)$
|
||||
/$1.' '.ScriptLink($action, T('Login'), 'login').$2
|
||||
/ex;
|
||||
}
|
||||
|
||||
@@ -199,7 +211,9 @@ sub CookieUsernameFix {
|
||||
}
|
||||
|
||||
sub CookieUsernameDelete {
|
||||
$Message .= $q->p(shift);
|
||||
if ($LogoutIsDebugging) {
|
||||
$Message .= $q->p(shift);
|
||||
}
|
||||
$q->delete('username');
|
||||
}
|
||||
|
||||
@@ -239,7 +253,7 @@ logout retains this (admittedly loose) concept of a "user."
|
||||
logout is "little brother" to the login module - from which it was inspired and
|
||||
for which it's partly named, in antiparallel.
|
||||
|
||||
logout only implements a slim subset of functionality implemented by the logout
|
||||
logout only implements a slim subset of functionality implemented by the login
|
||||
module. For a full-bodied, fully configurable alternative to Oddmuse security,
|
||||
please use that module instead.
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ sub MailNewGetFooterTimestamp {
|
||||
$addition = ScriptLink("action=subscribe;pages=$id",
|
||||
T('subscribe'), 'subscribe');
|
||||
}
|
||||
$html =~ s!(.*)(</span>)!$1 $addition$2!i;
|
||||
$html =~ s!(.*)(<br /></span>)!$1 $addition$2!i;
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,45 +17,60 @@ package OddMuse;
|
||||
use File::Basename;
|
||||
use File::Copy;
|
||||
|
||||
AddModuleDescription('module-bisect.pl', 'Bisect Extension');
|
||||
AddModuleDescription('module-bisect.pl', 'Module Bisect Extension');
|
||||
|
||||
push(@MyAdminCode, \&ModuleBisectMenu);
|
||||
$Action{bisect} = \&BisectAction;
|
||||
|
||||
sub ModuleBisectMenu {
|
||||
return unless UserIsAdmin();
|
||||
my ($id, $menuref, $restref) = @_;
|
||||
push(@$menuref, ScriptLink('action=bisect', T('Bisect modules'), 'modulebisect'));
|
||||
}
|
||||
|
||||
sub BisectAction {
|
||||
UserIsAdminOrError();
|
||||
RequestLockOrError();
|
||||
print GetHeader('', T('Module Bisect'), '');
|
||||
print GetHeader('', T('Module Bisect'), '', 'nocache');
|
||||
if (GetParam('stop')) {
|
||||
BisectEnableAll(1);
|
||||
print $q->br(), $q->strong(T('All modules enabled now!'));
|
||||
print GetFormStart(undef, 'get', 'bisect');
|
||||
print GetHiddenValue('action', 'bisect');
|
||||
print $q->submit(-name=>'noop', -value=>T('Go back'));
|
||||
print $q->end_form();
|
||||
} elsif (GetParam('good') or GetParam('bad')) {
|
||||
BisectProcess(GetParam('good'));
|
||||
} else {
|
||||
print GetFormStart(undef, 'get', 'bisect');
|
||||
print GetHiddenValue('action', 'bisect');
|
||||
my @disabledFiles = bsd_glob("$ModuleDir/*.p[ml].disabled");
|
||||
if (@disabledFiles == 0) {
|
||||
print 'Test / Always enabled / Always disabled', $q->br();
|
||||
my @files = bsd_glob("$ModuleDir/*.p[ml]");
|
||||
for (my $i = 0; $i < @files; $i++) {
|
||||
my $moduleName = fileparse($files[$i]);
|
||||
my @disabled = ($moduleName eq 'module-bisect.pl' ? (-disabled=>'disabled') : ());
|
||||
print $q->input({-type=>'radio', -name=>"m$i", -value=>'t', ($moduleName ne 'module-bisect.pl' ? (-checked=>'checked') : ()), @disabled});
|
||||
print $q->input({-type=>'radio', -name=>"m$i", -value=>'on', ($moduleName eq 'module-bisect.pl' ? (-checked=>'checked') : ())});
|
||||
print $q->input({-type=>'radio', -name=>"m$i", -value=>'off', @disabled});
|
||||
print $moduleName, $q->br();
|
||||
}
|
||||
print $q->submit(-name=>'bad', -value=>T('Start'));
|
||||
} else {
|
||||
print T('Biscecting proccess is already active.'), $q->br();
|
||||
print $q->submit(-name=>'stop', -value=>T('Stop'));
|
||||
}
|
||||
print $q->end_form();
|
||||
BisectInitialScreen();
|
||||
}
|
||||
PrintFooter();
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
sub BisectInitialScreen {
|
||||
print GetFormStart(undef, 'get', 'bisect');
|
||||
print GetHiddenValue('action', 'bisect');
|
||||
my @disabledFiles = bsd_glob("$ModuleDir/*.p[ml].disabled");
|
||||
if (@disabledFiles == 0) {
|
||||
print T('Test / Always enabled / Always disabled'), $q->br();
|
||||
my @files = bsd_glob("$ModuleDir/*.p[ml]");
|
||||
for (my $i = 0; $i < @files; $i++) {
|
||||
my $moduleName = fileparse($files[$i]);
|
||||
my @disabled = ($moduleName eq 'module-bisect.pl' ? (-disabled=>'disabled') : ());
|
||||
print $q->input({-type=>'radio', -name=>"m$i", -value=>'t', ($moduleName ne 'module-bisect.pl' ? (-checked=>'checked') : ()), @disabled});
|
||||
print $q->input({-type=>'radio', -name=>"m$i", -value=>'on', ($moduleName eq 'module-bisect.pl' ? (-checked=>'checked') : ())});
|
||||
print $q->input({-type=>'radio', -name=>"m$i", -value=>'off', @disabled});
|
||||
print $moduleName, $q->br();
|
||||
}
|
||||
print $q->submit(-name=>'bad', -value=>T('Start'));
|
||||
} else {
|
||||
print T('Biscecting proccess is already active.'), $q->br();
|
||||
print $q->submit(-name=>'stop', -value=>T('Stop'));
|
||||
}
|
||||
print $q->end_form();
|
||||
}
|
||||
|
||||
sub BisectProcess {
|
||||
my ($isGood) = @_;
|
||||
my $parameterHandover = '';
|
||||
@@ -71,12 +86,13 @@ sub BisectProcess {
|
||||
splice @files, $i, 1;
|
||||
}
|
||||
}
|
||||
my $start = GetParam('start') - 1; # $start and $end are indexes
|
||||
my $end = GetParam('end') - 1;
|
||||
$start = 0 if $start < 0; # not specified (probably right after Start)
|
||||
$end = @files * 2 - 1 if $end < 0;
|
||||
my $start = GetParam('start', 1) - 1; # $start and $end are indexes
|
||||
my $end = GetParam('end', @files * 2) - 1;
|
||||
if ($end - $start <= 1) {
|
||||
print 'It seems like ', $q->strong((fileparse($isGood ? $files[$end] : $files[$start]))[0]), ' is causing your problem.';
|
||||
print Ts('It seems like module %s is causing your problem.',
|
||||
$q->strong((fileparse($isGood ? $files[$end] : $files[$start]))[0])), $q->br(), $q->br();
|
||||
print T('Please note that this module does not handle situations when your problem is caused by a combination of specific modules (which is rare anyway).'), $q->br();
|
||||
print T('Good luck fixing your problem! ;)');
|
||||
print GetFormStart(undef, 'get', 'bisect');
|
||||
print GetHiddenValue('action', 'bisect');
|
||||
print $q->submit(-name=>'stop', -value=>T('Stop'));
|
||||
@@ -115,7 +131,7 @@ sub BisectEnableAll {
|
||||
for (bsd_glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
|
||||
my $oldName = $_;
|
||||
s/\.disabled$//;
|
||||
print "Enabling ", (fileparse($_))[0], '...', $q->br() if $_[0];
|
||||
print Ts('Enabling %s', (fileparse($_))[0]), '...', $q->br() if $_[0];
|
||||
move($oldName, $_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ sub ModuleUpdaterMenu {
|
||||
}
|
||||
|
||||
sub ModuleUpdaterAction {
|
||||
return unless UserIsAdminOrError();
|
||||
UserIsAdminOrError();
|
||||
RequestLockOrError();
|
||||
print GetHeader('', T('Module Updater'), '');
|
||||
print GetHeader('', T('Module Updater'), '', 'nocache');
|
||||
|
||||
if (GetParam('ok')) {
|
||||
ModuleUpdaterApply();
|
||||
@@ -68,7 +68,7 @@ sub ModuleUpdaterApply {
|
||||
print $q->br(), $q->strong('Done!');
|
||||
}
|
||||
|
||||
sub ProcessModule() {
|
||||
sub ProcessModule {
|
||||
my $module = shift;
|
||||
CreateDir($TempDir);
|
||||
print $q->hr();
|
||||
|
||||
@@ -67,7 +67,7 @@ not the namespace Foo.
|
||||
|
||||
=cut
|
||||
|
||||
@NamespaceParameters = qw(action search title);
|
||||
@NamespaceParameters = qw(action search title match);
|
||||
|
||||
$NamespaceSlashing = 0; # affects : decoding NamespaceRcLines
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
# Copyright (C) 2004, 2005 Alex Schroeder <alex@emacswiki.org>
|
||||
# Copyright (C) 2004–2015 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2015 Matt Adams <opensource@radicaldynamic.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.
|
||||
# 59 Temple Place, Suite 330
|
||||
# Boston, MA 02111-1307 USA
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
AddModuleDescription('tables-long.pl', 'Long Table Markup Extension');
|
||||
|
||||
@@ -58,7 +55,7 @@ sub TablesLongRule {
|
||||
my %rowspan = ();
|
||||
my $label = '';
|
||||
my $rowspan = '';
|
||||
my $first = 1;
|
||||
my $rownum = 1;
|
||||
for my $line (@lines) {
|
||||
if ($line =~ m|^($regexp)/?([0-9]+)?/?([A-Za-z\x{0080}-\x{fffd}/]+)?[:=] *(.*)|) { # regexp changes for other tables
|
||||
$label = $1;
|
||||
@@ -66,8 +63,7 @@ sub TablesLongRule {
|
||||
$class = join(' ', split(m|/|, $3)); # no leading / therefore no leading space
|
||||
$line = $4;
|
||||
if ($row{$label}) { # repetition of label, we must start a new row
|
||||
TablesLongRow(\@labels, \%row, \%class, \%rowspan, $first);
|
||||
$first = 0;
|
||||
TablesLongRow(\@labels, \%row, \%class, \%rowspan, $rownum++);
|
||||
%row = ();
|
||||
%class = %default_class;
|
||||
foreach my $key (keys %rowspan) {
|
||||
@@ -80,7 +76,7 @@ sub TablesLongRule {
|
||||
}
|
||||
$row{$label} .= $line . "\n";
|
||||
}
|
||||
TablesLongRow(\@labels, \%row, \%class, \%rowspan, $first); # don't forget the last row
|
||||
TablesLongRow(\@labels, \%row, \%class, \%rowspan, $rownum); # don't forget the last row
|
||||
Clean('</table>' . AddHtmlEnvironment('p'));
|
||||
pos = $lastpos;
|
||||
return '';
|
||||
@@ -93,8 +89,14 @@ sub TablesLongRow {
|
||||
my %row = %{$_[1]};
|
||||
my %class = %{$_[2]};
|
||||
my %rowspan = %{$_[3]};
|
||||
my $first = $_[4];
|
||||
Clean('<tr>');
|
||||
my $rownum = $_[4];
|
||||
if ($rownum == 1) {
|
||||
Clean('<tr class="first odd">');
|
||||
} elsif ($rownum % 2 == 0) {
|
||||
Clean('<tr class="even">');
|
||||
} else {
|
||||
Clean('<tr class="odd">');
|
||||
}
|
||||
# first print the old row
|
||||
for my $i (0 .. $#labels) {
|
||||
next if not $row{$labels[$i]}; # should only happen after previous cellspans
|
||||
@@ -107,18 +109,18 @@ sub TablesLongRow {
|
||||
my $rowspan = $rowspan{$labels[$i]};
|
||||
my $class = $class{$labels[$i]};
|
||||
my $html = '<';
|
||||
$html .= $first ? 'th' : 'td';
|
||||
$html .= $rownum == 1 ? 'th' : 'td';
|
||||
$html .= " colspan=\"$colspan\"" if $colspan != 1;
|
||||
$html .= " rowspan=\"$rowspan\"" if defined $rowspan and $rowspan >= 0; # ignore negatives
|
||||
$html .= " class=\"$class\"" if $class;
|
||||
$html .= '>';
|
||||
Clean($html);
|
||||
|
||||
# WATCH OUT: here comes the evil magic messing with the internals!
|
||||
# first, clean everything up like at the end of ApplyRules
|
||||
|
||||
# WATCH OUT: here comes the evil magic messing with the internals! first, clean everything up like at the end of
|
||||
# ApplyRules. The reason we are doing this is because we don't want to treat the entire long table as a single dirty
|
||||
# block. We want to cache as much as possible.
|
||||
if ($Fragment ne '') {
|
||||
$Fragment =~ s|<p></p>||g; # clean up extra paragraphs (see end Dirty())
|
||||
$Fragment =~ s|<p>\s*</p>||g; # clean up extra paragraphs (see end Dirty())
|
||||
print $Fragment;
|
||||
push(@Blocks, $Fragment);
|
||||
push(@Flags, 0);
|
||||
@@ -127,14 +129,12 @@ sub TablesLongRow {
|
||||
# call ApplyRules, and *inline* the results; ignoring $PortraitSupportColorDiv
|
||||
local $PortraitSupportColorDiv;
|
||||
my ($blocks, $flags) = ApplyRules($row{$labels[$i]}, 1, 1); # local links, anchors
|
||||
push(@Blocks, split(/$FS/, $blocks));
|
||||
push(@Flags, split(/$FS/, $flags));
|
||||
# split using a negative limit so that trailing empty fields are not stripped
|
||||
push(@Blocks, split(/$FS/, $blocks, -1));
|
||||
push(@Flags, split(/$FS/, $flags, -1));
|
||||
# end of evil magic
|
||||
|
||||
# Alternatively, just use
|
||||
# Clean($row{$labels[$i]});
|
||||
# or mark this block as dirty.
|
||||
Clean(CloseHtmlEnvironments() . '</' . ($first ? 'th' : 'td') . '>');
|
||||
Clean(CloseHtmlEnvironments() . '</' . ($rownum == 1 ? 'th' : 'td') . '>');
|
||||
}
|
||||
Clean('</tr>');
|
||||
}
|
||||
|
||||
111
modules/tags.pl
111
modules/tags.pl
@@ -37,7 +37,7 @@ AddModuleDescription('tags.pl', 'Tagging Extension');
|
||||
|
||||
These variable will be used to link the tags. By default, they will
|
||||
point at the wiki itself, using C<$ScriptName>. They use C<%s> as a
|
||||
placeholder for the URL encoded tag.
|
||||
placeholder for the tag.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -76,6 +76,19 @@ sub TagsGetLink {
|
||||
return $url;
|
||||
}
|
||||
|
||||
sub TagReadHash {
|
||||
require Storable;
|
||||
return %{ Storable::retrieve($TagFile) } if -f $TagFile;
|
||||
}
|
||||
|
||||
|
||||
# returns undef if encountering an error
|
||||
sub TagWriteHash {
|
||||
my $h = shift;
|
||||
require Storable;
|
||||
return Storable::store($h, $TagFile);
|
||||
}
|
||||
|
||||
push(@MyRules, \&TagsRule);
|
||||
|
||||
sub TagsRule {
|
||||
@@ -119,18 +132,15 @@ sub NewTagSave { # called within a lock!
|
||||
($Page{text} =~ m/\[\[tag:$FreeLinkPattern\]\]/g,
|
||||
$Page{text} =~ m/\[\[tag:$FreeLinkPattern\|([^]|]+)\]\]/g);
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
my %h = TagReadHash();
|
||||
|
||||
# For each tag we list the files tagged. Add the current file for
|
||||
# all those tags where it is missing. Note that the values in %h is
|
||||
# an encoded string; the alternative would be to use a form of
|
||||
# freeze and thaw.
|
||||
# all those tags where it is missing.
|
||||
foreach my $tag (keys %tag) {
|
||||
my %file = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($tag)}));
|
||||
my %file = map {$_=>1} @{$h{$tag}};
|
||||
if (not $file{$id}) {
|
||||
$file{$id} = 1;
|
||||
$h{UrlEncode($tag)} = UrlEncode(join($FS, keys %file));
|
||||
$h{$tag} = [keys %file];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,16 +148,16 @@ sub NewTagSave { # called within a lock!
|
||||
# tags used. This allows us to delete the references that no longer
|
||||
# show up without looping through them all. The files are indexed
|
||||
# with a starting underscore because this is an illegal tag name.
|
||||
foreach my $tag (split (/$FS/, UrlDecode($h{UrlEncode("_$id")}))) {
|
||||
foreach my $tag (@{$h{"_$id"}}) {
|
||||
# If the tag we're looking at is no longer listed, we have work to
|
||||
# do.
|
||||
if (!$tag{$tag}) {
|
||||
my %file = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($tag)}));
|
||||
my %file = map {$_=>1} @{$h{$tag}};
|
||||
delete $file{$id};
|
||||
if (%file) {
|
||||
$h{UrlEncode($tag)} = UrlEncode(join($FS, keys %file));
|
||||
$h{$tag} = [keys %file];
|
||||
} else {
|
||||
delete $h{UrlEncode($tag)};
|
||||
delete $h{$tag};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,12 +165,12 @@ sub NewTagSave { # called within a lock!
|
||||
# Store the new reverse lookup of all the tags used on the current
|
||||
# page. If no more tags appear on this page, delete the entry.
|
||||
if (%tag) {
|
||||
$h{UrlEncode("_$id")} = UrlEncode(join($FS, keys %tag));
|
||||
$h{"_$id"} = [keys %tag];
|
||||
} else {
|
||||
delete $h{UrlEncode("_$id")};
|
||||
delete $h{"_$id"};
|
||||
}
|
||||
|
||||
untie %h;
|
||||
TagWriteHash(\%h);
|
||||
}
|
||||
|
||||
=pod
|
||||
@@ -177,25 +187,24 @@ sub NewTagDeletePage { # called within a lock!
|
||||
my $id = shift;
|
||||
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
my %h = TagReadHash();
|
||||
|
||||
# For each file in our hash, we have a reverse lookup of all the
|
||||
# tags used. This allows us to delete the references that no longer
|
||||
# show up without looping through them all.
|
||||
foreach my $tag (split (/$FS/, UrlDecode($h{UrlEncode("_$id")}))) {
|
||||
my %file = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($tag)}));
|
||||
foreach my $tag (@{$h{"_$id"}}) {
|
||||
my %file = map {$_=>1} @{$h{$tag}};
|
||||
delete $file{$id};
|
||||
if (%file) {
|
||||
$h{UrlEncode($tag)} = UrlEncode(join($FS, keys %file));
|
||||
$h{$tag} = [keys %file];
|
||||
} else {
|
||||
delete $h{UrlEncode($tag)};
|
||||
delete $h{$tag};
|
||||
}
|
||||
}
|
||||
|
||||
# Delete reverse lookup entry.
|
||||
delete $h{UrlEncode("_$id")};
|
||||
untie %h;
|
||||
delete $h{"_$id"};
|
||||
TagWriteHash(\%h);
|
||||
|
||||
# Return any error codes?
|
||||
return OldTagDeletePage($id, @_);
|
||||
@@ -213,15 +222,13 @@ pages and a new search term without the tag terms.
|
||||
sub TagFind {
|
||||
my @tags = @_;
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
my %h = TagReadHash();
|
||||
my %page;
|
||||
foreach my $tag (@tags) {
|
||||
foreach my $id (split(/$FS/, UrlDecode($h{UrlEncode(lc($tag))}))) {
|
||||
foreach my $id (@{$h{lc($tag)}}) {
|
||||
$page{$id} = 1;
|
||||
}
|
||||
}
|
||||
untie %h;
|
||||
return sort keys %page;
|
||||
}
|
||||
|
||||
@@ -286,25 +293,23 @@ sub TagCloud {
|
||||
print GetHeader('', T('Tag Cloud'), ''),
|
||||
$q->start_div({-class=>'content cloud'}) . '<p>';
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
my %h = TagReadHash();
|
||||
my $max = 0;
|
||||
my $min = 0;
|
||||
my %count = ();
|
||||
foreach my $encoded_tag (grep !/^_/, keys %h) {
|
||||
$count{$encoded_tag} = split(/$FS/, UrlDecode($h{$encoded_tag}));
|
||||
$max = $count{$encoded_tag} if $count{$encoded_tag} > $max;
|
||||
$min = $count{$encoded_tag} if not $min or $count{$encoded_tag} < $min;
|
||||
foreach my $tag (grep !/^_/, keys %h) {
|
||||
$count{$tag} = @{$h{$tag}};
|
||||
$max = $count{$tag} if $count{$tag} > $max;
|
||||
$min = $count{$tag} if not $min or $count{$tag} < $min;
|
||||
}
|
||||
untie %h;
|
||||
foreach my $encoded_tag (sort keys %count) {
|
||||
my $n = $count{$encoded_tag};
|
||||
print $q->a({-href => "$ScriptName?search=tag:" . $encoded_tag,
|
||||
foreach my $tag (sort keys %count) {
|
||||
my $n = $count{$tag};
|
||||
print $q->a({-href => "$ScriptName?search=tag:" . UrlEncode($tag),
|
||||
-title => $n,
|
||||
-style => 'font-size: '
|
||||
. int(80+120*($max == $min ? 1 : ($n-$min)/($max-$min)))
|
||||
. '%;',
|
||||
}, NormalToFree(UrlDecode($encoded_tag))), T(' ... ');
|
||||
}, NormalToFree($tag)), T(' ... ');
|
||||
}
|
||||
print '</p></div>';
|
||||
PrintFooter();
|
||||
@@ -338,9 +343,8 @@ sub DoTagsReindex {
|
||||
print GetHttpHeader('text/plain');
|
||||
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
%h = ();
|
||||
require Storable;
|
||||
my %h = ();
|
||||
|
||||
foreach my $id (AllPagesList()) {
|
||||
print "$id\n";
|
||||
@@ -354,18 +358,18 @@ sub DoTagsReindex {
|
||||
# For each tag we list the files tagged. Add the current file for
|
||||
# all tags.
|
||||
foreach my $tag (keys %tag) {
|
||||
my $encoded_tag = UrlEncode($tag);
|
||||
$h{$encoded_tag} = $h{$encoded_tag}
|
||||
? $h{$encoded_tag} . UrlEncode($FS . $id)
|
||||
: UrlEncode($id);
|
||||
push(@{$h{$tag}}, $id);
|
||||
}
|
||||
|
||||
# Store the reverse lookup of all the tags used on the current
|
||||
# page.
|
||||
$h{UrlEncode("_$id")} = UrlEncode(join($FS, keys %tag));
|
||||
$h{"_$id"} = [keys %tag];
|
||||
}
|
||||
if (TagWriteHash(\%h)) {
|
||||
print "Saved tag file.\n";
|
||||
} else {
|
||||
print "Error saving tag file.\n";
|
||||
}
|
||||
|
||||
untie %h;
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
@@ -385,12 +389,11 @@ $Action{taglist} = \&TagList;
|
||||
sub TagList {
|
||||
print GetHttpHeader('text/plain');
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
foreach my $id (sort map { UrlDecode($_) } keys %h) {
|
||||
print "$id: " . join(', ', split(/$FS/, UrlDecode($h{UrlEncode($id)}))) . "\n";
|
||||
my %h = TagReadHash();
|
||||
foreach my $id (sort keys %h) {
|
||||
print "$id: " . join(', ', @{$h{$id}}) . "\n";
|
||||
}
|
||||
untie %h;
|
||||
TagWriteHash(\%h);
|
||||
}
|
||||
|
||||
=pod
|
||||
@@ -412,7 +415,7 @@ sub TagsMenu {
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2005, 2009, 2013 Alex Schroeder <alex@gnu.org>
|
||||
Copyright (C) 2005–2015 Alex Schroeder <alex@gnu.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -52,6 +52,13 @@ sub TocScript {
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/280634/endswith-in-javascript
|
||||
if (typeof String.prototype.endsWith !== 'function') {
|
||||
String.prototype.endsWith = function(suffix) {
|
||||
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
var initToc=function() {
|
||||
|
||||
var outline = HTML5Outline(document.body);
|
||||
@@ -87,6 +94,14 @@ sub TocScript {
|
||||
if (toc) {
|
||||
var html = outline.asHTML(true);
|
||||
toc.innerHTML = html;
|
||||
|
||||
items = toc.getElementsByTagName('a');
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
while (items[i].textContent.endsWith('✎')) {
|
||||
var text = items[i].childNodes[0].nodeValue;
|
||||
items[i].childNodes[0].nodeValue = text.substring(0, text.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ You are currently an editor on this site.
|
||||
Atualmente você é um editor nesse site.
|
||||
You are a normal user on this site.
|
||||
Você é um usuário normal nesse site.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Sua senha não confere com nenhuma dos administradores ou editores.
|
||||
Password:
|
||||
Senha:
|
||||
|
||||
@@ -397,7 +397,7 @@ You are currently an editor on this site.
|
||||
Ти си редактор.
|
||||
You are a normal user on this site.
|
||||
Ти си нормален потребител.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Твоята парола не съвпада нито с администраторската, нито с редакторската парола.
|
||||
Password:
|
||||
Парола:
|
||||
|
||||
@@ -397,7 +397,7 @@ You are currently an editor on this site.
|
||||
你現在是本站的編輯者。
|
||||
You are a normal user on this site.
|
||||
你現在是本站的一般使用者。
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
你的密碼不符合任何管理者或編輯者的密碼。
|
||||
Password:
|
||||
密碼:
|
||||
|
||||
@@ -414,7 +414,7 @@ You are currently an editor on this site.
|
||||
您现在是本站的编辑者。
|
||||
You are a normal user on this site.
|
||||
您现在是本站的普通用户。
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
您的密码和任何管理员或编辑者的密码都不匹配。
|
||||
Password:
|
||||
密码:
|
||||
|
||||
@@ -403,7 +403,7 @@ You are currently an editor on this site.
|
||||
U bent een redacteur op deze site.
|
||||
You are a normal user on this site.
|
||||
U bent een gewone gebruiker op deze site.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Uw wachtwoord komt niet overeen met een beheerders- of redacteurswachtwoord.
|
||||
Password:
|
||||
Wachtwoord:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
Olet tällä hetkellä sivuston toimittaja (editor).
|
||||
You are a normal user on this site.
|
||||
Olet tällä hetkellä tavallinen sivuston käyttäjä.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Salasanasi ei ole yksikään ylläpidon tai toimittajien salasanoista.
|
||||
Password:
|
||||
Salasana:
|
||||
|
||||
@@ -402,7 +402,7 @@ You are currently an editor on this site.
|
||||
Vous êtes actuellement éditeur de ce site.
|
||||
You are a normal user on this site.
|
||||
Vous êtes un utilisateur normal de ce site.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Vote mot de passe ne correspond ni au mot de passe administrateur ni au mot de passe éditeur.
|
||||
Password:
|
||||
Mot de passe :
|
||||
|
||||
@@ -396,7 +396,7 @@ You are currently an editor on this site.
|
||||
Sie sind momentan ein Redaktor auf dieser Webseite.
|
||||
You are a normal user on this site.
|
||||
Sie sind ein normaler Benutzer auf dieser Webseite.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Ihr Passwort stimmt nicht mit einem Administrator- oder Redaktor-Passwort überein.
|
||||
Password:
|
||||
Passwort:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
Είστε ένας εκδότης σε αυτό το δικτυακό τόπο.
|
||||
You are a normal user on this site.
|
||||
Είστε ένας απλός χρήστης σε αυτό το δικτυακό τόπο.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Ο κωδικός σας δεν ταιριάζει με κανένα κωδικό από τους διαχειριστές ή τους εκδότες.
|
||||
Password:
|
||||
Κωδικός:
|
||||
|
||||
@@ -396,7 +396,7 @@ You are currently an editor on this site.
|
||||
אתה כרגע עורך באתר זה.
|
||||
You are a normal user on this site.
|
||||
אתה משתמש רגיל באתר זה.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
הססמה שלך לא מתאימה ללסמאות של המנהל או העורך.
|
||||
Password:
|
||||
ססמה:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
Al momento potete modificare le pagine di questo sito.
|
||||
You are a normal user on this site.
|
||||
Siete un utente normale su queto sito.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
La vostra password non coincide con nessuna di quelle degli amministratori o degli editor.
|
||||
Password:
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ You are currently an editor on this site.
|
||||
あなたは現在このサイトの編集者です。
|
||||
You are a normal user on this site.
|
||||
あなたはこのサイトの通常ユーザです。
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
あなたのパスワードは管理者パスワードにも編集者パスワードにも一致しません。
|
||||
Password:
|
||||
パスワード:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
사용자가 이 페이지의 편집인입니다.
|
||||
You are a normal user on this site.
|
||||
사용자가 이 사이트의 정상 사용자입니다.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
사용자의 암호가 관리자 또는 편집자의 암호와 일치하지 않습니다.
|
||||
Password:
|
||||
암호:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
|
||||
You are a normal user on this site.
|
||||
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
|
||||
Password:
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ You are currently an editor on this site.
|
||||
Jesteś obecnie redaktorem tej strony.
|
||||
You are a normal user on this site.
|
||||
Jesteś zwyczajnym użytkownikiem.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Twoje hasło nie pasuje do żadnego z haseł edytora ani administratora.
|
||||
Password:
|
||||
Hasło:
|
||||
|
||||
@@ -399,7 +399,7 @@ You are currently an editor on this site.
|
||||
Você é actualmente um editor deste sítio.
|
||||
You are a normal user on this site.
|
||||
Você é um utilizador normal deste sítio.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
A sua senha não coincide com nenhuma dos administradores ou editores.
|
||||
Password:
|
||||
Senha:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
Sunteţi editor pe acest site.
|
||||
You are a normal user on this site.
|
||||
Sunteţi un utilizator obişnuit pe acest site.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Parola dumneavoastră nu corespunde nici unei parole de administrator sau editor.
|
||||
Password:
|
||||
Parola:
|
||||
|
||||
@@ -399,7 +399,7 @@ You are currently an editor on this site.
|
||||
Сейчас вы имеете права редактора.
|
||||
You are a normal user on this site.
|
||||
Сейчас вы имеете права обычного пользователя.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Ваш пароль не совпадает с паролями администратора или редактора.
|
||||
Password:
|
||||
Пароль:
|
||||
|
||||
@@ -396,7 +396,7 @@ You are currently an editor on this site.
|
||||
Тренутно сте уредник на овом сајту.
|
||||
You are a normal user on this site.
|
||||
Ви сте нормални корисник на овом сајту.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Ваша лозинка се не поклапа ни са једном од аминистраторских или уредничких лозинки.
|
||||
Password:
|
||||
Лозинка:
|
||||
|
||||
@@ -397,7 +397,7 @@ You are currently an editor on this site.
|
||||
Ahora eres un editor en este sitio.
|
||||
You are a normal user on this site.
|
||||
Eres un usuario normal en este sitio.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Tu contraseña no coincide con ninguna de las contraseñas de administrador o editor.
|
||||
Password:
|
||||
Contraseña:
|
||||
|
||||
@@ -398,7 +398,7 @@ You are currently an editor on this site.
|
||||
Du är för närvarande redaktör för den här webbplatsen.
|
||||
You are a normal user on this site.
|
||||
Du är en normal användare på den här webbplatsen.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Ditt lösenord motsvarar inget av admininistratörs- eller redaktörslösenorden.
|
||||
Password:
|
||||
Lösenord:
|
||||
|
||||
@@ -394,7 +394,7 @@ You are currently an editor on this site.
|
||||
Наразі, ви редактор на цьому сайті.
|
||||
You are a normal user on this site.
|
||||
Ви звичайний користувач сайту.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Your password does not match any of the administrator or editor passwords.
|
||||
Ваш пароль не збігається ні з паролєм адміністратора, ні з паролєм редактора.
|
||||
Password:
|
||||
Пароль:
|
||||
|
||||
@@ -27,6 +27,7 @@ sub UpgradeNewInitVariables {
|
||||
$LocalNamesPage = undef;
|
||||
$SidebarName = undef;
|
||||
$NearMap = undef;
|
||||
$GotobarName = undef;
|
||||
UpgradeOldInitVariables(@_);
|
||||
}
|
||||
|
||||
@@ -66,11 +67,12 @@ sub DoUpgrade {
|
||||
for my $ns ('', keys %InterSite) {
|
||||
next unless -d "$DataDir/$ns";
|
||||
print "<br />\n<strong>$ns</strong>" if $ns;
|
||||
for my $dir ($PageDir, $KeepDir, $RefererDir, $JoinerDir, $JoinerEmailDir) {
|
||||
next unless $dir;
|
||||
for my $dirname ($PageDir, $KeepDir, $RefererDir, $JoinerDir, $JoinerEmailDir) {
|
||||
next unless $dirname;
|
||||
my $dir = $dirname; # copy in order not to modify the original
|
||||
$dir =~ s/^$DataDir/$DataDir\/$ns/ if $ns;
|
||||
for my $old (bsd_glob("$dir/*/*"), bsd_glob("$dir/*/.*")) {
|
||||
next if substr($old, -2) eq '/.' or substr($old, -3) eq '/..';
|
||||
next if $old =~ /\/\.\.?$/;
|
||||
my $oldname = $old;
|
||||
utf8::decode($oldname);
|
||||
print "<br />\n$oldname";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2009 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2009–2015 Alex Schroeder <alex@gnu.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 16;
|
||||
use Test::More tests => 18;
|
||||
clear_pages();
|
||||
|
||||
AppendStringToFile($ConfigFile,
|
||||
@@ -24,11 +24,19 @@ AppendStringToFile($ConfigFile,
|
||||
|
||||
add_module('big-brother.pl');
|
||||
|
||||
get_page('action=browse id=HomePage username=Alex');
|
||||
my $item = xpath_test(get_page('action=visitors'),
|
||||
'//li[contains(text(), "was here")]');
|
||||
ok($item =~ /Alex was here (just now|\d seconds? ago) and read HomePage/,
|
||||
'Alex was here and read HomePage');
|
||||
get_page('action=browse id=SomePage username=Alex');
|
||||
get_page('username=Berta pwd=foo');
|
||||
my $visitors = get_page('action=visitors');
|
||||
my $item = xpath_test($visitors,
|
||||
'//li[contains(., "Alex")]');
|
||||
like($item, qr/Alex was here (just now|\d seconds? ago) and read SomePage/,
|
||||
'Alex was here and read SomePage');
|
||||
|
||||
my $item = xpath_test($visitors,
|
||||
'//li[contains(., "Berta")]');
|
||||
like($item, qr/Berta was here (just now|\d seconds? ago) and read some action/,
|
||||
'Berta was here and read some action');
|
||||
unlike($item, qr/pwd/, 'Link does not contain password');
|
||||
|
||||
# check surge protection still works (taking into account the previous
|
||||
# get_page calls)
|
||||
@@ -44,7 +52,6 @@ ok($status, "Read $VisitorFile");
|
||||
%BigBrotherData = ();
|
||||
foreach (split(/\n/,$data)) {
|
||||
my ($name, %entries) = split /$FS/;
|
||||
ok($name eq 'Alex', 'Alex is the only entry');
|
||||
$BigBrotherData{$name} = \%entries if $name and %entries;
|
||||
}
|
||||
my %entries = %{$BigBrotherData{Alex}};
|
||||
|
||||
46
t/cache.t
46
t/cache.t
@@ -1,24 +1,20 @@
|
||||
# Copyright (C) 2006, 2007 Alex Schroeder <alex@emacswiki.org>
|
||||
# Copyright (C) 2006, 2007 Alex Schroeder <alex@gnu.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.
|
||||
# 59 Temple Place, Suite 330
|
||||
# Boston, MA 02111-1307 USA
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 7;
|
||||
use Test::More tests => 8;
|
||||
|
||||
clear_pages();
|
||||
|
||||
@@ -27,23 +23,39 @@ sub get_etag {
|
||||
return $1 if $str =~ /Etag: (.*)\r\n/;
|
||||
}
|
||||
|
||||
sub get_last_modified {
|
||||
my $str = shift;
|
||||
return $1 if $str =~ /Last-Modified: (.*)\r\n/i;
|
||||
}
|
||||
|
||||
# Get the ts from the page db and compare it to the Etag
|
||||
update_page('CacheTest', 'something');
|
||||
OpenPage('CacheTest');
|
||||
my $ts1 = $Page{ts};
|
||||
my $ts2 = get_etag(get_page('CacheTest'));
|
||||
ok(abs($ts1 - $ts2) <= 1, "Latest edit of this page: $ts1 and $ts2 are close");
|
||||
|
||||
# When updating another page, that page's ts is the new Etag for all of them
|
||||
update_page('OtherPage', 'something');
|
||||
OpenPage('OtherPage');
|
||||
$ts1 = $Page{ts};
|
||||
$ts2 = get_etag(get_page('OtherPage'));
|
||||
ok(abs($ts1 - $ts2) <= 1, "Latest edit of other page: $ts1 and $ts2 are close");
|
||||
|
||||
# Getting it raw should use the original timestamp
|
||||
OpenPage('CacheTest');
|
||||
$ts1 = $Page{ts};
|
||||
$ts2 = get_etag(get_page('/raw/CacheTest?'));
|
||||
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 are close");
|
||||
$page = get_page('/raw/CacheTest?');
|
||||
$ts2 = get_etag($page);
|
||||
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 based on etag are close");
|
||||
|
||||
SKIP: {
|
||||
eval { require Date::Parse };
|
||||
skip ("Date::Parse not installed", 1) if $@;
|
||||
|
||||
$ts2 = Date::Parse::str2time(get_last_modified($page));
|
||||
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 based on last-modified timestamp are close");
|
||||
}
|
||||
|
||||
$str = 'This is a WikiLink.';
|
||||
|
||||
|
||||
@@ -24,4 +24,4 @@ AppendStringToFile($ConfigFile, "\$ConfigPage = 'Config';\n");
|
||||
|
||||
xpath_test(update_page('Config', '@UserGotoBarPages = ("Foo", "Bar");',
|
||||
'config', 0, 1),
|
||||
'//div[@class="header"]/span[@class="gotobar bar"]/a[@class="local"][text()="Foo"]/following-sibling::a[@class="local"][text()="Bar"]');
|
||||
'//div[@class="header"]/div[@class="menu"]/span[@class="gotobar bar"]/a[@class="local"][text()="Foo"]/following-sibling::a[@class="local"][text()="Bar"]');
|
||||
|
||||
299
t/edit-paragraphs.t
Normal file
299
t/edit-paragraphs.t
Normal file
@@ -0,0 +1,299 @@
|
||||
# Copyright (C) 2014 Alex Schroeder <alex@gnu.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 56;
|
||||
use utf8;
|
||||
clear_pages();
|
||||
|
||||
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
|
||||
|
||||
add_module('edit-paragraphs.pl');
|
||||
|
||||
my $text = q{Give me a torch: I am not for this ambling;
|
||||
Being but heavy, I will bear the light.
|
||||
|
||||
Nay, gentle Romeo, we must have you dance.
|
||||
|
||||
|
||||
Not I, believe me: you have dancing shoes
|
||||
With nimble soles: I have a soul of lead
|
||||
So stakes me to the ground I cannot move.
|
||||
};
|
||||
|
||||
my $page = update_page('Romeo_and_Mercutio', $text);
|
||||
for my $paragraph (split(/\n\n+/, $text)) {
|
||||
test_page($page, 'action=edit-paragraph;title=Romeo_and_Mercutio;around=\d*;paragraph='
|
||||
. UrlEncode($paragraph));
|
||||
}
|
||||
|
||||
# Check whether the form is right.
|
||||
|
||||
ok($page =~ /action=edit-paragraph;title=Romeo_and_Mercutio;around=(\d*);paragraph=([^"]*)/, 'found example link to use');
|
||||
my $around = $1;
|
||||
my $enc = $2;
|
||||
my $par = UrlDecode($enc);
|
||||
xpath_test(get_page("action=edit-paragraph title=Romeo_and_Mercutio around=$around paragraph=$enc"),
|
||||
q'//input[@type="hidden"][@value="Romeo_and_Mercutio"][@name="title"]',
|
||||
q'//input[@type="hidden"][@value="edit-paragraph"][@name="action"]',
|
||||
qq'//textarea[text()="$par"]',
|
||||
qq'//input[\@type="hidden"][\@value="$around"][\@name="around"]');
|
||||
|
||||
# Test for extra links in empty paragraphs before and after tables.
|
||||
|
||||
add_module('creole.pl');
|
||||
|
||||
$text = q{|PARIS |JULIET |
|
||||
|Come you to make confession to this father? |To answer that, I should confess to you. |
|
||||
|Do not deny to him that you love me. |I will confess to you that I love him. |
|
||||
|So will ye, I am sure, that you love me. | |
|
||||
|
||||
-- William Shakespeare, Romeo and Juliet, Act IV, Scene I
|
||||
};
|
||||
|
||||
$page = update_page('Paris_and_Juliet', $text);
|
||||
test_page_negative($page, '\|', '</table><p><a ');
|
||||
|
||||
for my $row (split(/\n/, $text)) {
|
||||
test_page($page, 'action=edit-paragraph;title=Paris_and_Juliet;around=\d*;paragraph='
|
||||
. UrlEncode($row));
|
||||
}
|
||||
|
||||
$text = q{== Romeo and Juliet, Act III, Scene II
|
||||
|
||||
* Tybalt is gone, and Romeo banished;
|
||||
Romeo that kill'd him, he is banished.
|
||||
* O God! did Romeo's hand shed Tybalt's blood?
|
||||
* It did, it did; alas the day, it did!
|
||||
};
|
||||
|
||||
$page = update_page('Nurse_and_Juliet', $text);
|
||||
|
||||
for my $item (split(/\n(?=[*])/, $text)) {
|
||||
my $str = UrlEncode($item);
|
||||
$str =~ s/\*/\\*/g;
|
||||
test_page($page, 'action=edit-paragraph;title=Nurse_and_Juliet;around=\d*;paragraph='
|
||||
. $str);
|
||||
}
|
||||
|
||||
$text = q{Benvolio: Tell me in sadness, who is that you love.
|
||||
|
||||
Romeo: What, shall I groan and tell thee?
|
||||
|
||||
Benvolio: Groan! why, no. But sadly tell me who.
|
||||
|
||||
Romeo: Bid a sick man in sadness make his will: Ah, word ill urged to
|
||||
one that is so ill! In sadness, cousin, I do love a woman.
|
||||
|
||||
Benvolio: I aim'd so near, when I supposed you loved.
|
||||
|
||||
Romeo: A right good mark-man! And she's fair I love.
|
||||
|
||||
Benvolio: A right fair mark, fair coz, is soonest hit.
|
||||
};
|
||||
|
||||
# Replace the first occurence.
|
||||
|
||||
test_page(update_page('Benvolio_and_Romeo', $text),
|
||||
'Benvolio: Tell me in sadness');
|
||||
test_page(get_page('action=edit-paragraph title=Benvolio_and_Romeo '
|
||||
. 'around=0 paragraph=Benvolio text=Ben'),
|
||||
# not using update_page because of the parameters
|
||||
'Status: 302');
|
||||
test_page(get_page('Benvolio_and_Romeo'),
|
||||
'Ben: Tell me in sadness',
|
||||
'Benvolio: Groan!');
|
||||
|
||||
# Reset and try again but replace the occurence around 105.
|
||||
|
||||
update_page('Benvolio_and_Romeo', $text);
|
||||
test_page(get_page('action=edit-paragraph title=Benvolio_and_Romeo '
|
||||
. 'around=105 '
|
||||
. 'paragraph=Benvolio text=Ben'),
|
||||
# not using update_page because of the parameters
|
||||
'Status: 302');
|
||||
test_page(get_page('Benvolio_and_Romeo'),
|
||||
'Benvolio: Tell me in sadness',
|
||||
'Ben: Groan!');
|
||||
|
||||
SKIP: {
|
||||
|
||||
skip "We don't do fuzzy matching anymore.", 3;
|
||||
|
||||
# Try again but now let's simulate a page changed in the background
|
||||
# such that the text is now not exactly at the expected position, but
|
||||
# close by.
|
||||
$text =~ s/tell thee/tell you/;
|
||||
update_page('Benvolio_and_Romeo', $text);
|
||||
test_page(get_page('action=edit-paragraph title=Benvolio_and_Romeo '
|
||||
. 'around=105 '
|
||||
. 'paragraph=Benvolio text=Ben'),
|
||||
# not using update_page because of the parameters
|
||||
'Status: 302');
|
||||
test_page(get_page('Benvolio_and_Romeo'),
|
||||
'Benvolio: Tell me in sadness',
|
||||
'Ben: Groan!');
|
||||
}
|
||||
|
||||
# HTML encoding.
|
||||
|
||||
$text = q{I am hurt.
|
||||
<em>A plague o' both your houses!</em> I am sped.
|
||||
};
|
||||
xpath_test(get_page('action=edit-paragraph title=Mercutio paragraph="' . UrlEncode($text) . '"'),
|
||||
qq'//textarea[text()="$text"]');
|
||||
|
||||
# Make sure the link at the very end is shown.
|
||||
|
||||
$text = q{
|
||||
{{{
|
||||
BENVOLIO Romeo, away, be gone!
|
||||
The citizens are up, and Tybalt slain.
|
||||
Stand not amazed: the prince will doom thee death,
|
||||
If thou art taken: hence, be gone, away!
|
||||
|
||||
ROMEO O, I am fortune's fool!
|
||||
}}}
|
||||
|
||||
----
|
||||
|
||||
William Shakespeare, Romeo and Juliet, Act III, Scene I
|
||||
};
|
||||
|
||||
$page = update_page('Benvolio_and_Romeo', $text);
|
||||
test_page_negative($page, '</pre><p><a ', '<p><a ');
|
||||
test_page($page, 'fool!<a ', '</pre><hr ?/><p>William', 'Scene I<a');
|
||||
|
||||
# Mixed lists.
|
||||
|
||||
$text = q{* One
|
||||
## Two
|
||||
};
|
||||
|
||||
$page = update_page('Alex_Daniel', $text);
|
||||
for my $item (split(/\n(?=[\*\#])/, $text)) {
|
||||
my $str = UrlEncode($item);
|
||||
$str =~ s/\*/\\*/g;
|
||||
test_page($page, 'action=edit-paragraph;title=Alex_Daniel;around=\d*;paragraph='
|
||||
. $str);
|
||||
}
|
||||
|
||||
# Comments, the last element in particular.
|
||||
|
||||
$text = q{Test
|
||||
|
||||
-- Anonymous
|
||||
|
||||
----
|
||||
|
||||
Test
|
||||
|
||||
-- Real Anonymous
|
||||
};
|
||||
|
||||
$page = update_page('Comments_on_Alex_Daniel', $text);
|
||||
test_page($page, 'action=edit-paragraph;title=Comments_on_Alex_Daniel;around=\d*;paragraph=--%20Real%20Anonymous%0a');
|
||||
|
||||
# More than one newline at the end.
|
||||
|
||||
$text = q{one
|
||||
|
||||
two
|
||||
|
||||
};
|
||||
|
||||
$page = update_page('Alex_Daniel', $text);
|
||||
for my $item (split(/\n+/, $text)) {
|
||||
my $str = UrlEncode($item);
|
||||
$str =~ s/\*/\\*/g;
|
||||
test_page($page, 'action=edit-paragraph;title=Alex_Daniel;around=\d*;paragraph='
|
||||
. $str);
|
||||
}
|
||||
|
||||
# More HTML encoding.
|
||||
|
||||
$text = q{Test.
|
||||
|
||||
<test1>
|
||||
|
||||
<test2>
|
||||
|
||||
<test3>
|
||||
};
|
||||
|
||||
$page = update_page('Test', $text);
|
||||
test_page_negative(get_page('action=browse id=Test raw=1'), '<');
|
||||
# HTML encoded text is longer: every < > counts adds 3. Thus 33 + 6x3 = 51.
|
||||
my $action = 'action=edit-paragraph;title=Test;around=51;paragraph=' . UrlEncode("<test3>\n");
|
||||
test_page($page, $action);
|
||||
test_page(get_page(join(' ', split(';', $action))), QuoteHtml("<test3>\n"));
|
||||
# test error
|
||||
test_page(get_page('action=edit-paragraph title=Test'
|
||||
. ' paragraph=' . UrlEncode("<test3>\n") # old
|
||||
. ' text=' . UrlEncode("<test30>\n") # new
|
||||
. ' around=1'),
|
||||
'Could not identify the paragraph you were editing',
|
||||
'<pre>' . QuoteHtml("<test3>\n") . '</pre>');
|
||||
test_page(get_page('action=edit-paragraph title=Test'
|
||||
. ' paragraph=' . UrlEncode("<test3>\n") # old
|
||||
. ' text=' . UrlEncode("<test30>\n") # new
|
||||
. ' around=51'), 'Status: 302');
|
||||
$text =~ s/test3/test30/;
|
||||
test_page(get_page('action=browse id=Test raw=1'), $text);
|
||||
|
||||
# ampersand in a pagename
|
||||
|
||||
$text = q{d4
|
||||
|
||||
d5
|
||||
|
||||
d8
|
||||
|
||||
d10
|
||||
};
|
||||
|
||||
$page = update_page('D%26D', $text);
|
||||
my $action = 'action=edit-paragraph;title=D%26D;around=8;paragraph=d5%0a%0a';
|
||||
test_page($page, $action);
|
||||
test_page(get_page(join(' ', split(';', $action))), 'd5');
|
||||
# test error
|
||||
test_page(get_page('action=edit-paragraph title=D%26D'
|
||||
. ' paragraph=d5%0a%0a' # old
|
||||
. ' text=d6%0a%0a' # new
|
||||
. ' around=8'),
|
||||
'Status: 302');
|
||||
$text =~ s/d5/d6/;
|
||||
test_page(get_page('action=browse id=D%26D raw=1'), $text);
|
||||
|
||||
# questionmark, square brackets, bullet lists in the edited text
|
||||
|
||||
$text = q{* who?
|
||||
* [where]?
|
||||
* why?
|
||||
* what?
|
||||
};
|
||||
|
||||
$page = update_page('Questions', $text);
|
||||
my $action = 'action=edit-paragraph;title=Questions;around=18;paragraph=*%20%5bwhere%5d%3f%0a';
|
||||
test_page($page, quotemeta($action));
|
||||
test_page(get_page(join(' ', split(';', $action))), '\[where\]\?');
|
||||
# test error
|
||||
test_page(get_page('action=edit-paragraph title=Questions'
|
||||
. ' paragraph=*%20%5bwhere%5d%3f%0a' # old
|
||||
. ' text=*%20how%3f%0a' # new
|
||||
. ' around=18'),
|
||||
'Status: 302');
|
||||
$text =~ s/\[where\]\?/how?/;
|
||||
test_page(get_page('action=browse id=Questions raw=1'), quotemeta($text));
|
||||
@@ -23,7 +23,7 @@ add_module('page-trail.pl');
|
||||
my $page = get_page('FirstPage');
|
||||
|
||||
xpath_test($page,
|
||||
'//div[@class="header"]/span[@class="gotobar bar"]/following-sibling::span[@class="trail"]',
|
||||
'//div[@class="header"]/div[@class="menu"]/span[@class="gotobar bar"]/following-sibling::span[@class="trail"]',
|
||||
'//span[@class="trail"][contains(text(),"Trail: ")]/br',
|
||||
'//span[@class="trail"]/a[@class="local"][@href="http://localhost/wiki.pl/FirstPage"][text()="FirstPage"]');
|
||||
|
||||
|
||||
48
t/rss.t
48
t/rss.t
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2006, 2008 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2006–2015 Alex Schroeder <alex@gnu.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 100;
|
||||
use Test::More tests => 114;
|
||||
use utf8; # tests contain UTF-8 characters and it matters
|
||||
|
||||
clear_pages();
|
||||
@@ -72,7 +72,7 @@ SKIP: {
|
||||
require XML::RSS;
|
||||
};
|
||||
|
||||
skip "XML::RSS not installed", 75 if $@;
|
||||
skip "XML::RSS not installed", 89 if $@;
|
||||
|
||||
use Cwd;
|
||||
$dir = cwd;
|
||||
@@ -216,4 +216,46 @@ unified rc for here and meatball
|
||||
<span class="contributor"><span> \. \. \. \. </span>AlexSchroeder</span>
|
||||
http://www.emacswiki.org/cgi-bin/community\?action=browse;id=RecentNearChanges;revision=1
|
||||
EOT
|
||||
|
||||
# Have multiple, separate feeds on a page.
|
||||
update_page('RSS', "One:\n\n<rss $uri/mb.rdf>\n\nTwo:\n\n<rss $uri/community.rdf>");
|
||||
test_page(get_page('RSS'), split('\n',<<'EOT'));
|
||||
LionKimbro
|
||||
2003-10-24T22:49:33\+06:00
|
||||
RecentNearChanges
|
||||
http://www.usemod.com/cgi-bin/mb.pl\?LionKimbro
|
||||
2003-10-24T21:02:53\+00:00
|
||||
unified rc for here and meatball
|
||||
<span class="contributor"><span> \. \. \. \. </span>AlexSchroeder</span>
|
||||
http://www.emacswiki.org/cgi-bin/community\?action=browse;id=RecentNearChanges;revision=1
|
||||
EOT
|
||||
|
||||
# Have multiple, separate feeds on a page, in a long table
|
||||
add_module('tables-long.pl');
|
||||
update_page('RSS', qq"Everything in a long table.
|
||||
|
||||
<table/mainpage a/third, b/third, c/third>
|
||||
a: Fire Engineering Training
|
||||
b: Fire Engineering LODDs
|
||||
c: Irons & Ladders
|
||||
|
||||
a:
|
||||
<rss 3 $uri/mb.rdf>
|
||||
|
||||
b:
|
||||
<rss 3 $uri/community.rdf>
|
||||
|
||||
c:
|
||||
<rss 3 $uri/rss1.0.rdf>
|
||||
----
|
||||
");
|
||||
test_page(get_page('RSS'), split('\n',<<'EOT'));
|
||||
reply to Scott's comment \(need threading!\)
|
||||
reply to sunir
|
||||
WikiEmigration is the way to go
|
||||
unified rc for here and meatball
|
||||
see newpage if you have a namepage on MeatballWiki
|
||||
GTKeyboard is a graphical keyboard that
|
||||
EOT
|
||||
|
||||
}
|
||||
|
||||
15
t/search.t
15
t/search.t
@@ -63,6 +63,8 @@ test_page(get_page('search=fooz replace=fuuz pwd=foo'), split('\n',<<'EOT'));
|
||||
This is <strong>fuuz</strong> and this is barz.
|
||||
EOT
|
||||
|
||||
# 'This is fuuz and this is barz.'
|
||||
|
||||
# Replace with empty string
|
||||
|
||||
test_page(get_page('search=this%20is%20 replace= pwd=foo delete=1'), split('\n',<<'EOT'));
|
||||
@@ -71,8 +73,11 @@ test_page(get_page('search=this%20is%20 replace= pwd=foo delete=1'), split('\n',
|
||||
fuuz and barz.
|
||||
EOT
|
||||
|
||||
# Replace with backreferences, where the replacement pattern is no longer found
|
||||
# 'fuuz and barz.'
|
||||
|
||||
|
||||
# Replace with backreferences, where the replacement pattern is no longer found.
|
||||
# Take 'fuuz and barz.' and replace ([a-z]+)z with x$1 results in 'xfuu and xbar.'
|
||||
test_page(get_page('"search=([a-z]%2b)z" replace=x%241 pwd=foo'), '1 pages found');
|
||||
test_page(get_page('SearchAndReplace'), 'xfuu and xbar.');
|
||||
|
||||
@@ -124,9 +129,5 @@ test_page(get_page('search="<b>"'),
|
||||
|
||||
# Test fallback when grep is unavailable
|
||||
|
||||
TODO: {
|
||||
local $TODO = "Don't get a decent error when opening the grep pipe";
|
||||
AppendStringToFile($ConfigFile, "\$ENV{PATH} = '';\n");
|
||||
test_page(get_page('search=empty'),
|
||||
"1 pages found");
|
||||
}
|
||||
AppendStringToFile($ConfigFile, "\$ENV{PATH} = '';\n");
|
||||
test_page(get_page('search=empty'), "1 pages found");
|
||||
|
||||
@@ -28,21 +28,21 @@ test_page(update_page('Diary', "This is the land of the crab-men.\n\n<journal>")
|
||||
|
||||
run_tests(split('\n',<<'EOT'));
|
||||
<table a,b>\na=a\nb=b\na=one\nb=two
|
||||
<table class="user long"><tr><th>a</th><th>b</th></tr><tr><td>one</td><td>two</td></tr></table>
|
||||
<table class="user long"><tr class="first odd"><th>a</th><th>b</th></tr><tr class="even"><td>one</td><td>two</td></tr></table>
|
||||
<table a,b>\na=a\nb=b\na=one\nb=two\n----
|
||||
<table class="user long"><tr><th>a</th><th>b</th></tr><tr><td>one</td><td>two</td></tr></table>
|
||||
<table class="user long"><tr class="first odd"><th>a</th><th>b</th></tr><tr class="even"><td>one</td><td>two</td></tr></table>
|
||||
<table a,b>\na=a\nb=b\na=one\nb=two\n----\n\nDone.
|
||||
<table class="user long"><tr><th>a</th><th>b</th></tr><tr><td>one</td><td>two</td></tr></table><p>Done.</p>
|
||||
<table class="user long"><tr class="first odd"><th>a</th><th>b</th></tr><tr class="even"><td>one</td><td>two</td></tr></table><p>Done.</p>
|
||||
Here is a table:\n<table a,b>\na=a\nb=b\na=one\ntwo\nand a half\nb=three\na=foo\nb=bar\n----\n\nDone.\n<table foo,bar>\nfoo=test\nbar=test as well\nfoo=what we test\n----\nthe end.
|
||||
Here is a table: <table class="user long"><tr><th>a</th><th>b</th></tr><tr><td>one two and a half</td><td>three</td></tr><tr><td>foo</td><td>bar</td></tr></table><p>Done. </p><table class="user long"><tr><th>test</th><th>test as well</th></tr><tr><td colspan="2">what we test</td></tr></table><p>the end.</p>
|
||||
Here is a table: <table class="user long"><tr class="first odd"><th>a</th><th>b</th></tr><tr class="even"><td>one two and a half</td><td>three</td></tr><tr class="odd"><td>foo</td><td>bar</td></tr></table><p>Done. </p><table class="user long"><tr class="first odd"><th>test</th><th>test as well</th></tr><tr class="even"><td colspan="2">what we test</td></tr></table><p>the end.</p>
|
||||
<table a,b>\na=a\nb=b\na=one\nb/2=odd\na=three
|
||||
<table class="user long"><tr><th>a</th><th>b</th></tr><tr><td>one</td><td rowspan="2">odd</td></tr><tr><td>three</td></tr></table>
|
||||
<table class="user long"><tr class="first odd"><th>a</th><th>b</th></tr><tr class="even"><td>one</td><td rowspan="2">odd</td></tr><tr class="odd"><td>three</td></tr></table>
|
||||
<table a,b,c>\na=a\nb=b\nc=c\na=one\nb/2=odd\nc=two\na=three\nc=four
|
||||
<table class="user long"><tr><th>a</th><th>b</th><th>c</th></tr><tr><td>one</td><td rowspan="2">odd</td><td>two</td></tr><tr><td>three</td><td>four</td></tr></table>
|
||||
<table class="user long"><tr class="first odd"><th>a</th><th>b</th><th>c</th></tr><tr class="even"><td>one</td><td rowspan="2">odd</td><td>two</td></tr><tr class="odd"><td>three</td><td>four</td></tr></table>
|
||||
<table a,b,c>\na=a\nb=b\nc=c\na=one\nb=two\nc/2=numbers\na=three\n
|
||||
<table class="user long"><tr><th>a</th><th>b</th><th>c</th></tr><tr><td>one</td><td>two</td><td rowspan="2">numbers</td></tr><tr><td colspan="2">three</td></tr></table>
|
||||
<table class="user long"><tr class="first odd"><th>a</th><th>b</th><th>c</th></tr><tr class="even"><td>one</td><td>two</td><td rowspan="2">numbers</td></tr><tr class="odd"><td colspan="2">three</td></tr></table>
|
||||
<table a, b, c>\na:0\nb:1\nc:00\n----\n
|
||||
<table class="user long"><tr><th>0</th><th>1</th><th>00</th></tr></table>
|
||||
<table class="user long"><tr class="first odd"><th>0</th><th>1</th><th>00</th></tr></table>
|
||||
EOT
|
||||
|
||||
add_module('portrait-support.pl');
|
||||
31
t/tags.t
31
t/tags.t
@@ -49,50 +49,49 @@ update_page('Pödgecäst´s', 'Another [[tag:podcast]]');
|
||||
update_page('Alex', 'Me! [[tag:Old School]]');
|
||||
|
||||
# open the DB file
|
||||
require DB_File;
|
||||
tie %h, "DB_File", $TagFile;
|
||||
my %h = TagReadHash();
|
||||
|
||||
%tag = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("_Brilliant")}));
|
||||
%tag = map {$_=>1} @{$h{"_Brilliant"}};
|
||||
ok($tag{podcast}, 'Brilliant page tagged podcast');
|
||||
ok($tag{mag}, 'Brilliant page tagged mag');
|
||||
%tag = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("_Pödgecäst´s")}));
|
||||
%tag = map {$_=>1} @{$h{"_Pödgecäst´s"}};
|
||||
ok($tag{podcast}, 'Pödgecäst´s page tagged podcast');
|
||||
%file = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("podcast")}));
|
||||
%file = map {$_=>1} @{$h{"podcast"}};
|
||||
ok($file{Brilliant}, 'Tag podcast applies to page Brilliant');
|
||||
ok($file{"Pödgecäst´s"}, 'Tag podcast applies to page Pödgecäst´s');
|
||||
%file = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("mag")}));
|
||||
%file = map {$_=>1} @{$h{"mag"}};
|
||||
ok($file{Brilliant}, 'Tag mag applies to page Brilliant');
|
||||
%file = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("old_school")}));
|
||||
%file = map {$_=>1} @{$h{"old_school"}};
|
||||
ok($file{Alex}, 'Tag Old School applies to page Alex');
|
||||
|
||||
# close the DB file before making changes via the wiki!
|
||||
untie %h;
|
||||
TagWriteHash(\%h);
|
||||
|
||||
update_page('Brilliant', 'Gameologists [[tag:mag]]');
|
||||
|
||||
# reopen changed file
|
||||
tie %h, "DB_File", $TagFile;
|
||||
%h = TagReadHash();
|
||||
|
||||
%tag = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("_Brilliant")}));
|
||||
%tag = map {$_=>1} @{$h{"_Brilliant"}};
|
||||
ok(!$tag{podcast}, 'Brilliant page no longer tagged podcast');
|
||||
ok($tag{mag}, 'Brilliant page still tagged mag');
|
||||
%file = map {$_=>1} split($FS, UrlDecode($h{UrlEncode("podcast")}));
|
||||
%file = map {$_=>1} @{$h{"podcast"}};
|
||||
ok(!$file{Brilliant}, 'Tag podcast no longer applies to page Brilliant');
|
||||
ok($file{"Pödgecäst´s"}, 'Tag podcast still applies to page Pödgecäst´s');
|
||||
|
||||
# close the DB file before making changes via the wiki!
|
||||
untie %h;
|
||||
TagWriteHash(\%h);
|
||||
|
||||
DeletePage('Brilliant');
|
||||
|
||||
# reopen changed file
|
||||
tie %h, "DB_File", $TagFile;
|
||||
%h = TagReadHash();
|
||||
|
||||
ok(!$h{UrlEncode("_Brilliant")}, 'Brilliant page no longer exists');
|
||||
ok(!exists($h{UrlEncode("mag")}), 'No page tagged mag exists');
|
||||
ok(!$h{"_Brilliant"}, 'Brilliant page no longer exists');
|
||||
ok(!exists($h{"mag"}), 'No page tagged mag exists');
|
||||
|
||||
# close the DB file before making changes via the wiki!
|
||||
untie %h;
|
||||
TagWriteHash(\%h);
|
||||
|
||||
update_page('Brilliant', 'Gameologists [[tag:podcast]] [[tag:mag]]');
|
||||
update_page('Sons', 'of Kryos [[tag:Podcast]]');
|
||||
|
||||
@@ -28,6 +28,7 @@ test_page(update_page('Logo', "#FILE image/foo\niVBORw0KGgoAAAA"), 'This page is
|
||||
$page = update_page('alex pic', "#FILE image/png\niVBORw0KGgoAAAA");
|
||||
test_page($page, 'This page contains an uploaded file:');
|
||||
xpath_test($page, '//img[@class="upload"][@src="http://localhost/wiki.pl/download/alex_pic"][@alt="alex pic"]');
|
||||
exit;
|
||||
test_page_negative($page, 'AAAA');
|
||||
test_page_negative(get_page('search=AAA raw=1'), 'alex_pic');
|
||||
test_page(get_page('search=alex raw=1'), 'alex_pic', 'image/png');
|
||||
|
||||
282
wiki.pl
282
wiki.pl
@@ -29,41 +29,36 @@
|
||||
|
||||
package OddMuse;
|
||||
use strict;
|
||||
use utf8; # in case anybody ever addes UTF8 characters to the source
|
||||
use CGI qw/-utf8/;
|
||||
use CGI::Carp qw(fatalsToBrowser);
|
||||
use File::Glob ':glob';
|
||||
use File::Basename;
|
||||
local $| = 1; # Do not buffer output (localized for mod_perl)
|
||||
|
||||
# Options:
|
||||
use vars qw($RssLicense $RssCacheHours @RcDays $TempDir $LockDir $DataDir
|
||||
$KeepDir $PageDir $RcOldFile $IndexFile $BannedContent $NoEditFile $BannedHosts
|
||||
$ConfigFile $FullUrl $SiteName $HomePage $LogoUrl $RcDefault $RssDir
|
||||
$IndentLimit $RecentTop $RecentLink $EditAllowed $UseDiff $KeepDays $KeepMajor
|
||||
$EmbedWiki $BracketText $UseConfig $AdminPass $EditPass
|
||||
$PassHashFunction $PassSalt $NetworkFile
|
||||
$BracketWiki $FreeLinks $WikiLinks $SummaryHours $FreeLinkPattern $RCName
|
||||
$RunCGI $ShowEdits $LinkPattern $RssExclude $InterLinkPattern $MaxPost $UseGrep
|
||||
$UrlPattern $UrlProtocols $ImageExtensions $InterSitePattern $FS $CookieName
|
||||
$SiteBase $StyleSheet $NotFoundPg $FooterNote $NewText $EditNote $UserGotoBar
|
||||
$VisitorFile $RcFile %Smilies %SpecialDays $InterWikiMoniker $SiteDescription
|
||||
$RssImageUrl $ReadMe $RssRights $BannedCanRead $SurgeProtection $TopLinkBar
|
||||
$LanguageLimit $SurgeProtectionTime $SurgeProtectionViews $DeletedPage
|
||||
%Languages $InterMap $ValidatorLink %LockOnCreation $RssStyleSheet
|
||||
%CookieParameters @UserGotoBarPages $NewComment $HtmlHeaders $StyleSheetPage
|
||||
$ConfigPage $ScriptName $CommentsPrefix $CommentsPattern @UploadTypes $AllNetworkFiles
|
||||
$UsePathInfo $UploadAllowed $LastUpdate $PageCluster %PlainTextPages
|
||||
$RssInterwikiTranslate $UseCache $Counter $ModuleDir $FullUrlPattern
|
||||
$SummaryDefaultLength $FreeInterLinkPattern %InvisibleCookieParameters
|
||||
%AdminPages $UseQuestionmark $JournalLimit $LockExpiration $RssStrip
|
||||
%LockExpires @IndexOptions @Debugging $DocumentHeader %HtmlEnvironmentContainers
|
||||
@MyAdminCode @MyFooters @MyInitVariables @MyMacros @MyMaintenance @MyRules);
|
||||
use vars qw($RssLicense $RssCacheHours @RcDays $TempDir $LockDir $DataDir $KeepDir $PageDir $FileDir $RcOldFile $IndexFile
|
||||
$BannedContent $NoEditFile $BannedHosts $ConfigFile $FullUrl $SiteName $HomePage $LogoUrl $RcDefault $RssDir
|
||||
$IndentLimit $RecentTop $RecentLink $EditAllowed $UseDiff $KeepDays $KeepMajor $EmbedWiki $BracketText $UseConfig
|
||||
$AdminPass $EditPass $PassHashFunction $PassSalt $NetworkFile $BracketWiki $FreeLinks $WikiLinks $SummaryHours
|
||||
$FreeLinkPattern $RCName $RunCGI $ShowEdits $LinkPattern $RssExclude $InterLinkPattern $MaxPost $UseGrep $UrlPattern
|
||||
$UrlProtocols $ImageExtensions $InterSitePattern $FS $CookieName $SiteBase $StyleSheet $NotFoundPg $FooterNote $NewText
|
||||
$EditNote $UserGotoBar $VisitorFile $RcFile %Smilies %SpecialDays $InterWikiMoniker $SiteDescription $RssImageUrl
|
||||
$ReadMe $RssRights $BannedCanRead $SurgeProtection $TopLinkBar $TopSearchForm $MatchingPages $LanguageLimit
|
||||
$SurgeProtectionTime $SurgeProtectionViews $DeletedPage %Languages $InterMap $ValidatorLink %LockOnCreation
|
||||
$RssStyleSheet %CookieParameters @UserGotoBarPages $NewComment $HtmlHeaders $StyleSheetPage $ConfigPage $ScriptName
|
||||
$CommentsPrefix $CommentsPattern @UploadTypes $AllNetworkFiles $UsePathInfo $UploadAllowed $FilenameWhitelist @AdditionalChars
|
||||
$LastUpdate $PageCluster
|
||||
%PlainTextPages $RssInterwikiTranslate $UseCache $Counter $ModuleDir $FullUrlPattern $SummaryDefaultLength
|
||||
$FreeInterLinkPattern %InvisibleCookieParameters %AdminPages $UseQuestionmark $JournalLimit $LockExpiration $RssStrip
|
||||
%LockExpires @IndexOptions @Debugging $DocumentHeader %HtmlEnvironmentContainers @MyAdminCode @MyFooters
|
||||
@MyInitVariables @MyMacros @MyMaintenance @MyRules);
|
||||
|
||||
# Internal variables:
|
||||
use vars qw(%Page %InterSite %IndexHash %Translate %OldCookie $FootnoteNumber
|
||||
$OpenPageName @IndexList $Message $q $Now %RecentVisitors @HtmlStack
|
||||
@HtmlAttrStack $ReplaceForm %MyInc $CollectingJournal $bol $WikiDescription
|
||||
$PrintedHeader %Locks $Fragment @Blocks @Flags $Today @KnownLocks
|
||||
$ModulesDescription %Action %RuleOrder %Includes %RssInterwikiTranslate);
|
||||
use vars qw(%Page %InterSite %IndexHash %Translate %OldCookie $FootnoteNumber $OpenPageName @IndexList $Message $q $Now
|
||||
%RecentVisitors @HtmlStack @HtmlAttrStack $ReplaceForm %MyInc $CollectingJournal $bol $WikiDescription $PrintedHeader
|
||||
%Locks $Fragment @Blocks @Flags $Today @KnownLocks $ModulesDescription %Action %RuleOrder %Includes
|
||||
%RssInterwikiTranslate);
|
||||
|
||||
# Can be set outside the script: $DataDir, $UseConfig, $ConfigFile, $ModuleDir,
|
||||
# $ConfigPage, $AdminPass, $EditPass, $ScriptName, $FullUrl, $RunCGI.
|
||||
@@ -97,7 +92,7 @@ $LogoUrl = ''; # URL for site logo ('' for no logo)
|
||||
$NotFoundPg = ''; # Page for not-found links ('' for blank pg)
|
||||
|
||||
$NewText = T('This page is empty.') . "\n"; # New page text
|
||||
$NewComment = T('Add your comment here:') . "\n"; # New comment text
|
||||
$NewComment = T('Add your comment here:'); # New comment text
|
||||
|
||||
$EditAllowed = 1; # 0 = no, 1 = yes, 2 = comments pages only, 3 = comments only
|
||||
$AdminPass //= ''; # Whitespace separated passwords.
|
||||
@@ -135,33 +130,37 @@ $ShowEdits = 0; # 1 = major and show minor edits in recent chang
|
||||
$RecentTop = 1; # 1 = most recent entries at the top of the list
|
||||
$RecentLink = 1; # 1 = link to usernames
|
||||
$PageCluster = ''; # name of cluster page, eg. 'Cluster' to enable
|
||||
$InterWikiMoniker = ''; # InterWiki prefix for this wiki for RSS
|
||||
$SiteDescription = ''; # RSS Description of this wiki
|
||||
$InterWikiMoniker = ''; # InterWiki prefix for this wiki for RSS
|
||||
$SiteDescription = ''; # RSS Description of this wiki
|
||||
$RssStrip = '^\d\d\d\d-\d\d-\d\d_'; # Regexp to strip from feed item titles
|
||||
$RssImageUrl = $LogoUrl; # URL to image to associate with your RSS feed
|
||||
$RssRights = ''; # Copyright notice for RSS, usually an URL to the appropriate text
|
||||
$RssImageUrl = $LogoUrl; # URL to image to associate with your RSS feed
|
||||
$RssRights = ''; # Copyright notice for RSS, usually an URL to the appropriate text
|
||||
$RssExclude = 'RssExclude'; # name of the page that lists pages to be excluded from the feed
|
||||
$RssCacheHours = 1; # How many hours to cache remote RSS files
|
||||
$RssStyleSheet = ''; # External style sheet for RSS files
|
||||
$UploadAllowed = 0; # 1 = yes, 0 = administrators only
|
||||
$RssCacheHours = 1; # How many hours to cache remote RSS files
|
||||
$RssStyleSheet = ''; # External style sheet for RSS files
|
||||
$UploadAllowed = 0; # 1 = yes, 0 = administrators only
|
||||
@UploadTypes = ('image/jpeg', 'image/png'); # MIME types allowed, all allowed if empty list
|
||||
$EmbedWiki = 0; # 1 = no headers/footers
|
||||
$FooterNote = ''; # HTML for bottom of every page
|
||||
$EditNote = ''; # HTML notice above buttons on edit page
|
||||
$TopLinkBar = 1; # 1 = add a goto bar at the top of the page
|
||||
@UserGotoBarPages = (); # List of pagenames
|
||||
$UserGotoBar = ''; # HTML added to end of goto bar
|
||||
$ValidatorLink = 0; # 1 = Link to the W3C HTML validator service
|
||||
$CommentsPrefix = ''; # prefix for comment pages, eg. 'Comments_on_' to enable
|
||||
$CommentsPattern = undef; # regex used to match comment pages
|
||||
$HtmlHeaders = ''; # Additional stuff to put in the HTML <head> section
|
||||
$IndentLimit = 20; # Maximum depth of nested lists
|
||||
$LanguageLimit = 3; # Number of matches req. for each language
|
||||
$JournalLimit = 200; # how many pages can be collected in one go?
|
||||
$FilenameWhitelist = 'a-zA-Z0-9_.-'; # Other characters will be removed from the filenames (uploaded files only)
|
||||
@AdditionalChars = ('A'..'Z', 'a'..'z', '0'..'9'); # These characters will be appended if the file already exists
|
||||
$EmbedWiki = 0; # 1 = no headers/footers
|
||||
$FooterNote = ''; # HTML for bottom of every page
|
||||
$EditNote = ''; # HTML notice above buttons on edit page
|
||||
$TopLinkBar = 1; # 0 = goto bar both at the top and bottom; 1 = top, 2 = bottom
|
||||
$TopSearchForm = 1; # 0 = search form both at the top and bottom; 1 = top, 2 = bottom
|
||||
$MatchingPages = 0; # 1 = search page content and page titles
|
||||
@UserGotoBarPages = (); # List of pagenames
|
||||
$UserGotoBar = ''; # HTML added to end of goto bar
|
||||
$ValidatorLink = 0; # 1 = Link to the W3C HTML validator service
|
||||
$CommentsPrefix = ''; # prefix for comment pages, eg. 'Comments_on_' to enable
|
||||
$CommentsPattern = undef; # regex used to match comment pages
|
||||
$HtmlHeaders = ''; # Additional stuff to put in the HTML <head> section
|
||||
$IndentLimit = 20; # Maximum depth of nested lists
|
||||
$LanguageLimit = 3; # Number of matches req. for each language
|
||||
$JournalLimit = 200; # how many pages can be collected in one go?
|
||||
$DocumentHeader = qq(<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN")
|
||||
. qq( "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n)
|
||||
. qq(<html xmlns="http://www.w3.org/1999/xhtml">);
|
||||
# Checkboxes at the end of the index.
|
||||
# Checkboxes at the end of the index.
|
||||
@IndexOptions = ();
|
||||
# Display short comments below the GotoBar for special days
|
||||
# Example: %SpecialDays = ('1-1' => 'New Year', '1-2' => 'Next Day');
|
||||
@@ -175,8 +174,8 @@ $DocumentHeader = qq(<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN")
|
||||
@KnownLocks = qw(main diff index merge visitors); # locks to remove
|
||||
$LockExpiration = 60; # How long before expirable locks are expired
|
||||
%LockExpires = (diff=>1, index=>1, merge=>1, visitors=>1); # locks to expire after some time
|
||||
%CookieParameters = (username=>'', pwd=>'', homepage=>'', theme=>'', css=>'', msg=>'',
|
||||
lang=>'', toplinkbar=>$TopLinkBar, embed=>$EmbedWiki, );
|
||||
%CookieParameters = (username=>'', pwd=>'', homepage=>'', theme=>'', css=>'', msg=>'', lang=>'', embed=>$EmbedWiki,
|
||||
toplinkbar=>$TopLinkBar, topsearchform=>$TopSearchForm, matchingpages=>$MatchingPages, );
|
||||
%InvisibleCookieParameters = (msg=>1, pwd=>1,);
|
||||
%Action = (rc => \&BrowseRc, rollback => \&DoRollback,
|
||||
browse => \&BrowseResolvedPage, maintain => \&DoMaintain,
|
||||
@@ -214,7 +213,8 @@ sub ReportError { # fatal!
|
||||
}
|
||||
|
||||
sub Init {
|
||||
binmode(STDOUT, ':utf8');
|
||||
binmode(STDOUT, ':utf8'); # this is where the HTML gets printed
|
||||
binmode(STDERR, ':utf8'); # just in case somebody prints debug info to stderr
|
||||
InitDirConfig();
|
||||
$FS = "\x1e"; # The FS character is the RECORD SEPARATOR control char in ASCII
|
||||
$Message = ''; # Warnings and non-fatal errors.
|
||||
@@ -252,6 +252,7 @@ sub InitConfig {
|
||||
sub InitDirConfig {
|
||||
utf8::decode($DataDir); # just in case, eg. "WikiDataDir=/tmp/Zürich♥ perl wiki.pl"
|
||||
$PageDir = "$DataDir/page"; # Stores page data
|
||||
$FileDir = "$DataDir/file"; # Stores uploaded files
|
||||
$KeepDir = "$DataDir/keep"; # Stores kept (old) page data
|
||||
$TempDir = "$DataDir/temp"; # Temporary files and locks
|
||||
$LockDir = "$TempDir/lock"; # DB is locked if this exists
|
||||
@@ -361,6 +362,7 @@ sub CookieRollbackFix {
|
||||
|
||||
sub GetParam {
|
||||
my ($name, $default) = @_;
|
||||
utf8::encode($name); # turn to byte string
|
||||
my $result = $q->param($name);
|
||||
$result //= $default;
|
||||
return QuoteHtml($result); # you need to unquote anything that can have <tags>
|
||||
@@ -424,7 +426,8 @@ sub ApplyRules {
|
||||
Clean(CloseHtmlEnvironments() . $q->pre($text));
|
||||
} elsif (my ($type) = TextIsFile($text)) { # TODO? $type defined here??
|
||||
Clean(CloseHtmlEnvironments() . $q->p(T('This page contains an uploaded file:'))
|
||||
. $q->p(GetDownloadLink($OpenPageName, (substr($type, 0, 6) eq 'image/'), $revision)));
|
||||
. $q->p(GetDownloadLink($OpenPageName, (substr($type, 0, 6) eq 'image/'), $revision))
|
||||
. (length $Page{summary} > 0 ? $q->blockquote(QuoteHtml($Page{summary})) : $q->p(T('No summary was provided for this file.'))));
|
||||
} else {
|
||||
my $smileyregex = join "|", keys %Smilies;
|
||||
$smileyregex = qr/(?=$smileyregex)/;
|
||||
@@ -1260,11 +1263,13 @@ sub PageHtml {
|
||||
local *STDOUT;
|
||||
OpenPage($id);
|
||||
open(STDOUT, '>', \$diff) or die "Can't open memory file: $!";
|
||||
binmode(STDOUT); # works whether STDOUT already has the UTF8 layer or not
|
||||
binmode(STDOUT, ":utf8");
|
||||
PrintPageDiff();
|
||||
utf8::decode($diff);
|
||||
return $error if $limit and length($diff) > $limit;
|
||||
open(STDOUT, '>', \$page) or die "Can't open memory file: $!";
|
||||
binmode(STDOUT); # works whether STDOUT already has the UTF8 layer or not
|
||||
binmode(STDOUT, ":utf8");
|
||||
PrintPageHtml();
|
||||
utf8::decode($page);
|
||||
@@ -1319,16 +1324,18 @@ sub DoBrowseRequest {
|
||||
my $id = GetId();
|
||||
my $action = lc(GetParam('action', '')); # script?action=foo;id=bar
|
||||
$action = 'download' if GetParam('download', '') and not $action; # script/download/id
|
||||
my $search = GetParam('search', '');
|
||||
if ($Action{$action}) {
|
||||
&{$Action{$action}}($id);
|
||||
} elsif ($action and defined &MyActions) {
|
||||
eval { local $SIG{__DIE__}; MyActions(); };
|
||||
} elsif ($action) {
|
||||
ReportError(Ts('Invalid action parameter %s', $action), '501 NOT IMPLEMENTED');
|
||||
} elsif ($search ne '') { # allow search for "0"
|
||||
SetParam('action', 'search'); # fake it
|
||||
DoSearch($search);
|
||||
} elsif (GetParam('match', '') ne '') {
|
||||
SetParam('action', 'index'); # make sure this gets a NOINDEX
|
||||
DoIndex();
|
||||
} elsif (GetParam('search', '') ne '') { # allow search for "0"
|
||||
SetParam('action', 'search'); # make sure this gets a NOINDEX
|
||||
DoSearch();
|
||||
} elsif (GetParam('title', '') and not GetParam('Cancel', '')) {
|
||||
DoPost(GetParam('title', ''));
|
||||
} else {
|
||||
@@ -1453,7 +1460,7 @@ sub PageFresh { # pages can depend on other pages (ie. last update), admin statu
|
||||
|
||||
sub PageEtag {
|
||||
my ($changed, $visible, %params) = CookieData();
|
||||
return UrlEncode(join($FS, $LastUpdate, sort(values %params))); # no CTL in field values
|
||||
return UrlEncode(join($FS, $LastUpdate||$Now, sort(values %params))); # no CTL in field values
|
||||
}
|
||||
|
||||
sub FileFresh { # old files are never stale, current files are stale when the page was modified
|
||||
@@ -1861,8 +1868,7 @@ sub GetRcRss {
|
||||
<channel>
|
||||
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
|
||||
};
|
||||
my $title = QuoteHtml($SiteName) . ': '
|
||||
. GetParam('title', QuoteHtml(NormalToFree($HomePage)));
|
||||
my $title = QuoteHtml($SiteName) . ': ' . GetParam('title', QuoteHtml(NormalToFree($HomePage)));
|
||||
$rss .= "<title>$title</title>\n";
|
||||
$rss .= "<link>" . ScriptUrl($HomePage) . "</link>\n";
|
||||
$rss .= qq{<atom:link href="} . GetScriptUrlWithRcParameters()
|
||||
@@ -2086,11 +2092,20 @@ sub DoAdminPage {
|
||||
my @menu = ();
|
||||
push(@menu, ScriptLink('action=index', T('Index of all pages'), 'index')) if $Action{index};
|
||||
push(@menu, ScriptLink('action=version', T('Wiki Version'), 'version')) if $Action{version};
|
||||
push(@menu, ScriptLink('action=unlock', T('Unlock Wiki'), 'unlock')) if $Action{unlock};
|
||||
push(@menu, ScriptLink('action=password', T('Password'), 'password')) if $Action{password};
|
||||
push(@menu, ScriptLink('action=password', T('Password'), 'password')) if $Action{password};
|
||||
push(@menu, ScriptLink('action=maintain', T('Run maintenance'), 'maintain')) if $Action{maintain};
|
||||
my @locks;
|
||||
for my $pattern (@KnownLocks) {
|
||||
for my $name (bsd_glob $pattern) {
|
||||
if (-d $LockDir . $name) {
|
||||
push(@locks, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (@locks and $Action{unlock}) {
|
||||
push(@menu, ScriptLink('action=unlock', T('Unlock Wiki'), 'unlock') . ' (' . join(', ', @locks) . ')');
|
||||
};
|
||||
if (UserIsAdmin()) {
|
||||
push(@menu, ScriptLink('action=clear', T('Clear Cache'), 'clear')) if $Action{clear};
|
||||
if ($Action{editlock}) {
|
||||
if (-f "$DataDir/noedit") {
|
||||
push(@menu, ScriptLink('action=editlock;set=0', T('Unlock site'), 'editlock 0'));
|
||||
@@ -2108,6 +2123,7 @@ sub DoAdminPage {
|
||||
Ts('Lock %s', $title), 'pagelock 1'));
|
||||
}
|
||||
}
|
||||
push(@menu, ScriptLink('action=clear', T('Clear Cache'), 'clear')) if $Action{clear};
|
||||
}
|
||||
foreach my $sub (@MyAdminCode) {
|
||||
&$sub($id, \@menu, \@rest);
|
||||
@@ -2208,10 +2224,10 @@ sub GetHeader {
|
||||
$result .= $q->start_div({-class=>'header'});
|
||||
if (not $embed and $LogoUrl) {
|
||||
my $url = $IndexHash{$LogoUrl} ? GetDownloadLink($LogoUrl, 2) : $LogoUrl;
|
||||
$result .= ScriptLink(UrlEncode($HomePage),
|
||||
$q->img({-src=>$url, -alt=>$alt, -class=>'logo'}), 'logo');
|
||||
$result .= ScriptLink(UrlEncode($HomePage), $q->img({-src=>$url, -alt=>$alt, -class=>'logo'}), 'logo');
|
||||
}
|
||||
if (GetParam('toplinkbar', $TopLinkBar)) {
|
||||
$result .= $q->start_div({-class=>'menu'});
|
||||
if (GetParam('toplinkbar', $TopLinkBar) != 2) {
|
||||
$result .= GetGotoBar($id);
|
||||
if (%SpecialDays) {
|
||||
my ($sec, $min, $hour, $mday, $mon, $year) = gmtime($Now);
|
||||
@@ -2221,6 +2237,8 @@ sub GetHeader {
|
||||
}
|
||||
}
|
||||
}
|
||||
$result .= GetSearchForm() if GetParam('topsearchform', $TopSearchForm) != 2;
|
||||
$result .= $q->end_div();
|
||||
$result .= $q->div({-class=>'message'}, $Message) if $Message;
|
||||
$result .= GetHeaderTitle($id, $title, $oldId);
|
||||
return $result . $q->end_div() . $q->start_div({-class=>'wrapper'});
|
||||
@@ -2235,11 +2253,17 @@ sub GetHeaderTitle {
|
||||
sub GetHttpHeader {
|
||||
return if $PrintedHeader;
|
||||
$PrintedHeader = 1;
|
||||
my ($type, $ts, $status, $encoding) = @_; # $ts is undef, a ts, or 'nocache'
|
||||
my ($type, $ts, $status, $encoding) = @_;
|
||||
$q->charset($type =~ m!^(text/|application/xml)! ? 'utf-8' : ''); # text/plain, text/html, application/xml: UTF-8
|
||||
my %headers = (-cache_control=>($UseCache < 0 ? 'no-cache' : 'max-age=10'));
|
||||
$headers{-etag} = $ts || PageEtag() if GetParam('cache', $UseCache) >= 2;
|
||||
$headers{'-last-modified'} = TimeToRFC822($ts) if $ts and $ts ne 'nocache'; # RFC 2616 section 13.3.4
|
||||
# Set $ts when serving raw content that cannot be modified by cookie parameters; or 'nocache'; or undef. If you
|
||||
# provide a $ts, the last-modiefied header generated will by used by HTTP/1.0 clients. If you provide no $ts, the etag
|
||||
# header generated will be used by HTTP/1.1 clients. In this situation, cookie parameters can influence the look of
|
||||
# the page and we cannot rely on $LastUpdate. HTTP/1.0 clients will ignore etags. See RFC 2616 section 13.3.4.
|
||||
if (GetParam('cache', $UseCache) >= 2 and $ts ne 'nocache') {
|
||||
$headers{'-last-modified'} = TimeToRFC822($ts) if $ts;
|
||||
$headers{-etag} = PageEtag();
|
||||
}
|
||||
$headers{-type} = GetParam('mime-type', $type);
|
||||
$headers{-status} = $status if $status;
|
||||
$headers{-Content_Encoding} = $encoding if $encoding;
|
||||
@@ -2355,9 +2379,11 @@ sub PrintFooter {
|
||||
return;
|
||||
}
|
||||
print GetCommentForm($id, $rev, $comment),
|
||||
$q->start_div({-class=>'wrapper close'}), $q->end_div(), $q->end_div(),
|
||||
$q->start_div({-class=>'footer'}), $q->hr(), GetGotoBar($id),
|
||||
GetFooterLinks($id, $rev), GetFooterTimestamp($id, $rev), GetSearchForm();
|
||||
$q->start_div({-class=>'wrapper close'}), $q->end_div(), $q->end_div();
|
||||
print $q->start_div({-class=>'footer'}), $q->hr();
|
||||
print GetGotoBar($id) if GetParam('toplinkbar', $TopLinkBar) != 1;
|
||||
print GetFooterLinks($id, $rev), GetFooterTimestamp($id, $rev);
|
||||
print GetSearchForm() if GetParam('topsearchform', $TopSearchForm) != 1;
|
||||
if ($DataDir =~ m|/tmp/|) {
|
||||
print $q->p($q->strong(T('Warning') . ': ')
|
||||
. Ts('Database is stored in temporary directory %s', $DataDir));
|
||||
@@ -2376,10 +2402,10 @@ sub PrintFooter {
|
||||
sub GetFooterTimestamp {
|
||||
my ($id, $rev) = @_;
|
||||
if ($id and $rev ne 'history' and $rev ne 'edit' and $Page{revision}) {
|
||||
my @elements = ($q->br(), ($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($Page{ts}),
|
||||
my @elements = (($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($Page{ts}),
|
||||
Ts('by %s', GetAuthorLink($Page{host}, $Page{username})));
|
||||
push(@elements, ScriptLinkDiff(2, $id, T('(diff)'), $rev)) if $UseDiff and $Page{revision} > 1;
|
||||
return $q->span({-class=>'time'}, @elements);
|
||||
return $q->div({-class=>'time'}, @elements);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -2416,7 +2442,7 @@ sub GetFooterLinks {
|
||||
$action .= ';id=' . UrlEncode($id) if $id;
|
||||
push(@elements, ScriptLink($action, T('Administration'), 'admin'));
|
||||
}
|
||||
return @elements ? $q->span({-class=>'edit bar'}, $q->br(), @elements) : '';
|
||||
return @elements ? $q->div({-class=>'edit bar'}, @elements) : '';
|
||||
}
|
||||
|
||||
sub GetCommentForm {
|
||||
@@ -2452,21 +2478,24 @@ sub GetFormStart {
|
||||
}
|
||||
|
||||
sub GetSearchForm {
|
||||
my $form = $q->label({-for=>'search'}, T('Search:')) . ' '
|
||||
. $q->textfield(-name=>'search', -id=>'search', -size=>20,
|
||||
-accesskey=>T('f')) . ' ';
|
||||
my $html = GetFormStart(undef, 'get', 'search') . $q->start_p;
|
||||
$html .= $q->label({-for=>'search'}, T('Search:')) . ' '
|
||||
. $q->textfield(-name=>'search', -id=>'search', -size=>20, -accesskey=>T('f')) . ' ';
|
||||
if ($ReplaceForm) {
|
||||
$form .= $q->label({-for=>'replace'}, T('Replace:')) . ' '
|
||||
. $q->textfield(-name=>'replace', -id=>'replace', -size=>20) . ' '
|
||||
$html .= $q->label({-for=>'replace'}, T('Replace:')) . ' '
|
||||
. $q->textfield(-name=>'replace', -id=>'replace', -size=>20) . ' '
|
||||
. $q->checkbox(-name=>'delete', -label=>T('Delete')) . ' ';
|
||||
}
|
||||
if (%Languages) {
|
||||
$form .= $q->label({-for=>'searchlang'}, T('Language:')) . ' '
|
||||
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>10,
|
||||
-default=>GetParam('lang', '')) . ' ';
|
||||
if (GetParam('matchingpages', $MatchingPages)) {
|
||||
$html .= $q->label({-for=>'matchingpage'}, T('Filter:')) . ' '
|
||||
. $q->textfield(-name=>'match', -id=>'matchingpage', -size=>20) . ' ';
|
||||
}
|
||||
return GetFormStart(undef, 'get', 'search')
|
||||
. $q->p($form . $q->submit('dosearch', T('Go!'))) . $q->end_form;
|
||||
if (%Languages) {
|
||||
$html .= $q->label({-for=>'searchlang'}, T('Language:')) . ' '
|
||||
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>10, -default=>GetParam('lang', '')) . ' ';
|
||||
}
|
||||
$html .= $q->submit('dosearch', T('Go!')) . $q->end_p . $q->end_form;
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub GetValidatorLink {
|
||||
@@ -2628,22 +2657,14 @@ sub DiffAddPrefix {
|
||||
return $q->div({-class=>$class}, $q->p(join($q->br(), @lines)));
|
||||
}
|
||||
|
||||
sub ParseData { # called a lot during search, so it was optimized
|
||||
my $data = shift; # by eliminating non-trivial regular expressions
|
||||
sub ParseData {
|
||||
my $data = shift;
|
||||
my %result;
|
||||
my $end = index($data, ': ');
|
||||
my $key = substr($data, 0, $end);
|
||||
my $start = $end += 2; # skip ': '
|
||||
while ($end = index($data, "\n", $end) + 1) { # include \n
|
||||
next if substr($data, $end, 1) eq "\t"; # continue after \n\t
|
||||
$result{$key} = substr($data, $start, $end - $start - 1); # strip last \n
|
||||
$start = index($data, ': ', $end); # starting at $end begins the new key
|
||||
last if $start == -1;
|
||||
$key = substr($data, $end, $start - $end);
|
||||
$end = $start += 2; # skip ': '
|
||||
while ($data =~ /(\S+?): (.*?)(?=\n[^ \t]|\Z)/sg) {
|
||||
my ($key, $value) = ($1, $2);
|
||||
$value =~ s/\n\t/\n/g;
|
||||
$result{$key} = $value;
|
||||
}
|
||||
$result{$key} .= substr($data, $end, -1); # strip last \n
|
||||
$result{$_} =~ s/\n\t/\n/g foreach (keys %result);
|
||||
return %result;
|
||||
}
|
||||
|
||||
@@ -3055,24 +3076,24 @@ sub DoDownload {
|
||||
OpenPage($id) if ValidIdOrDie($id);
|
||||
print $q->header(-status=>'304 NOT MODIFIED') and return if FileFresh(); # FileFresh needs an OpenPage!
|
||||
my ($text, $revision) = GetTextRevision(GetParam('revision', '')); # maybe revision reset!
|
||||
my $ts = $Page{ts};
|
||||
if (my ($type, $encoding) = TextIsFile($text)) {
|
||||
my ($data) = $text =~ /^[^\n]*\n(.*)/s;
|
||||
my %allowed = map {$_ => 1} @UploadTypes;
|
||||
if (@UploadTypes and not $allowed{$type}) {
|
||||
ReportError(Ts('Files of type %s are not allowed.', $type), '415 UNSUPPORTED MEDIA TYPE');
|
||||
}
|
||||
print GetHttpHeader($type, $ts, undef, $encoding);
|
||||
print GetHttpHeader($type, $Page{ts}, undef, $encoding);
|
||||
require MIME::Base64;
|
||||
binmode(STDOUT, ":pop:raw"); # need to pop utf8 for Windows users!?
|
||||
print MIME::Base64::decode($data);
|
||||
} else {
|
||||
print GetHttpHeader('text/plain', $ts);
|
||||
print GetHttpHeader('text/plain', $Page{ts});
|
||||
print $text;
|
||||
}
|
||||
}
|
||||
|
||||
sub DoPassword {
|
||||
my $id = shift;
|
||||
print GetHeader('', T('Password')), $q->start_div({-class=>'content password'});
|
||||
print $q->p(T('Your password is saved in a cookie, if you have cookies enabled. Cookies may get lost if you connect from another machine, from another account, or using another software.'));
|
||||
if (UserIsAdmin()) {
|
||||
@@ -3082,17 +3103,21 @@ sub DoPassword {
|
||||
} else {
|
||||
print $q->p(T('You are a normal user on this site.'));
|
||||
if ($AdminPass or $EditPass) {
|
||||
print $q->p(T('Your password does not match any of the administrator or editor passwords.'));
|
||||
print $q->p(T('Your password does not match any of the administrator or editor passwords.'));
|
||||
}
|
||||
}
|
||||
if ($AdminPass or $EditPass) {
|
||||
print GetFormStart(undef, undef, 'password'),
|
||||
$q->p(GetHiddenValue('action', 'password'), T('Password:'), ' ',
|
||||
$q->password_field(-name=>'pwd', -size=>20, -maxlength=>50),
|
||||
$q->hidden(-name=>'id', -value=>$id),
|
||||
$q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save'))), $q->end_form;
|
||||
} else {
|
||||
print $q->p(T('This site does not use admin or editor passwords.'));
|
||||
}
|
||||
if ($id) {
|
||||
print $q->p(ScriptLink('action=browse;id=' . UrlEncode($id) . '&time=' . time, T('Return to ' . NormalToFree($id))));
|
||||
}
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
}
|
||||
@@ -3275,7 +3300,7 @@ sub AllPagesList {
|
||||
}
|
||||
|
||||
sub DoSearch {
|
||||
my $string = shift;
|
||||
my $string = shift || GetParam('search', '');;
|
||||
return DoIndex() if $string eq '';
|
||||
eval { qr/$string/ }
|
||||
or $@ and ReportError(Ts('Malformed regular expression in %s', $string),
|
||||
@@ -3370,6 +3395,7 @@ sub GrepFiltered { # grep is so much faster!!
|
||||
push(@result, $1) if m/.*\/(.*)\.pg/ and not $found{$1};
|
||||
}
|
||||
close(F);
|
||||
return @pages if $?;
|
||||
return sort @result;
|
||||
}
|
||||
|
||||
@@ -3481,7 +3507,13 @@ sub Replace {
|
||||
next if (@languages and not grep(/$lang/, @languages));
|
||||
}
|
||||
$_ = $Page{text};
|
||||
if (eval "s{$from}{$to}gi") { # allows use of backreferences
|
||||
my $replacement = sub {
|
||||
my ($o1, $o2, $o3, $o4, $o5, $o6, $o7, $o8, $o9) = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
|
||||
my $str = $to;
|
||||
$str =~ s/\$([1-9])/'$o' . $1/gee;
|
||||
$str
|
||||
};
|
||||
if (s/$from/$replacement->()/gei) { # allows use of backreferences
|
||||
push (@result, $id);
|
||||
Save($id, $_, $from . ' → ' . $to, 1, ($Page{host} ne GetRemoteHost()));
|
||||
}
|
||||
@@ -3490,6 +3522,27 @@ sub Replace {
|
||||
return @result;
|
||||
}
|
||||
|
||||
sub SaveUploadedFile {
|
||||
my ($filename, $file) = @_;
|
||||
my ($name, $path, $extension) = fileparse($filename, '\..*');
|
||||
$name =~ tr/ /_/;
|
||||
$name =~ s/[^$FilenameWhitelist]//g;
|
||||
$extension =~ tr/ /_/;
|
||||
$extension =~ s/[^$FilenameWhitelist]//g;
|
||||
my $curFilename = $name . $extension;
|
||||
while (-e "$FileDir/$curFilename") { # keep adding random characters until we get unique filename
|
||||
die 'Error: Cannot save file with such filename' if length $curFilename >= 150; # cannot find available filename after so many attempts
|
||||
$name .= $AdditionalChars[rand @AdditionalChars];
|
||||
$curFilename = $name . $extension;
|
||||
}
|
||||
CreateDir($FileDir);
|
||||
open(UPLOADFILE, '>', "$FileDir/$curFilename") or die "$!";
|
||||
binmode UPLOADFILE;
|
||||
print UPLOADFILE while <$file>;
|
||||
close UPLOADFILE;
|
||||
return $curFilename;
|
||||
}
|
||||
|
||||
sub DoPost {
|
||||
my $id = FreeToNormal(shift);
|
||||
UserCanEditOrDie($id);
|
||||
@@ -3506,9 +3559,9 @@ sub DoPost {
|
||||
}
|
||||
my $comment = UnquoteHtml(GetParam('aftertext', undef));
|
||||
$comment =~ s/(\r|$FS)//go;
|
||||
if (defined($comment) and (not $comment or $comment eq $NewComment)) {
|
||||
if (defined $comment and $comment eq '') {
|
||||
ReleaseLock();
|
||||
ReBrowsePage($id);
|
||||
return ReBrowsePage($id);
|
||||
}
|
||||
if ($filename) { # upload file
|
||||
my $file = $q->upload('file');
|
||||
@@ -3518,11 +3571,8 @@ sub DoPost {
|
||||
ReportError(T('Browser reports no file info.'), '500 INTERNAL SERVER ERROR') unless $q->uploadInfo($filename);
|
||||
$type = $q->uploadInfo($filename)->{'Content-Type'};
|
||||
ReportError(T('Browser reports no file type.'), '415 UNSUPPORTED MEDIA TYPE') unless $type;
|
||||
local $/ = undef; # Read complete files
|
||||
my $content = <$file>; # Apparently we cannot count on <$file> to always work within the eval!?
|
||||
my $encoding = 'gzip' if substr($content, 0, 2) eq "\x1f\x8b";
|
||||
eval { require MIME::Base64; $_ = MIME::Base64::encode($content) };
|
||||
$string = "#FILE $type $encoding\n" . $_;
|
||||
my $savedFile = SaveUploadedFile($filename, $file);
|
||||
$string = "Uploaded file: [[File:$savedFile]]\n";
|
||||
} else { # ordinary text edit
|
||||
$string = AddComment($old, $comment) if $comment;
|
||||
if ($comment and substr($string, 0, length($DeletedPage)) eq $DeletedPage) { # look ma, no regexp!
|
||||
@@ -3604,6 +3654,7 @@ sub DoPost {
|
||||
|
||||
sub GetSummary {
|
||||
my $text = GetParam('aftertext', '') || ($Page{revision} > 0 ? '' : GetParam('text', ''));
|
||||
return '' if $text =~ /^#FILE /;
|
||||
if ($SummaryDefaultLength and length($text) > $SummaryDefaultLength) {
|
||||
$text = substr($text, 0, $SummaryDefaultLength);
|
||||
$text =~ s/\s*\S*$/ . . ./;
|
||||
@@ -3620,7 +3671,7 @@ sub AddComment {
|
||||
my ($string, $comment) = @_;
|
||||
$comment =~ s/\r//g; # Remove "\r"-s (0x0d) from the string
|
||||
$comment =~ s/\s+$//g; # Remove whitespace at the end
|
||||
if ($comment ne '' and $comment ne $NewComment) {
|
||||
if ($comment ne '') {
|
||||
my $author = GetParam('username', T('Anonymous'));
|
||||
my $homepage = GetParam('homepage', '');
|
||||
$homepage = 'http://' . $homepage if $homepage and $homepage !~ /^($UrlProtocols):/;
|
||||
@@ -3873,8 +3924,7 @@ sub DoDebug {
|
||||
|
||||
sub DoSurgeProtection {
|
||||
return unless $SurgeProtection;
|
||||
my $name = GetParam('username', '');
|
||||
$name = GetRemoteHost() if not $name and $SurgeProtection;
|
||||
my $name = GetParam('username', GetRemoteHost());
|
||||
return unless $name;
|
||||
ReadRecentVisitors();
|
||||
AddRecentVisitor($name);
|
||||
|
||||
Reference in New Issue
Block a user