Compare commits

..

78 Commits

Author SHA1 Message Date
Alex Jakimenko
03111c7f58 New file upload (draft) 2015-02-17 23:12:05 +02:00
Alex Schroeder
b1d39e3195 tables-long.pl: fix tracking of dirty blocks 2015-02-05 09:21:04 +01:00
Alex Schroeder
48a00a6ff6 rss.t: testing multiple feeds on a page 2015-02-05 08:30:12 +01:00
Alex Schroeder
a3ee3c60ce Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2015-02-04 22:30:29 +01:00
Matt Adams
d69063599e logout.pl: link back to the last page after logout
Users who currently use the back button to return to the previous page
after logging in or logging out need to select it more than once and are
often return to a cached version of the page they were previously
viewing instead of one that reflects an updated cookie state. The only
other option currently available to the user is to re-navigate to the
page which they wish to view (likely the previous page).

This change improves the usability of the logout functions by providing
a link to return to the previous pages after logging in or logging out.
Previous pages are returned without cache.
2015-02-04 19:09:44 +01:00
Alex Schroeder
0146225c4f Fix whitespace in message to match up with last commit. 2015-02-04 19:06:29 +01:00
Matt Adams
50fca72f82 Password action (login) has link back to last page. 2015-02-04 19:04:34 +01:00
Alex Schroeder
1a4e6aa527 Submit empty comment no longer wipes comment page. 2015-02-03 10:50:49 +01:00
Alex Schroeder
cdf8b561a6 Fix summary for uploaded files.
The summary for uploaded files had nested p elements; this was
removed. When no summary is provided, we now remove the "#FILE..."
stuff. In this case, no summary is better.
2015-02-01 00:26:43 +01:00
Alex Jakimenko
905d8c930e Fixed recently introduced XSS vulnerability 2015-01-31 20:50:26 +02:00
Matt Adams
f64c6d470b logout.pl: Provide login link if not logged in. 2015-01-31 16:13:01 +01:00
Matt Adams
0657d84769 Show latest summary when displaying uploaded files 2015-01-31 12:57:52 +01:00
Matt Adams
0181d8b944 hiddenpages.pl: stop breaking search & recent changes 2015-01-30 16:42:25 +01:00
Alex Schroeder
fae5f1e345 replacements: fixed recently introduced bug
The recently introduced code to prevent Perl injection broke repeated
replacements with backreferences.
2015-01-25 09:06:22 +01:00
Alex Schroeder
81b179acac search: fixed handling of missing grep
When closing the pipe to grep, check the status returned by the child
process in $? and return all pages if there was an error (which means
that grep did not filter any pages).
2015-01-25 08:40:07 +01:00
Alex Jakimenko
bc810ee0ce Fixed vulnerability (ugly, but works) 2015-01-25 08:09:07 +02:00
Alex Jakimenko
3a4236bc45 div-foo.pl: syntax for setting the "title" attribute; /x in regexes
The syntax is <class1 class2?My title stuff> ... </>
2015-01-25 06:03:20 +02:00
Alex Jakimenko
8642ae63a2 module-updater.pl: unnecessary () in sub declaration 2015-01-25 05:59:52 +02:00
Alex Jakimenko
3c5373f76b image.pl: It is already quoted, no need to quote it again. 2015-01-25 05:53:40 +02:00
Alex Schroeder
58e297b092 long-tables.pl: $rownum replaces $first and $odd
Updated tests, too.
2015-01-19 20:03:46 +01:00
Matt Adams
cd4f6dc64c tables-long.pl: add a class for even and odd rows 2015-01-19 19:45:14 +01:00
Alex Schroeder
5a112b64b3 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2015-01-19 10:06:19 +01:00
Alex Schroeder
a7b0c661c8 big-brother.pl: Remove password parameters.
Make sure admins and editors don't accidentally leave their passwords on
the Recent Visitors page.
2015-01-19 10:02:34 +01:00
Alex Schroeder
b2f9a0044b ParseData: Reverting to the code from 2006.
As explained on my blog
<https://alexschroeder.ch/wiki/2015-01-13_Handwritten_Optimization>,
the current implementation is "suddenly" very slow. This is specially
noticeable when loading large pages. Without quite understanding how
this is possible, I'm reverting to the old implementation.
2015-01-13 15:32:37 +01:00
Alex Schroeder
d0cdd451e4 oddmuse-2014.css: Add Symbola for smileys. 2015-01-13 13:11:26 +01:00
Alex Schroeder
86334d6532 Summary: tags.pl delete unnecessary calls to write hash
When running TagFind and TagCloud, writing the tag file is not
necessary. This is the result of a search and replace operation that
assumed every DB_File untie is equivalent to a write operation. This
is not true.
2015-01-09 17:09:56 +01:00
Alex Schroeder
733752727d tags.pl: Fixed error reporting for reindex action. 2015-01-09 17:00:43 +01:00
Alex Schroeder
6635803807 tags.pl: Use real lists instead of strings
The strings used to be concatenated elements using $FS as the
separator. Now they are references to lists.
2015-01-08 01:04:46 +01:00
Alex Schroeder
7f3488baaa tags.pl: Fixed tests, fixed bugs. 2015-01-08 00:37:37 +01:00
Alex Schroeder
dcc318f34e tags.pl: Switch from DB_File to Storable
The drawback will be that the entire tag database including all the
backlinks will be stored in a hash. On my wiki with 5799 pages the
tag.db file is just 333K so it’s not too bad.
2015-01-08 00:03:29 +01:00
Alex Schroeder
17edc1c523 namespaces.pl: add match to @NamespaceParameters
When using $MatchingPages = 1, the following URL would is a
possiblity:
https://campaignwiki.org/wiki/Adventures?search=&match=dung&dosearch=Go%21

In this situation, "Adventures" needs to be the namespace. The
'search' parameter is ignored and the script needs to react to
'match'.
2015-01-07 15:16:09 +01:00
Alex Schroeder
85912f211b add-link.pl: use https 2015-01-05 15:13:04 +01:00
Alex Schroeder
52d7239400 Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/oddmuse 2015-01-05 14:57:18 +01:00
Alex Schroeder
9c691e5b9b add-link.pl: How to add entries to wikis.
These two wikis work as public bookmarking sites. This script allows
easy addition of links.

https://campaignwiki.org/wiki/LinksToWisdom/
https://campaignwiki.org/wiki/Adventures/
2015-01-05 14:57:05 +01:00
Alex Schroeder
0e45ea2e99 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2015-01-04 11:19:43 +01:00
Alex Schroeder
8773242dba Add support for zh-cn and zh-tw 2015-01-04 11:18:05 +01:00
Alex Schroeder
267cd53adb Update CSS files for emacswiki.org 2014-12-18 14:16:27 +01:00
Alex Schroeder
ce82a328b6 edit-paragraphs: fix $around parameter
In bullet lists, the $around parameter would overshoot. The link would
say around=20, for example, when in fact the correct value would be
around=18. It would add the "* " of the next list item, apparently. The
edit link would still look good because the test we're using is
"$EditParagraphs[0]->[1] <= $pos" -- but if we then don't set "$pos =
$EditParagraphs[0]->[1]" it won't help as we'll get a "Could not
identify the paragraph you were editing" error as soon as we try to edit
with around=20 instead of around=18.
2014-12-10 09:05:14 +01:00
Alex Schroeder
b925805800 edit-paragraphs.t: more tests
Unsuccessfully trying to find a bug by adding more tests.
But then again, more tests is good, so they'll stay.
2014-12-10 08:19:59 +01:00
Alex Schroeder
c8c50b4e81 Merge commit '8ec456e'
Conflicts:
	modules/upgrade.pl
2014-12-09 12:22:22 +01:00
Alex Schroeder
4a976278d5 alex-2014.css: better printing 2014-12-09 12:16:43 +01:00
Alex Schroeder
1255fe8168 Updated tests for ad/spans-and-divs branch. 2014-12-09 09:16:12 +01:00
Alex Schroeder
081e8243d7 Merge branch 'ad/spans-and-divs' 2014-12-09 03:02:34 +01:00
Alex Schroeder
a20fc60617 Merge branch 'as/search-form-rewrites'
Conflicts:
	css/alex-2014.css
2014-12-09 02:59:32 +01:00
Alex Schroeder
770de2986a edit-paragraphs.pl: handle page names containing & 2014-12-09 02:56:23 +01:00
Alex Schroeder
0551018de1 light.css: iPhone changes 2014-12-04 16:19:15 +01:00
Alex Schroeder
aa77f2ce2f Simplify DoSurgeProtection 2014-12-03 08:41:37 +01:00
Alex Schroeder
471994f7b1 Merge branch 'as/search-form-rewrites' of git.sv.gnu.org:/srv/git/oddmuse into as/search-form-rewrites
Conflicts:
	modules/edit-paragraphs.pl
2014-12-03 08:37:39 +01:00
Alex Jakimenko
9d2c0216f6 top gotobar and search form is now wrapped into 'menu' div (might break existing stylesheets) 2014-12-02 21:34:04 +02:00
Alex Jakimenko
82d888f0ea divs instead of <br> tags (might break existing stylesheets) 2014-12-02 21:06:53 +02:00
Alex Schroeder
ccaf283204 edit-paragraphs.pl: handle </p>\s*</form> 2014-11-30 01:22:45 +01:00
Alex Schroeder
3fb5319562 Merge branch 'as/search-form-rewrites' of ssh://git.sv.gnu.org/srv/git/oddmuse into as/search-form-rewrites 2014-11-26 12:24:18 +01:00
Alex Schroeder
76c92f027c edit-paragraphs.pl: handle empty rows 2014-11-26 12:08:38 +01:00
Alex Schroeder
d828454511 Merge branch 'as/search-form-rewrites' of git.sv.gnu.org:/srv/git/oddmuse into as/search-form-rewrites 2014-11-25 23:41:08 +01:00
Alex Schroeder
34c6e93780 Fake action for search and match.
A test for the action in GetRobots makes sure that the robots meta
element gets a NOINDEX if the action is not "browse". Since the
default is "browse", this means we need to set an action for
search=foo and match=foo.
2014-11-25 19:34:38 +01:00
Alex Schroeder
0a6f473098 alex-2014.css: don't hide all the input fields 2014-11-25 17:01:31 +01:00
Alex Schroeder
4d67f9bfd2 oddmuse-2014.css: new CSS file 2014-11-25 09:09:31 +01:00
Alex Schroeder
6e80adc293 alex-2014.css: iPhone adaptation
Depending on the number of visible fields, the form requires a submit button.
Instead of using display:none, use visibility:hidden;position:absolute in order
to "fix" this for the iPhone.

See:
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form
2014-11-25 08:21:49 +01:00
Alex Schroeder
dc3fb65317 light.css: adapting to search form 2014-11-24 22:55:44 +01:00
Alex Schroeder
6a652de193 Put matching pages into the same form as search.
This works if we later handle a stand-alone match parameter like we handle the search parameter.
We just fake an action.
2014-11-24 22:32:11 +01:00
Alex Schroeder
4747235fe7 light.css: Adaptations to the new layout of search. 2014-11-24 18:47:11 +01:00
Alex Schroeder
0ecbeeb2c4 alex-2014.css: Symbola for textareas 2014-11-24 10:16:38 +01:00
Alex Jakimenko
65378d91cb div-foo.pl: more restrictive regexes, added $DivFooPrefix 2014-11-23 23:04:07 +02:00
Alex Schroeder
cb6a6bf4a6 mail.pl: adapt to changes in the footer 2014-11-23 21:43:35 +01:00
Alex Schroeder
db67c34203 Font paths fixed. Search form changes. 2014-11-23 21:23:10 +01:00
Alex Schroeder
ccf8fe2314 Search form: fix HTML issues 2014-11-23 21:22:29 +01:00
Alex Schroeder
e336086cf0 Reorganizing the Search Form
Introducing  (0, 1, 2); supporting  (2);
adding .
2014-11-23 19:37:27 +01:00
Alex Schroeder
5315b3f6ad toc-js.pl: Fix typo 2014-11-19 13:05:11 +01:00
Alex Schroeder
e31abd57bc toc-js.pl: Provide my own endsWith implementation. 2014-11-19 13:03:54 +01:00
Alex Schroeder
5bf60bb5d8 oddmuse-2014.css: new 2014-11-17 14:23:10 +01:00
Alex Schroeder
ad672aff28 alex-2014.css: Font locations, iPhone optimisation. 2014-11-17 10:51:07 +01:00
Alex Schroeder
e49af47d30 alex-2014.css: keep search forms together 2014-11-15 08:55:10 +01:00
Alex Schroeder
ecbe6a859a Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-11-14 22:55:05 +01:00
Alex Schroeder
413228c56c Emacs interface: fixed vc-diff, history is back
The history command was interesting because it limits the display to
those revisions that are actually available for diff and rollback.
Eventually this might form the basis of a better interface.

The vc-diff code also needed to be fixed.
2014-11-14 22:51:46 +01:00
Alex Schroeder
a905de7ab5 alex-2014.css: get rid of line-height everywhere 2014-11-14 12:26:13 +01:00
Alex Schroeder
a09409f375 alex-2014.css: use Noticia Text as font 2014-11-13 13:08:30 +01:00
Alex Schroeder
8ec456ed41 Merge branch 'as/no-more-page-subdirectories' of ssh://git.sv.gnu.org/srv/git/oddmuse into as/no-more-page-subdirectories 2014-06-21 05:51:35 -04:00
Alex Schroeder
020df9098d upgrade.pl: Fix globbing.
The globbing expression was broken such that pages starting with a dot
("invisible" files) were not migrated.
2014-06-21 05:49:31 -04:00
54 changed files with 1716 additions and 446 deletions

315
contrib/add-link.pl Normal file
View File

@@ -0,0 +1,315 @@
#! /usr/bin/perl
# Copyright (C) 20112015 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__

View File

@@ -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.

View 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)

View File

@@ -1,82 +1,184 @@
/* font-face includes TTF for PDF generation */
/* vietnamese */
@font-face {
font-family: "EB Garamond";
font-family: 'Noticia Text';
font-style: normal;
src: local('EB Garamond 12'), url('EBGaramond12-Regular.ttf') format("truetype");
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: "EB Garamond";
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;
src: local('EB Garamond 12'), url('EBGaramond12-Italic.ttf') format("truetype");
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: 'EB Garamond All SC';
src: local('EB Garamond 12 All SC'), url('EBGaramond12-AllSC.ttf') format("truetype");
}
@font-face {
font-family:"Symbola";
src: local('Symbola'), url("Symbola.woff") format("woff"), url("Symbola.ttf") format("truetype");
font-weight:normal;
font-style:normal;
font-family: 'Symbola';
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff') url('/fonts/Symbola.ttf') format('truetype');
}
body, rss {
font-family: "EB Garamond", Symbola, serif;
font-family: "Noticia Text", Symbola, serif;
font-style: normal;
font-size: 18pt;
line-height: 22pt;
margin:1em 3em;
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: "EB Garamond", Symbola, serif;
font-family: inherit;
font-weight: normal;
line-height: 100%;
}
h1, channel title {
font-size: 32pt;
margin: 1em 0 0.5em 0;
margin: 1em 0 0.5em 0;
padding: 0.4em 0;
}
h2 {
font-size: 18pt;
margin: 2em 0 0 0;
margin: 2em 0 0 0;
padding: 0;
}
h3 {
font-size: inherit;
font-size: inherit;
font-weight: bold;
padding: 0;
margin: 1em 0 0 0;
clear: both;
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;
font-size: inherit;
padding: 0;
clear: both;
border-bottom: 1px solid #000;
}
div.journal h2 {
font-family: inherit;
font-size: inherit;
font-size: inherit;
}
div.journal h3 {
font-family: inherit;
font-size: inherit;
font-size: inherit;
font-weight: inherit;
font-style: italic;
font-style: italic;
}
div.journal hr {
display:none;
visibility: hidden;
}
p.more {
margin-top: 3em;
@@ -159,28 +261,35 @@ a img {
/* search box in the top bar */
form.tiny, form.tiny p {
.header form, .header p {
display: inline;
white-space: nowrap;
}
form.tiny input {
padding: 0;
width: 10ex;
font-size: 80%;
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) {
}
/* bold = small caps */
b, strong {
font-family: "EB Garamond All SC";
font-weight: normal;
.header input {
width: 10ex;
}
/* other form fields */
input[type="text"] {
padding: 0;
font-size: 80%;
line-height: 125%;
}
/* code */
pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace;
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 80%;
line-height: 110%;
}
pre {
@@ -198,26 +307,28 @@ pre {
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
line-height: 16pt;
}
@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 {
line-height: 110%;
}
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-left: 5%;
padding-right: 5%;
font-size: 12pt;
line-height: 14pt;
color: #000;
font-size: 12pt;
color: #000;
}
div.old {
@@ -228,10 +339,9 @@ div.new {
}
div.refer {
padding-left: 5%;
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
line-height: 13pt;
font-size: 12pt;
}
div.message {
@@ -298,8 +408,8 @@ textarea[name="summary"] { width:97%; height:3em; }
/* comments */
textarea[name="aftertext"] { width:97%; height:10em; }
div.commentshown {
background-color:#ffc;
padding-bottom:1ex;
font-size: 12pt;
padding: 2em 0;
}
div.commenthidden {
display:none;
@@ -346,22 +456,22 @@ div.content div.month a.edit {
/* history tables and other tables */
table.history {
border: none;
border: none;
}
td.history {
border: none;
border: none;
}
table.user {
border: none;
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;
border: none;
padding: 0.2em 0.5em;
vertical-align: top;
}
table.arab tr th {
@@ -395,10 +505,10 @@ 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;
float: right;
margin: 0 0 0 1ex;
padding: 0;
border: 1px solid #000;
border: 1px solid #000;
opacity: 0.3;
background-color:#ffe;
}
@@ -462,7 +572,6 @@ dl.irc dd { margin-left:22ex; }
div.footer, div.comment, hr { clear: both; }
.portrait { float: left; font-size: small; margin-right: 1em; }
.portrait { line-height: 1em; }
.portrait a { color: #999; }
div.left { float:left; margin:1em; padding: 0.5em; }
@@ -479,76 +588,8 @@ p.table + p { clear:both; }
}
} */
@media print {
body {
font-size: 12pt;
line-height: 13pt;
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 {
display:none;
}
a,
a:visited,
div.content a.near:link,
div.content a.near:visited,
div.content a.near:active {
color:inherit;
font-weight: bold;
}
div.content a.feed {
display: none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
/* no difference */
pre, code, tt {
font-size: inherit;
line-height: inherit;
}
/* no dotted underlines */
acronym, abbr {
border: none;
text-decoration: none;
}
/* headings */
h1 {
color: inherit;
margin-top: 2em;
}
h2 {
color:inherit;
margin: 1em 0;
font-variant: small-caps;
font-family: "EB Garamond All SC", "EB Garamond", Garamond, serif;
}
h3 {
font-weight:inherit;
font-style:italic;
color:inherit;
margin: 1em 0;
}
h1 a, h2 a, h3 a {
color: inherit;
}
div.journal h1 a:visited,
div.journal h2 a:visited,
div.journal h3 a:visited {
color: inherit;
}
}
/* rss */
channel * { display: block; }
channel title {
@@ -556,7 +597,6 @@ channel title {
}
copyright {
font-size: 14pt;
line-height: 16pt;
margin-top: 1em;
}
channel > link:before {

347
css/bootstrap.css vendored Normal file
View 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: */

View File

@@ -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;

View File

@@ -4,18 +4,125 @@
@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. */
calling wkhtmltopdf.
@import url(https://fonts.googleapis.com/css?family=Noticia+Text:400,400italic,700italic,700&subset=latin,latin-ext);
@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", Times, serif;
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;
@@ -39,13 +146,14 @@ body {
}
/* general */
div.browse {
min-height: 3em;
}
div.footer {
clear:both;
font-size: 90%;
}
.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 {
@@ -149,6 +257,10 @@ a.pencil {
display: none;
}
}
table a.pencil {
position: absolute;
right: inherit;
}
/* table of contents */
.toc {

250
css/oddmuse-2014.css Normal file
View 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%;
};

View File

@@ -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)

View File

@@ -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\&lt;([\w ]+)\&gt;\s*\n/cg) {
return CloseHtmlEnvironment('p') . AddHtmlEnvironment('div', qq{class="$1"});
if (m/\G \&lt; ([a-z-_][a-z-_ ]+[a-z-_]) \&gt; \s*\n /cgx) {
return CloseHtmlEnvironment('p') . AddHtmlEnvironment('div', 'class="' . join(' ', map {"$DivFooPrefix$_"} split /\s+/, $1) . '"');
}
if (m/\G\&lt;([\w ]+)\&gt;/cg) {
return AddHtmlEnvironment('span', qq{class="$1"});
if (m/\G \&lt; ([a-z-_][a-z-_ ]+[a-z-_]) (\?(.*?(?=\&gt;)))? \&gt; /cgx) {
my $title = $3 ? ' title="' . QuoteHtml($3) . '"' : '';
return AddHtmlEnvironment('span', 'class="' . join(' ', map {"$DivFooPrefix$_"} split /\s+/, $1) . '"' . $title);
}
if (m/\G\&lt;\/\/\&gt;/cg) {
if (m/\G \&lt; \/ \/ \&gt; /cgx) {
return CloseHtmlEnvironment('div') . (InElement('div') ? '' : AddHtmlEnvironment('p'));
}
if (m/\G\&lt;\/\&gt;/cg) {
if (m/\G \&lt; \/ \&gt; /cgx) {
return CloseHtmlEnvironment('span');
}
return undef;

View File

@@ -27,7 +27,7 @@ our $EditParagraphPencil = '&#x270E;';
$Action{'edit-paragraph'} = \&DoEditParagraph;
sub DoEditParagraph {
my $id = GetParam('title', '');
my $id = UnquoteHtml(GetParam('title', ''));
UserCanEditOrDie($id);
my $old = UnquoteHtml(GetParam('paragraph', ''));
@@ -69,6 +69,10 @@ sub DoEditParagraph {
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,
@@ -156,6 +160,7 @@ sub EditParagraph {
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);
}
@@ -181,17 +186,27 @@ sub EditParagraph {
# <table><tr><td>...<a ...>&#x270E;</a></td></tr></table></p>
$pos = $pos || length(QuoteHtml($Page{text})); # make sure we have an around value
my $link = ScriptLink("action=edit-paragraph;title=$OpenPageName;around=$pos;paragraph="
. UrlEncode(UnquoteHtml($text)), $EditParagraphPencil, 'pencil');
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 .= '<!-- 1 -->';
# $Fragment .= '<!-- moved inside -->';
$Fragment .= $link . $1;
} elsif ($pos and $Fragment =~ /<p>$/) {
# Do nothing: this will result in <p></p> and get eliminated.
# $Fragment .= '<!-- 2 -->';
} 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 .= '<!-- 3 -->';
# $Fragment .= '<!-- default -->';
$Fragment .= $link;
}
}

View File

@@ -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()

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -68,7 +68,7 @@ sub ModuleUpdaterApply {
print $q->br(), $q->strong('Done!');
}
sub ProcessModule() {
sub ProcessModule {
my $module = shift;
CreateDir($TempDir);
print $q->hr();

View File

@@ -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

View File

@@ -1,20 +1,17 @@
# Copyright (C) 2004, 2005 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 20042015 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>');
}

View File

@@ -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) 20052015 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

View File

@@ -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);

View File

@@ -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:

View File

@@ -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:
Парола:

View File

@@ -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:
密碼

View File

@@ -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:
密码

View File

@@ -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:

View File

@@ -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:

View File

@@ -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 :

View File

@@ -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:

View File

@@ -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:
Κωδικός:

View File

@@ -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:
ססמה:

View File

@@ -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:

View File

@@ -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:
パスワード:

View File

@@ -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:
암호:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:
Пароль:

View File

@@ -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:
Лозинка:

View File

@@ -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:

View File

@@ -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 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:

View File

@@ -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:
Пароль:

View File

@@ -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";

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2009 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20092015 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}};

View File

@@ -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"]');

View File

@@ -14,7 +14,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 48;
use Test::More tests => 56;
use utf8;
clear_pages();
@@ -241,14 +241,59 @@ 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'
. ' text=' . UrlEncode("<test30>\n") # new
. ' 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'
. ' text=' . UrlEncode("<test30>\n") # new
. ' 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));

View File

@@ -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
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2006, 2008 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062015 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
}

View File

@@ -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");

View File

@@ -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');

View File

@@ -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]]');

View File

@@ -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');

234
wiki.pl
View File

@@ -33,38 +33,32 @@ 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.
@@ -136,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');
@@ -176,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,
@@ -254,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
@@ -427,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)/;
@@ -1324,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 {
@@ -1866,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()
@@ -2223,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);
@@ -2236,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'});
@@ -2376,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));
@@ -2397,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 '';
}
@@ -2437,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 {
@@ -2473,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 {
@@ -2649,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;
}
@@ -3093,6 +3093,7 @@ sub DoDownload {
}
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()) {
@@ -3102,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();
}
@@ -3295,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),
@@ -3390,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;
}
@@ -3501,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()));
}
@@ -3510,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);
@@ -3528,7 +3561,7 @@ sub DoPost {
$comment =~ s/(\r|$FS)//go;
if (defined $comment and $comment eq '') {
ReleaseLock();
ReBrowsePage($id);
return ReBrowsePage($id);
}
if ($filename) { # upload file
my $file = $q->upload('file');
@@ -3538,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!
@@ -3624,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*$/ . . ./;
@@ -3893,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);