Compare commits

..

123 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
764c0ffcf1 alex-2014.css: no bold, use local fonts 2014-11-13 11:37:47 +01:00
Alex Schroeder
5962745937 Fixed comment handling.
A simple $comment eq '' resulted in previews if page edits to no
longer work. The correct test is defined $comment and $comment eq ''.
2014-11-11 11:17:29 +01:00
Alex Schroeder
d380062ec6 Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/oddmuse 2014-11-11 11:11:46 +01:00
Alex Schroeder
cd8066233c Unlock Wiki menu lists locks.
If no locks are set, the menu is not shown.
If the menu is shown, the known locks are listed.
2014-11-11 11:04:58 +01:00
Alex Schroeder
6f04d2044f edit-paragraphs: get rid of fuzzy matching
Too many bugs!
2014-11-10 23:17:00 +01:00
Alex Schroeder
f41ded592b Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-11-10 22:11:55 +01:00
Alex Schroeder
8a2c9eca9c edit-paragraphs: fix HTML quoting issues 2014-11-10 22:10:51 +01:00
Alex Schroeder
d712a17f82 toc-js.pl: remove multiple pencils
The existence of multiple pencils appears to be a bug in edit-paragraphs.
2014-11-10 15:39:51 +01:00
Alex Schroeder
504190b752 Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/oddmuse 2014-11-10 15:37:50 +01:00
Alex Schroeder
56e515a791 toc-js.pl: remove pencil icon
This icon is added by edit-paragraphs.pl and it looks ugly.
2014-11-10 15:37:37 +01:00
Alex Schroeder
36feb62052 oddmuse-curl: provide oddmuse-curl
It used to provide oddmuse, and vc-oddmuse used to require oddmuse, but
that doesn't load oddmuse-curl.el.
2014-11-10 13:12:51 +01:00
Alex Schroeder
239f15cdbc Simplified handling of $NewComment
A long time ago, $NewComment was the default text for the comment form,
ie. aftertext. That's why the code still had some comparisons of
aftertext with $NewComment. Now that $NewComment is a label in the
comment form and no longer the content of the text area, these tests can
be removed.
2014-11-10 09:48:12 +01:00
Alex Schroeder
f39cfd3235 edit-paragraphs.pl: handle trailing newlines
This used to generate an extra entry for @EditParagraphs which in turn
prevented the page from ending with an edit link if it ended with
multiple newlines.

Also made $EditParagraphPencil settable in the config file.
2014-11-10 09:45:59 +01:00
Alex Schroeder
329699a6aa Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-11-09 17:56:45 +01:00
Alex Schroeder
28965bdaa6 edit-paragraphs.t: added test cases by Alex Daniel 2014-11-09 17:55:43 +01:00
Alex Schroeder
ed42d2dad5 oddmuse_stats: fixed handling of non-ASCII names 2014-11-09 17:05:02 +01:00
Alex Schroeder
45b21cbdb8 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-11-09 13:11:32 +01:00
Alex Schroeder
b540093c2c edit-paragraphs: handle pre, hr. 2014-11-09 13:09:44 +01:00
Alex Schroeder
d164d47e24 oddmuse-stats: a munin plugin 2014-11-09 12:29:28 +01:00
Alex Schroeder
a56b92ecb3 edit-paragraphs: fix HTML escaping 2014-11-08 21:15:15 +01:00
Alex Schroeder
114d914754 edit-paragraphs: pass around parameter to the form 2014-11-08 18:33:52 +01:00
Alex Schroeder
875051ea84 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse
Conflicts:
	t/edit-paragraphs.t
2014-11-08 18:02:01 +01:00
Alex Schroeder
003357acad edit-paragraphs: handle multiple paragraphs 2014-11-08 18:00:16 +01:00
Alex Schroeder
e1c77c4ba6 edit-paragraphs: handle multiple paragraphs 2014-11-08 17:59:44 +01:00
Alex Schroeder
88475c3e41 edit-paragraphs: fiddled with unit tests
Trying to find a constellation to reproduce missing edit links.
Adding support for editing table rows.
2014-11-08 13:37:26 +01:00
Alex Schroeder
e80f05301d edit-paragraphs: added unit test, fixed table bug
After a table, we'd get a lone link in a separate paragraph, just like
headers.
2014-11-07 23:04:12 +01:00
Alex Schroeder
a624e78975 edit-paragraphs: added unit tests, fixed table bug
Before a table, we'd get a lone link.
2014-11-07 22:47:02 +01:00
Alex Schroeder
8cd869f0f9 alex-2012.css: simplication of a.pencil stuff 2014-11-07 09:56:44 +01:00
Alex Schroeder
b09b3f8f8e light.css: new CSS file used by campaignwiki.org 2014-11-07 09:56:21 +01:00
Alex Schroeder
b9043ffd98 alex-2012.css: new stuff.
* a.pencil things for edit-paragraph.pl
* pre with word wrap
* limiting images to 100% width for the phone
2014-11-07 09:27:11 +01:00
Alex Schroeder
54d3dc400a edit-paragraph.js: new
This hides and shows the pencils at the end of each paragraph.
2014-11-06 21:48:08 +01:00
Alex Schroeder
3962068385 edit-paragraphs.pl: improve caching
Hook into PrintWikiToHTML instead of PrintPageContent in order to
avoid adding an edit link after every page link (as these are the
dirty blocks that get printed from the cache).
2014-11-06 16:01:01 +01:00
Alex Schroeder
a0b74ac3c6 edit-paragraph.pl: fixing stuff that didn't work 2014-11-06 15:38:21 +01:00
Alex Schroeder
870d75ac64 edit-paragraphs.pl: hack $Fragment
In order to get the edit button into the headings, we hack it into
$Fragment -- the current, clean HTML block being assembled by
ApplyRules.
2014-11-06 15:00:46 +01:00
Alex Schroeder
42d8260ce4 edit-paragraphs.pl: remove \r
Multiline paragraphs were not replaced correctly because of extra \r
in line endings.
2014-11-06 14:40:23 +01:00
Alex Schroeder
bfda4abe54 New module. 2014-11-06 13:43:20 +01:00
Alex Schroeder
4b0d411564 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse
Conflicts:
	wiki.pl
2014-11-01 01:03:16 +01:00
Alex Schroeder
6790de2d6a Yet another attempt at fixing encoding issues.
To facilitate future debugging, STDERR now also gets the UTF-8 layer.

Apparently CGI does not decode UTF-8 encoded URL parameters. Handle this
case in GetParam.

PageHtml can be called when STDOUT already has the UTF-8 layer. It needs
to be able to handle both cases. That's why we call binmode without any
layers and then we call binmode with the UTF-8 layer again. Now it will
work for RSS files as well.

Unrelated fix: In order to force a decent Etag header even if no index
file exists (and thus $LastUpdate is undef), we use $Now as an
alternative.
2014-11-01 00:52:55 +01:00
Alex Schroeder
2784628544 PageHtml no longer uses utf8 layer
binmode adding utf8 layer to STDOUT resulted in double encoded pages
included via PageHtml. On my homepage I was appending the comments to
every page using the following code:

    my $target = $CommentsPrefix . $id;
    my $page = '';
    $page = PageHtml($target) if $IndexHash{$target};
    print $q->div({-class=>'comment'},
                  $q->h2(T('Comments')),
                  $page);
2014-10-31 23:50:28 +01:00
Alex Schroeder
dd8c687b2b Caching: Fixed tests.
There is no problem generating an Etag header even if a Last Modified
header is provided.
2014-10-31 23:49:41 +01:00
Alex Jakimenko
9f4ceb2d72 module-updater.pl: No need to return when calling OrError subs 2014-10-31 18:48:48 +02:00
Alex Jakimenko
0f8a4fa1df module-updater.pl: Fixed cache problems 2014-10-31 18:48:06 +02:00
Alex Jakimenko
3b16b58880 module-bisect.pl: Solved cache problems, added 'Back' button from 'Stop' page, all strings are now translatable, some refactoring. 2014-10-31 18:41:08 +02:00
Alex Jakimenko
192a902932 Typos and language 2014-10-31 15:41:00 +02:00
Alex Schroeder
aedf77cff8 wiki.pl: Fix caching.
Previously, if calling GetHeader with 'nocache', this would get passed
on to GetHttpHeader as $ts. The code would then produce an etag header
with a value of 'nocache'. This is now fixed. A long comment now
explains how it is supposed to work to reduce confusion in the future.
2014-10-31 09:27:27 +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
59 changed files with 3208 additions and 351 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.
@@ -1031,6 +1049,6 @@ PAGENAME is the pagename of the page you want to browse."
(interactive)
(kill-new (oddmuse-url oddmuse-wiki oddmuse-page-name)))
(provide 'oddmuse)
(provide 'oddmuse-curl)
;;; oddmuse-curl.el ends here

108
contrib/oddmuse_stats Executable file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/perl -w
# -*- perl -*-
=head1 NAME
oddmuse-stats - Plugin to monitor Oddmuse edits
=head1 CONFIGURATION
Set env.parent_dirs in the config file. The directories in this list
are searched for data directories containing rc.log files. No
whitespace in the directory names, sorry.
Example:
[oddmuse_stats]
user www-data
env.parent_dirs /home/alex /home/alex/campaignwiki
=head1 AUTHORS
Original Author: Alex Schroeder
=head1 LICENSE
GPLv3
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf
=cut
use Munin::Plugin;
use File::Basename;
# The wiki directories may not contain any spaces.
# Use the config file to set the environment variable!
my @parent_dirs = ();
my %logfiles = ();
my %names = ();
my $debug = $ENV{MUNIN_DEBUG};
if ($ENV{'parent_dirs'}) {
@parent_dirs = split(/ /, $ENV{'parent_dirs'});
} else {
die "The parent_dirs environment variable must be set.\n";
}
for my $parent_dir (@parent_dirs) {
warn "opening $parent_dir\n" if $debug;
if (opendir(my $dh, $parent_dir)) {
while(readdir $dh) {
next if $_ eq '.' or $_ eq '..';
if (-r "$parent_dir/$_/rc.log") {
my $basename = basename($_);
$names{clean_fieldname($basename)}
= $basename;
$logfiles{clean_fieldname($basename)}
= "$parent_dir/$_/rc.log";
} else {
warn "discarding $_\n" if $debug;
}
}
closedir $dh;
}
}
my $yesterday = time() - 86400;
if ($ARGV[0]) {
if ($ARGV[0] eq 'autoconf') {
if (keys %logfiles) {
print "yes\n";
exit 0;
} else {
print "no (no logfiles found in " . join(", ", @parent_dirs) . ")\n";
exit 0;
}
} elsif ($ARGV[0] eq 'config') {
print "graph_title Oddmuse Wikis\n";
print "graph_category wikis\n";
print "graph_info This graph shows how many edits the wiki had in the last 24h.\n";
print "graph_vlabel edits/day\n";
print "graph_order";
for my $wiki (sort keys %logfiles) {
print " $wiki";
};
print "\n";
for my $wiki (sort keys %logfiles) {
my $name = $names{$wiki};
print "$wiki.label $name\n";
}
exit 0;
}
}
for my $wiki (sort keys %logfiles) {
open (my $fh, '<', $logfiles{$wiki})
or die "cannot open " . $logfiles{$wiki} . ": $!";
my $value = 0;
while (<$fh>) {
my ($ts) = split(/\x1e/);
$value++ if $ts and $ts >= $yesterday;
}
print "$wiki.value $value\n";
}

View File

@@ -28,7 +28,7 @@
(add-to-list 'vc-handled-backends 'oddmuse)
(require 'oddmuse)
(require 'oddmuse-curl)
(require 'diff)
(defun vc-oddmuse-revision-granularity () 'file)
@@ -110,11 +110,11 @@ It must print the page to stdout.
(defun oddmuse-revision-filename (rev)
"Return filename for revision REV.
This uses `oddmuse-directory', `oddmuse-wiki' and
`oddmuse-page-name'."
This uses `oddmuse-directory', `wiki' and `pagename' as bound by
`with-oddmuse-file'."
(concat oddmuse-directory
"/" oddmuse-wiki
"/" oddmuse-page-name
"/" wiki
"/" pagename
".~" rev "~"))
(defun vc-oddmuse-diff (files &optional rev1 rev2 buffer)
@@ -126,13 +126,12 @@ This uses `oddmuse-directory', `oddmuse-wiki' and
(dolist (rev (list rev1 rev2))
(when (and rev (not (file-readable-p (oddmuse-revision-filename rev))))
(let* ((oddmuse-revision rev)
(command (oddmuse-format-command
vc-oddmuse-get-revision-command))
(command vc-oddmuse-get-revision-command)
(filename (oddmuse-revision-filename rev)))
(with-temp-buffer
(oddmuse-run
(concat "Downloading revision " rev)
command wiki)
command wiki pagename)
(write-file filename)))))
(diff-no-select
(if rev1 (oddmuse-revision-filename rev1) file)

View File

@@ -100,6 +100,25 @@ h1 a:visited, h2 a:visited, h3 a:visited {
/* links */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visible: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visible: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
a.number {
text-decoration: none;
}
@@ -140,6 +159,15 @@ pre, code, tt {
line-height: 110%;
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
@@ -366,6 +394,10 @@ div.image span.caption {
margin: 0 1em;
}
img {
max-width: 100%;
}
.left { float:left; margin-right: 1em; }
.right { float:right; margin-left: 1em; }
.half a img { height: 50%; width: 50%; }

632
css/alex-2014.css Normal file
View File

@@ -0,0 +1,632 @@
/* font-face includes TTF for PDF generation */
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 400;
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff') url('/fonts/NoticiaText-Regular.ttf') format('truetype');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 400;
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff') url('/fonts/NoticiaText-Regular.ttf') format('truetype');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 400;
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff') url('/fonts/NoticiaText-Regular.ttf') format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 700;
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff') url('/fonts/NoticiaText-Bold.ttf') format('truetype');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 700;
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff') url('/fonts/NoticiaText-Bold.ttf') format('truetype');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 700;
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff') url('/fonts/NoticiaText-Bold.ttf') format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 400;
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff') url('/fonts/NoticiaText-Italic.ttf') format('truetype');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 400;
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff') url('/fonts/NoticiaText-Italic.ttf') format('truetype');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 400;
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff') url('/fonts/NoticiaText-Italic.ttf') format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 700;
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff') url('/fonts/NoticiaText-BoldItalic.ttf') format('truetype');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 700;
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff') url('/fonts/NoticiaText-BoldItalic.ttf') format('truetype');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 700;
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff') url('/fonts/NoticiaText-BoldItalic.ttf') format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Symbola';
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff') url('/fonts/Symbola.ttf') format('truetype');
}
body, rss {
font-family: "Noticia Text", Symbola, serif;
font-style: normal;
font-size: 14pt;
margin: 1em 3em;
padding:0;
}
@media print {
body {
font-size: 12pt;
color: #000;
background-color: #fff;
}
/* hide all the crap */
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone, div.content a.feed {
display:none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
img[alt="RSS"] { display: none }
a.rss { font-size: 8pt }
}
/* headings: we can use larger sizes if we use a lighter color.
we cannot inherit the font-family because header and footer use a narrow font. */
h1, h2, h3, title {
font-family: inherit;
font-weight: normal;
}
h1, channel title {
font-size: 32pt;
margin: 1em 0 0.5em 0;
padding: 0.4em 0;
}
h2 {
font-size: 18pt;
margin: 2em 0 0 0;
padding: 0;
}
h3 {
font-size: inherit;
font-weight: bold;
padding: 0;
margin: 1em 0 0 0;
clear: both;
}
/* headers in the journal are smaller */
div.journal h1, item title {
font-size: inherit;
padding: 0;
clear: both;
border-bottom: 1px solid #000;
}
div.journal h2 {
font-family: inherit;
font-size: inherit;
}
div.journal h3 {
font-family: inherit;
font-size: inherit;
font-weight: inherit;
font-style: italic;
}
div.journal hr {
visibility: hidden;
}
p.more {
margin-top: 3em;
}
/* Links in headings appear on journal pages. */
h1 a, h2 a, h3 a {
color:inherit;
text-decoration:none;
font-weight: normal;
}
h1 a:visited, h2 a:visited, h3 a:visited {
color: inherit;
}
/* for download buttons and the like */
.button {
display: inline-block;
font-size: 120%;
cursor: pointer;
padding: 0.4em 0.6em;
text-shadow: 0px -1px 0px #ccc;
background-color: #cfa;
border: 1px solid #9d8;
border-radius: 5px;
box-shadow: 0px 1px 3px white inset, 0px 1px 3px black;
}
.button .icon {
color: #363;
text-shadow: 0px -1px 1px white, 0px 1px 3px #666;
}
.button a {
text-decoration: none;
font-weight: normal;
}
/* links */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visibility: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visibility: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
a.number {
text-decoration: none;
}
/* stop floating content from flowing over the footer */
hr {
clear: both;
}
/* the distance between links in the navigation bars */
span.bar a {
margin-right: 1ex;
}
a img {
border: none;
}
/* search box in the top bar */
.header form, .header p {
display: inline;
white-space: nowrap;
}
label[for="searchlang"], #searchlang, .header input[type="submit"] {
/* don't use display: none! http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
visibility: hidden; position: absolute;
}
/* wrap on the iphone */
@media media only screen and (max-device-width: 480px) {
}
.header input {
width: 10ex;
}
/* other form fields */
input[type="text"] {
padding: 0;
font-size: 80%;
line-height: 125%;
}
/* code */
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 80%;
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
}
@media print {
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 8pt;
}
}
div.footer form.search {
display: none;
}
div.rc li + li {
margin-top: 1em;
}
div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;
}
div.new {
background-color: #cfffcf;
}
div.refer {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
}
div.message {
background-color:#fee;
color:#000;
}
img.xml {
border:none;
padding:1px;
}
a.small img {
max-width:300px;
}
a.large img {
max-width:600px;
}
div.sister {
margin-right:1ex;
background-color:inherit;
}
div.sister p {
margin-top:0;
}
div.sister hr {
display:none;
}
div.sister img {
border:none;
}
div.near, div.definition {
background-color:#efe;
}
div.sidebar {
float:right;
border:1px dotted #000;
padding:0 1em;
}
div.sidebar ul {
padding-left:1em;
}
/* replacements, features */
ins {
color: #b33;
text-decoration: none;
}
acronym, abbr {
letter-spacing:0.1em;
font-variant:small-caps;
}
/* Interlink prefix not shown */
a .site, a .separator {
display: none;
}
a cite { font:inherit; }
/* browser borkage */
textarea[name="text"] { width:97%; height:80%; }
textarea[name="summary"] { width:97%; height:3em; }
/* comments */
textarea[name="aftertext"] { width:97%; height:10em; }
div.commentshown {
font-size: 12pt;
padding: 2em 0;
}
div.commenthidden {
display:none;
}
div.commentshown {
display:block;
}
p.comment {
margin-bottom: 0;
}
div.comment {
font-size: 14pt;
}
div.comment h2 {
margin-top: 5em;
}
/* comment pages with username, homepage, and email subscription */
.comment form span { display: block; }
.comment form span label { display: inline-block; width: 10em; }
/* IE sucks */
.comment input#username,
.comment input#homepage,
.comment input#mail { width: 20em; }
/* cal */
div.month { padding:0; margin:0 2ex; }
body > div.month {
float:right;
background-color: inherit;
border:solid thin;
padding:0 1ex;
}
div.year > div.month {
float:left;
}
div.footer {
clear:both;
}
div.content div.month a.edit {
color:inherit;
font-weight:inherit;
text-decoration: none;
}
/* history tables and other tables */
table.history {
border: none;
}
td.history {
border: none;
}
table.user {
border: none;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 1em;
margin: 1em 2em;
}
table.user tr td, table.user tr th {
border: none;
padding: 0.2em 0.5em;
vertical-align: top;
}
table.arab tr th {
font-weight:normal;
text-align:left;
vertical-align:top;
}
table.arab, table.arab tr th, table.arab tr td {
border:none;
}
th.nobreak {
white-space:nowrap;
}
table.full { width:99%; margin-left:1px; }
table.j td, table.j th, table tr td.j, table tr th.j, .j { text-align:justify; }
table.l td, table.l th, table tr td.l, table tr th.l, .l { text-align:left; }
table.r td, table.r th, table tr td.r, table tr th.r, .r { text-align:right; }
table.c td, table.c th, table tr td.c, table tr th.c, .c { text-align:center; }
table.t td { vertical-align: top; }
td.half { width:50%; }
td.third { width:33%; }
form table td { padding:5px; }
/* lists */
dd { padding-bottom:0.5ex; }
dl.inside dt { float:left; }
/* search */
div.search span.result { font-size:larger; }
div.search span.info { font-size:smaller; font-style:italic; }
div.search p.result { display:none; }
img.logo {
float: right;
margin: 0 0 0 1ex;
padding: 0;
border: 1px solid #000;
opacity: 0.3;
background-color:#ffe;
}
/* images */
div.content a.feed img, div.journal a.feed img,
div.content a img.smiley, div.journal a img.smiley, img.smiley,
div.content a.inline img, div.journal a.inline img,
div.content li a.image img, div.journal li a.image img {
margin: 0; padding: 0; border: none;
}
div.image a img {
margin-bottom: 0;
}
div.image span.caption {
margin: 0 1em;
}
img {
max-width: 100%;
}
.left { float:left; margin-right: 1em; }
.right { float:right; margin-left: 1em; }
.half a img { height: 50%; width: 50%; }
div.left .left, div.right .right {
float:none;
}
.center { text-align:center; }
table.aside {
float:right;
width:40%;
margin-left: 1em;
padding: 1ex;
border: 1px dotted #666;
}
table.aside td {
text-align:left;
}
div.sidebar {
float:right; width: 250px;
text-align: right;
border: none;
margin: 1ex;
}
.bigsidebar {
float:right;
width: 500px;
border: none;
margin-left: 1ex;
font-size: 80%;
}
dl.irc dt { width:20ex; float:left; text-align:right; clear:left; }
dl.irc dt span.time { float:left; }
dl.irc dd { margin-left:22ex; }
/* portrait */
div.footer, div.comment, hr { clear: both; }
.portrait { float: left; font-size: small; margin-right: 1em; }
.portrait a { color: #999; }
div.left { float:left; margin:1em; padding: 0.5em; }
div.left p { display:table-cell; }
div.left p + p { display:table-caption; caption-side:bottom; }
p.table a { float:left; width:20ex; }
p.table + p { clear:both; }
/* no bleeding
@media screen {
div.content, div.rc {
overflow:hidden;
}
} */
/* rss */
channel * { display: block; }
channel title {
margin-top: 30pt;
}
copyright {
font-size: 14pt;
margin-top: 1em;
}
channel > link:before {
font-size: 18pt;
display: block;
margin: 1em;
padding: 0.5em;
content: "This is an RSS feed, designed to be read in a feed reader.";
color: red;
border: 1px solid red;
}
link, license {
font-size: 11pt;
margin-bottom: 9pt;
}
username:before { content: "Last edited by "; }
username:after { content: "."; }
generator:before { content: "Feed generated by "; }
generator:after { content: "."; }
channel description {
font-weight: bold;
}
item description {
font-style: italic;
font-weight: normal;
margin-bottom: 1em;
}
docs, language,
pubDate, lastBuildDate, ttl, guid, category, comments,
docs, image title, image link,
status, version, diff, history, importance {
display: none;
}

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

384
css/light.css Normal file
View File

@@ -0,0 +1,384 @@
/* This file is in the public domain. */
/* Esteban is nice, but bold is not so nice, and on Windows it suffers.
@import url(http://fonts.googleapis.com/css?family=Esteban&subset=latin,latin-ext);
For campaignwiki.org, we need to use the same URL in the config file when
calling wkhtmltopdf.
@import url(https://fonts.googleapis.com/css?family=Noticia+Text:400,400italic,700italic,700&subset=latin,latin-ext); */
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 400;
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 400;
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 400;
src: local('Noticia Text'), local('NoticiaText-Regular)'), url('/fonts/NoticiaText-Regular.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 700;
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 700;
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: normal;
font-weight: 700;
src: local('Noticia Text Bold'), local('NoticiaText-Bold)'), url('/fonts/NoticiaText-Bold.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 400;
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 400;
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 400;
src: local('Noticia Text Italic'), local('NoticiaText-Italic)'), url('/fonts/NoticiaText-Italic.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* vietnamese */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 700;
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 700;
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noticia Text';
font-style: italic;
font-weight: 700;
src: local('Noticia Text Bold Italic'), local('NoticiaText-BoldItalic)'), url('/fonts/NoticiaText-BoldItalic.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Symbola';
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff');
}
body {
font-family: "Noticia Text", Symbola, serif;
font-size: 14pt;
color: #000;
background-color: #eed;
margin:1em 2em;
}
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, Symbola;
font-size: 80%;
}
@media print {
body {
background-color: white;
font-family: Times, serif;
font-size:10pt;
}
}
/* iPhone */
@media only screen and (max-device-width: 480px) {
img { max-width: 480px !important; }
}
/* iPad */
@media only screen and (min-device-width: 481px) and (max-device-width: 900px) {
body { font-size: 150%; }
textarea,input { font-size: 100%; }
img { max-width: 550px !important; }
}
/* general */
.browse { min-height: 3em; }
.header form, .header p { margin: 0; }
/* hide the buttons but don't use display:none because of
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
.header input[type="submit"] { position: absolute; visibility: hidden; }
.header input { width: 5em; font-size: 80%; }
.footer { clear:both; font-size: 90%; }
.content input { font-size: 80%; line-height: 125%; }
/* comments, footer */
div.commentshown {
padding-bottom: 1ex;
padding-left: 2em;
border-left: 2px solid black;
font-size: smaller;
}
div.commenthidden { display:none; }
div.commentshown { display:block; }
/* comment pages with username, homepage, and email subscription */
.comment span { display: block; }
.comment span label {
display: inline-block; width: 10em;
}
input#mail, input#homepage, input#username {
display: inline-block; width: 20em;
}
/* titles */
h1 {
font-weight: bold;
font-size: 150%;
padding: 1em 0;
}
h1 a:link, h1 a:visited {
color: inherit;
background-color: inherit;
text-decoration: inherit;
}
h2 {
font-weight: bold;
font-size: 130%;
padding: 1em 0;
clear: both;
}
@media print {
h1 a, h2 a, h3 a, h4 a { font-style: normal; }
}
/* links */
a:link {
color: #851;
background-color: inherit;
}
a:visited {
color: #542;
background-color: inherit;
}
a:active {
color:#a41;
background-color: inherit;
}
.button {
display: inline-block;
font-size: 150%;
cursor: pointer;
padding: 0.3em 0.5em;
text-shadow: 0px -1px 0px #ccc;
background-color: #cfa;
border: 1px solid #9d8;
border-radius: 5px;
box-shadow: 0px 1px 3px white inset,
0px 1px 3px black;
}
.button a {
text-decoration: none;
font-weight: normal;
}
.bar a { padding-right: 1em; }
@media print {
a, a:link, a:visited {
color:#000;
text-decoration:none;
font-weight: normal;
}
a.edit, div.footer, form, span.gotobar, a.number span { display:none; }
a[class="url number"]:after, a[class="inter number"]:after {
content:"[" attr(href) "]";
}
a[class="local number"]:after { content:"[" attr(title) "]"; }
img[smiley] { line-height: inherit; }
}
/* edit paragraphs */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visible: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visible: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
table a.pencil {
position: absolute;
right: inherit;
}
/* table of contents */
.toc {
font-size: smaller;
border-left: 1em solid #886;
}
.toc ol {
list-style-type: none;
padding-left: 1em;
}
.toc a {
font-weight: normal;
}
/* images with links, captions, etc */
div.image { display: inline; margin: 1em; font-size: 90%; text-align: center; }
.left { float: left; margin-right: 1em; }
.right { float: right; margin-left: 1em; }
div.right .right { float: none; }
div.left .left { float: none; }
.caption { padding: 0 1em; }
.license { font-size: small; }
.aside {
font-size: small;
width: 30%;
float: right;
margin-left: 1em;
margin-bottom: 1em;
padding-left: 1em;
}
.aside img.smiley { height: 1em; }
.narrow {
width: 70%;
}
a img { border: 1px solid #333; }
.fit img { width: 80%; text-align: center; margin: 2em 8%; }
.half img { width: 50%; height: 50%; text-align: center; margin: 2em 8%; }
.noborder img { border: none; }
.twenty img { max-width: 20em; }
img.logo {
float: right;
clear: right;
border-style:none;
margin-left: 1em;
margin-bottom: 1ex;
border: 1px solid black;
}
/* fancy bold underline */
em.underline { font-weight: bold; }
/* editing, previewing */
textarea { width:100%; }
div.edit { padding-right: 1em; }
div.diff { padding-left:5%; padding-right:5%; }
div.old { background-color:#FFFFAF; }
div.new { background-color:#CFFFCF; }
/* div.message { background-color:#FEE; } */
div.message {
background-color: inherit;
font-size: smaller;
}
table.history { border-style:none; }
td.history { border-style:none; }
span.result { font-size:larger; }
span.info { font-size:smaller; font-style:italic; }
div.rc hr { display: none; }
div.rc li { padding-bottom: 0.5em; }
/* Tables */
table.user {
margin: 1em 0;
padding: 0 1em;
border-top: 1px solid black;
border-bottom: 1px solid black;
}
div.aside table.user {
margin: 1em 0;
padding: 0;
}
table.user td, table.user th {
border-style: none;
padding:5px 10px;
vertical-align: top;
}
table.user th { font-weight:bold; }
table.user td.r { text-align:right; }
table.user td.l { text-align:left; }
table.user td.c { text-align:center; }
table.user td.j { text-align:justify; }
table.user td.mark { background-color:yellow; }
tr:empty { display: block; height: 0.5em; }
@media print {
table {
font-size: 9pt;
margin: 0;
}
table.user td, table.user th {
padding: 0 1ex;
}
}
/* Calendar */
div.month { margin:0; padding:0; font-size:x-small; float:right; }
div.content div.month { float:none; }
div.year div.month { float:left; font-size:medium; padding:1ex; }
div.month pre { margin:0; padding:0 0 0 1ex; }
div.month a { text-decoration:none; font: inherit; }
div.month span.title a { font: inherit; }
/* no difference between a.exact and a.collection */
div.month a.local { font-weight: bold; }
div.month a.local:link { color: #562; }
div.month a.local:visited { color: #542; }
div.month a.today { background-color: #faa; }
div.month span.title a.local { font-weight: normal; color: #842; }
@media print {
div.month { display: none; }
div.year div.month { display: block; }
div.year div.month a { display: inline; }
}

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

@@ -0,0 +1,75 @@
/* Copyright 2014 Alex Schroeder <alex@gnu.org>
based on http://git.savannah.gnu.org/cgit/oddmuse.git/plain/plinks.js
for more information see http://oddmuse.org/wiki/Purple_Numbers_Extension
based on http://simon.incutio.com/archive/2004/05/30/plinks#p-13
Copyright 2004 Simon Willison
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
*/
function add_edit_links() {
/* Only show edit links on ordinary pages: They either use
* path_info or keywords in the URL, not parameters. */
if (/=/.test(document.location.href)) {
return;
}
// find all the pencil links
var links = new Array;
var elem = document.getElementsByTagName('a');
for (var i = 0; i < elem.length; i++) {
var atr = elem[i].getAttribute('class');
if (atr != null) {
var classes = atr.split(" ");
for (var j = 0; j < classes.length; j++) {
if (classes[j] == 'pencil') {
links.push(elem[i]);
}
}
}
}
// make them invisible
for (var i = 0; i < links.length; i++) {
var link = links[i];
var func = function(thislink) {
return function() {
if (thislink.style.visibility == "visible") {
thislink.style.transition = "visibility 0s 1s, opacity 1s linear";
thislink.style.visibility = "hidden";
thislink.style.opacity = "0";
} else {
thislink.style.transition = "opacity 1s linear";
thislink.style.visibility = "visible";
thislink.style.opacity = "1";
};
}
};
link.style.transition = "visibility 0s 1s, opacity 1s linear";
link.style.visibility = "hidden";
link.style.opacity = "0";
link.parentNode.onclick = func(link);
}
}
function add_load_event(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
add_load_event(add_edit_links);

213
modules/edit-paragraphs.pl Normal file
View File

@@ -0,0 +1,213 @@
# Copyright (C) 2014 Alex Schroeder <alex@gnu.org>
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
AddModuleDescription('edit-paragraph.pl', 'Edit Paragraphs Extension');
# edit icon
# http://publicdomainvectors.org/en/free-clipart/Pencil-vector-icon/9221.html
# q{<img width="30" height="30" title="" alt="edit" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAE2UlEQVRIx53WW0sbaRwG8GcmSsxhdTKT5K60FFqa4CmKh96uq62gtR563butN1KNQSgESqFemBjr2mKjMRpBt1uln2FJe9ObHsyu3d2yyH6EFoTVhr7PXrQTJslMmuwLA0mYvL95/vOebPjSJJMLJZ+t7iu9pw9AHEAQwAGAE4v/Wnasf5dNHsQK/R7A37IsEwAB/ATAY9W/VUpjh9Wi7wHw5s2b4vz588KAKyb9fDMVqniwPgB/AWA0GhX5fF68fftWBAIBHV82wf83bET/AMDJyUnx4cMHwa/t1atXRry07AW0FlhvPwA4dDgclCRJtLe3ixcvXtDYXr9+bcRXjLhcQ+JS9LeGhgbeunVLXL58WQBgIBDg8+fPy/BgMKjjD3VcrrHUOppraGjg3NycWFpaEmfOnKGiKLTC37x5I1paWnT8US2w3voBHDgcDkYiEZFIJITP56PH42E0GmVLSwsB8NKlS8xms0V4JpMRdXV1+lSrWNbS3/sBHLhcLs7OzopEIiH8fj8VReHy8jIjkQhdLhfPnTtXhudyOdHV1UVJkgSAPVQxV4uSulwuhsNhsbi4KPx+P1VV5dLSEsPhMGVZZnNzMxcWFnj27FkCYDAY5P7+vmhra9OT/gLAWe0yOADgwO12c2ZmpoBqmsbFxUWGw2HabDY2Nzdza2uLN27coCzL7Ojo0BPq6C4Ad7Ur0gCAnNvtLkrq9XoZj8cLSVtbW7mxscHx8XEC4NDQEBcWFoSqqjq6DeC7ass7ACBXWl6v18tYLFZA29vbub6+zomJCQLg8PAwt7e3RW9vL7+u22kATWYrlxl6BcDvTqeTkUhExONx4fP5CklnZ2dps9kYCoWYTCYL6PXr15lOp0V3d7eeNGWxUVii7xwOB+fm5spQPWlHR0cROjY2xlQqJXp6enQ0CUD71u5kRP+02+28c+eOiMfj9Hq91DSNiUSCMzMzpuj4+DjX1tZEb2+vjq4C8FZ4pUXoVQDv6+vrGY1GRSwWE5qmUVVVPnjwoICGQiGura0VBtLExASTyaQRfQTAV+U4wlV9E797966IxWJUVZWKonBlZYXT09OUZZltbW1MpVKV0IeSJPnN3qkZPAjgCADv3bsnYrEYPR4PGxsbubq6ytu3b1OSJLa2tjKdTnNsbMyqvCsmqGWprwD4R9/E79+/z6amJrrdbq6vr3NqaqqwImUyGY6OjhIAR0dHSwfSyjeSlrV3dXV1nJ+fFx8/fuSTJ0/Y2NjIjY0NTk1NUZIkBgIB7uzscGRkhAA4MjLCzc1N45R5KEmSr8LZzbRRURTx6dMnkuTx8TGz2SwnJycJgBcvXuTTp0957dq1osWhq6vLOJAqjl6rB6CqqoIkhfhyasnn80yn07xw4QL39/c5PDxMABwcHOTu7q7o7Ow0Thmt2tFbBmuaRiMshODJyQlfvnzJoaEhAuDAwAD39vZEKBQyLg6eGk6h5XB9fT0zmUwRTpKnp6d8/Pgx+/v7+ezZM+PWlrI4sko1lfrre+bm5mYZ/vnzZ2azWREMBnU0XbLLSFVureawjm9tbRUdV3K5nAgEAgQgAGQAuGpALRNLBlgCAKfTib6+PtjtdpyenvLw8FA6OjoSAH4G8COAfyuVzyKYafvVmNrkygPYAWCvMWnFUv8Hwmcq2TCQ4MwAAAAASUVORK5CYII=" />};
our $EditParagraphPencil = '&#x270E;';
# Allow editing of substrings
$Action{'edit-paragraph'} = \&DoEditParagraph;
sub DoEditParagraph {
my $id = UnquoteHtml(GetParam('title', ''));
UserCanEditOrDie($id);
my $old = UnquoteHtml(GetParam('paragraph', ''));
$old =~ s/\r//g;
return DoEdit($id) unless $old;
my $around = GetParam('around', undef);
# Find text to edit
my $new = GetParam('text', '');
OpenPage($id);
if ($new) {
my $myoldtime = GetParam('oldtime', ''); # maybe empty!
my $text;
if ($myoldtime and $myoldtime != $LastUpdate) {
($text) = GetTextAtTime($myoldtime);
} else {
$text = $Page{text};
}
my $done;
if ($around) {
# The tricky part is that the numbers refer to the HTML quoted text. What a pain.
my $qold = QuoteHtml($old);
my $qtext = QuoteHtml($text);
if (substr($qtext, $around - length($qold), length($qold)) eq $qold) {
$text = UnquoteHtml(substr($qtext, 0, $around - length($qold))
. QuoteHtml($new) . substr($qtext, $around));
$done = 1;
}
} else {
# simple case, just do it
my $search_term = quotemeta($old);
$done = $text =~ s/$search_term/$new/;
}
if ($done) {
SetParam('text', UnquoteHtml($text));
return DoPost($id);
} else {
$text = substr($text, 0, $around)
. "\n### around here ###\n"
. substr($text, $around)
if $around;
ReportError(T('Could not identify the paragraph you were editing'),
'500 INTERNAL SERVER ERROR',
undef,
$q->p(T('This is the section you edited:'))
. $q->pre(QuoteHtml($old))
. $q->p(T('This is the current page:'))
. $q->pre($text));
}
}
print GetHeader('', Ts('Editing %s', NormalToFree($id)));
print $q->start_div({-class=>'content edit paragraph'});
my $form = GetEditForm($id, undef, $old);
my $param = GetHiddenValue('paragraph', $old);
$param .= GetHiddenValue('action', 'edit-paragraph'); # add action
$param .= GetHiddenValue('around', $around); # add around position
$form =~ s!</form>!$param</form>!;
print $form;
print $q->end_div();
PrintFooter($id, 'edit');
}
# When PrintWikiToHTML is called for the current revision of a page we
# initialize our data structure. The data structure simply divides the
# page up into blocks based on what one would like to edit. By
# default, that's just paragraph breaks and list items. When using
# Creole, ordered list items and table rows are added.
my @EditParagraphs = ();
*EditParagraphOldPrintWikiToHTML = *PrintWikiToHTML;
*PrintWikiToHTML = *EditParagraphNewPrintWikiToHTML;
sub EditParagraphNewPrintWikiToHTML {
my ($text, $is_saving_cache, $revision, $is_locked) = @_;
# We need to use quoted HTML because that's what the rules will applied to!
my $quoted_text = QuoteHtml($text);
if ($quoted_text and not $revision) {
my ($start, $end) = (0, 0);
# This grouping with zero-width positive look-ahead assertion makes sure that this chunk of text does not include
# markup need for the next chunk of text.
if (grep { $_ eq \&CreoleRule } @MyRules) {
$regexp = "\n+(\n|(?=[*#-=|]))";
} else {
$regexp = "\n+(\n|(?=[*]))";
}
while ($quoted_text =~ /$regexp/g) {
$end = pos($quoted_text);
push(@EditParagraphs,
[$start, $end, substr($quoted_text, $start, $end - $start)]);
$start = $end;
}
# Only do this if we have at least two paragraphs and the end isn't just some empty lines.
if (@EditParagraphs and $start and $start < length($quoted_text)) {
push(@EditParagraphs, [$start, length($quoted_text), substr($quoted_text, $start)]);
}
}
# warn join('', '', map { $_->[0] . "-" . $_->[1] .": " . $_->[2]; } @EditParagraphs);
return EditParagraphOldPrintWikiToHTML(@_);
}
# Whenever an important element is closed, we try to add a link.
*EditParagraphOldCloseHtmlEnvironments = *CloseHtmlEnvironments;
*CloseHtmlEnvironments = *EditParagraphNewCloseHtmlEnvironments;
sub EditParagraphNewCloseHtmlEnvironments {
EditParagraph();
return EditParagraphOldCloseHtmlEnvironments(@_);
}
*EditParagraphOldCloseHtmlEnvironmentUntil = *CloseHtmlEnvironmentUntil;
*CloseHtmlEnvironmentUntil = *EditParagraphNewCloseHtmlEnvironmentUntil;
sub EditParagraphNewCloseHtmlEnvironmentUntil {
my $tag = $_[0];
if ($tag =~ /^(p|li|table|h[1-6])$/i) {
EditParagraph();
}
return EditParagraphOldCloseHtmlEnvironmentUntil(@_);
}
sub EditParagraph {
my $text;
my $pos = pos; # pos is empty for the last link
if (@EditParagraphs) {
if ($pos) {
while (@EditParagraphs and $EditParagraphs[0]->[1] <= $pos) {
$pos = $EditParagraphs[0]->[1]; # just in case we're overshooting
$text .= $EditParagraphs[0]->[2];
shift(@EditParagraphs);
}
} else {
# the last one
$text = $EditParagraphs[-1]->[2];
}
}
if ($text) {
# Huge Hack Alert: We are appending to $Fragment, which is what Clean appends to. We do this so that we can handle
# headers and other block elements. Without this fix we'd see something like this:
# <h2>...</h2><p><a ...>&#x270E;</a></p>
# Usually this would look as follows:
# <h2>...</h2><p></p>
# This is eliminated in Dirty. But it won't be eliminated if we leave the link in there. What we want is this:
# <h2>...<a ...>&#x270E;</a></h2><p></p>
#
# The same issue arises for other block level elements. What happens at the end of a table? Without this fix we'd
# see something like this:
# <table><tr><td>...</td></tr></table><p><a ...>&#x270E;</a></p>
# What we want, I guess, is this:
# <table><tr><td>...<a ...>&#x270E;</a></td></tr></table></p>
$pos = $pos || length(QuoteHtml($Page{text})); # make sure we have an around value
my $title = UrlEncode($OpenPageName);
my $paragraph = UrlEncode(UnquoteHtml($text));
my $link = ScriptLink("action=edit-paragraph;title=$title;around=$pos;paragraph=$paragraph",
$EditParagraphPencil, 'pencil');
if ($Fragment =~ s!((:?</h[1-6]>|</t[dh]></tr></table>|</pre>)<p>)$!!) {
# $Fragment .= '<!-- moved inside -->';
$Fragment .= $link . $1;
} elsif ($Fragment =~ s!(</p>\s*</form>)$!!) {
# $Fragment .= '<!-- HTML fixes for <html> -->';
# Since anything can appear in raw HTML tags, there is no one-size fits all rule.
# I usually use the <html> tags to embed forms, and forms need to contain a <p>.
# so that's what I'm handling.
$Fragment .= $link . $1;
} elsif ($pos and $Fragment =~ /<(p|tr)>$/) {
# Do nothing: this is either an empty paragraph and will be
# eliminated, or an empty row which will not be shown.
# $Fragment .= '<!-- empty -->';
} else {
# This is the default: add the link.
# $Fragment .= '<!-- default -->';
$Fragment .= $link;
}
}
}

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

@@ -17,45 +17,60 @@ package OddMuse;
use File::Basename;
use File::Copy;
AddModuleDescription('module-bisect.pl', 'Bisect Extension');
AddModuleDescription('module-bisect.pl', 'Module Bisect Extension');
push(@MyAdminCode, \&ModuleBisectMenu);
$Action{bisect} = \&BisectAction;
sub ModuleBisectMenu {
return unless UserIsAdmin();
my ($id, $menuref, $restref) = @_;
push(@$menuref, ScriptLink('action=bisect', T('Bisect modules'), 'modulebisect'));
}
sub BisectAction {
UserIsAdminOrError();
RequestLockOrError();
print GetHeader('', T('Module Bisect'), '');
print GetHeader('', T('Module Bisect'), '', 'nocache');
if (GetParam('stop')) {
BisectEnableAll(1);
print $q->br(), $q->strong(T('All modules enabled now!'));
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
print $q->submit(-name=>'noop', -value=>T('Go back'));
print $q->end_form();
} elsif (GetParam('good') or GetParam('bad')) {
BisectProcess(GetParam('good'));
} else {
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
my @disabledFiles = bsd_glob("$ModuleDir/*.p[ml].disabled");
if (@disabledFiles == 0) {
print 'Test / Always enabled / Always disabled', $q->br();
my @files = bsd_glob("$ModuleDir/*.p[ml]");
for (my $i = 0; $i < @files; $i++) {
my $moduleName = fileparse($files[$i]);
my @disabled = ($moduleName eq 'module-bisect.pl' ? (-disabled=>'disabled') : ());
print $q->input({-type=>'radio', -name=>"m$i", -value=>'t', ($moduleName ne 'module-bisect.pl' ? (-checked=>'checked') : ()), @disabled});
print $q->input({-type=>'radio', -name=>"m$i", -value=>'on', ($moduleName eq 'module-bisect.pl' ? (-checked=>'checked') : ())});
print $q->input({-type=>'radio', -name=>"m$i", -value=>'off', @disabled});
print $moduleName, $q->br();
}
print $q->submit(-name=>'bad', -value=>T('Start'));
} else {
print T('Biscecting proccess is already active.'), $q->br();
print $q->submit(-name=>'stop', -value=>T('Stop'));
}
print $q->end_form();
BisectInitialScreen();
}
PrintFooter();
ReleaseLock();
}
sub BisectInitialScreen {
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
my @disabledFiles = bsd_glob("$ModuleDir/*.p[ml].disabled");
if (@disabledFiles == 0) {
print T('Test / Always enabled / Always disabled'), $q->br();
my @files = bsd_glob("$ModuleDir/*.p[ml]");
for (my $i = 0; $i < @files; $i++) {
my $moduleName = fileparse($files[$i]);
my @disabled = ($moduleName eq 'module-bisect.pl' ? (-disabled=>'disabled') : ());
print $q->input({-type=>'radio', -name=>"m$i", -value=>'t', ($moduleName ne 'module-bisect.pl' ? (-checked=>'checked') : ()), @disabled});
print $q->input({-type=>'radio', -name=>"m$i", -value=>'on', ($moduleName eq 'module-bisect.pl' ? (-checked=>'checked') : ())});
print $q->input({-type=>'radio', -name=>"m$i", -value=>'off', @disabled});
print $moduleName, $q->br();
}
print $q->submit(-name=>'bad', -value=>T('Start'));
} else {
print T('Biscecting proccess is already active.'), $q->br();
print $q->submit(-name=>'stop', -value=>T('Stop'));
}
print $q->end_form();
}
sub BisectProcess {
my ($isGood) = @_;
my $parameterHandover = '';
@@ -71,12 +86,13 @@ sub BisectProcess {
splice @files, $i, 1;
}
}
my $start = GetParam('start') - 1; # $start and $end are indexes
my $end = GetParam('end') - 1;
$start = 0 if $start < 0; # not specified (probably right after Start)
$end = @files * 2 - 1 if $end < 0;
my $start = GetParam('start', 1) - 1; # $start and $end are indexes
my $end = GetParam('end', @files * 2) - 1;
if ($end - $start <= 1) {
print 'It seems like ', $q->strong((fileparse($isGood ? $files[$end] : $files[$start]))[0]), ' is causing your problem.';
print Ts('It seems like module %s is causing your problem.',
$q->strong((fileparse($isGood ? $files[$end] : $files[$start]))[0])), $q->br(), $q->br();
print T('Please note that this module does not handle situations when your problem is caused by a combination of specific modules (which is rare anyway).'), $q->br();
print T('Good luck fixing your problem! ;)');
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
print $q->submit(-name=>'stop', -value=>T('Stop'));
@@ -115,7 +131,7 @@ sub BisectEnableAll {
for (bsd_glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
my $oldName = $_;
s/\.disabled$//;
print "Enabling ", (fileparse($_))[0], '...', $q->br() if $_[0];
print Ts('Enabling %s', (fileparse($_))[0]), '...', $q->br() if $_[0];
move($oldName, $_);
}
}

View File

@@ -31,9 +31,9 @@ sub ModuleUpdaterMenu {
}
sub ModuleUpdaterAction {
return unless UserIsAdminOrError();
UserIsAdminOrError();
RequestLockOrError();
print GetHeader('', T('Module Updater'), '');
print GetHeader('', T('Module Updater'), '', 'nocache');
if (GetParam('ok')) {
ModuleUpdaterApply();
@@ -68,7 +68,7 @@ sub ModuleUpdaterApply {
print $q->br(), $q->strong('Done!');
}
sub ProcessModule() {
sub ProcessModule {
my $module = shift;
CreateDir($TempDir);
print $q->hr();

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);
@@ -87,6 +94,14 @@ sub TocScript {
if (toc) {
var html = outline.asHTML(true);
toc.innerHTML = html;
items = toc.getElementsByTagName('a');
for (var i = 0; i < items.length; i++) {
while (items[i].textContent.endsWith('✎')) {
var text = items[i].childNodes[0].nodeValue;
items[i].childNodes[0].nodeValue = text.substring(0, text.length - 1);
}
}
}
}
}

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

@@ -1,24 +1,20 @@
# Copyright (C) 2006, 2007 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 2006, 2007 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.
# 59 Temple Place, Suite 330
# Boston, MA 02111-1307 USA
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 7;
use Test::More tests => 8;
clear_pages();
@@ -27,23 +23,39 @@ sub get_etag {
return $1 if $str =~ /Etag: (.*)\r\n/;
}
sub get_last_modified {
my $str = shift;
return $1 if $str =~ /Last-Modified: (.*)\r\n/i;
}
# Get the ts from the page db and compare it to the Etag
update_page('CacheTest', 'something');
OpenPage('CacheTest');
my $ts1 = $Page{ts};
my $ts2 = get_etag(get_page('CacheTest'));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of this page: $ts1 and $ts2 are close");
# When updating another page, that page's ts is the new Etag for all of them
update_page('OtherPage', 'something');
OpenPage('OtherPage');
$ts1 = $Page{ts};
$ts2 = get_etag(get_page('OtherPage'));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of other page: $ts1 and $ts2 are close");
# Getting it raw should use the original timestamp
OpenPage('CacheTest');
$ts1 = $Page{ts};
$ts2 = get_etag(get_page('/raw/CacheTest?'));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 are close");
$page = get_page('/raw/CacheTest?');
$ts2 = get_etag($page);
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 based on etag are close");
SKIP: {
eval { require Date::Parse };
skip ("Date::Parse not installed", 1) if $@;
$ts2 = Date::Parse::str2time(get_last_modified($page));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 based on last-modified timestamp are close");
}
$str = 'This is a WikiLink.';

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

299
t/edit-paragraphs.t Normal file
View File

@@ -0,0 +1,299 @@
# Copyright (C) 2014 Alex Schroeder <alex@gnu.org>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 56;
use utf8;
clear_pages();
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
add_module('edit-paragraphs.pl');
my $text = q{Give me a torch: I am not for this ambling;
Being but heavy, I will bear the light.
Nay, gentle Romeo, we must have you dance.
Not I, believe me: you have dancing shoes
With nimble soles: I have a soul of lead
So stakes me to the ground I cannot move.
};
my $page = update_page('Romeo_and_Mercutio', $text);
for my $paragraph (split(/\n\n+/, $text)) {
test_page($page, 'action=edit-paragraph;title=Romeo_and_Mercutio;around=\d*;paragraph='
. UrlEncode($paragraph));
}
# Check whether the form is right.
ok($page =~ /action=edit-paragraph;title=Romeo_and_Mercutio;around=(\d*);paragraph=([^"]*)/, 'found example link to use');
my $around = $1;
my $enc = $2;
my $par = UrlDecode($enc);
xpath_test(get_page("action=edit-paragraph title=Romeo_and_Mercutio around=$around paragraph=$enc"),
q'//input[@type="hidden"][@value="Romeo_and_Mercutio"][@name="title"]',
q'//input[@type="hidden"][@value="edit-paragraph"][@name="action"]',
qq'//textarea[text()="$par"]',
qq'//input[\@type="hidden"][\@value="$around"][\@name="around"]');
# Test for extra links in empty paragraphs before and after tables.
add_module('creole.pl');
$text = q{|PARIS |JULIET |
|Come you to make confession to this father? |To answer that, I should confess to you. |
|Do not deny to him that you love me. |I will confess to you that I love him. |
|So will ye, I am sure, that you love me. | |
-- William Shakespeare, Romeo and Juliet, Act IV, Scene I
};
$page = update_page('Paris_and_Juliet', $text);
test_page_negative($page, '\|', '</table><p><a ');
for my $row (split(/\n/, $text)) {
test_page($page, 'action=edit-paragraph;title=Paris_and_Juliet;around=\d*;paragraph='
. UrlEncode($row));
}
$text = q{== Romeo and Juliet, Act III, Scene II
* Tybalt is gone, and Romeo banished;
Romeo that kill'd him, he is banished.
* O God! did Romeo's hand shed Tybalt's blood?
* It did, it did; alas the day, it did!
};
$page = update_page('Nurse_and_Juliet', $text);
for my $item (split(/\n(?=[*])/, $text)) {
my $str = UrlEncode($item);
$str =~ s/\*/\\*/g;
test_page($page, 'action=edit-paragraph;title=Nurse_and_Juliet;around=\d*;paragraph='
. $str);
}
$text = q{Benvolio: Tell me in sadness, who is that you love.
Romeo: What, shall I groan and tell thee?
Benvolio: Groan! why, no. But sadly tell me who.
Romeo: Bid a sick man in sadness make his will: Ah, word ill urged to
one that is so ill! In sadness, cousin, I do love a woman.
Benvolio: I aim'd so near, when I supposed you loved.
Romeo: A right good mark-man! And she's fair I love.
Benvolio: A right fair mark, fair coz, is soonest hit.
};
# Replace the first occurence.
test_page(update_page('Benvolio_and_Romeo', $text),
'Benvolio: Tell me in sadness');
test_page(get_page('action=edit-paragraph title=Benvolio_and_Romeo '
. 'around=0 paragraph=Benvolio text=Ben'),
# not using update_page because of the parameters
'Status: 302');
test_page(get_page('Benvolio_and_Romeo'),
'Ben: Tell me in sadness',
'Benvolio: Groan!');
# Reset and try again but replace the occurence around 105.
update_page('Benvolio_and_Romeo', $text);
test_page(get_page('action=edit-paragraph title=Benvolio_and_Romeo '
. 'around=105 '
. 'paragraph=Benvolio text=Ben'),
# not using update_page because of the parameters
'Status: 302');
test_page(get_page('Benvolio_and_Romeo'),
'Benvolio: Tell me in sadness',
'Ben: Groan!');
SKIP: {
skip "We don't do fuzzy matching anymore.", 3;
# Try again but now let's simulate a page changed in the background
# such that the text is now not exactly at the expected position, but
# close by.
$text =~ s/tell thee/tell you/;
update_page('Benvolio_and_Romeo', $text);
test_page(get_page('action=edit-paragraph title=Benvolio_and_Romeo '
. 'around=105 '
. 'paragraph=Benvolio text=Ben'),
# not using update_page because of the parameters
'Status: 302');
test_page(get_page('Benvolio_and_Romeo'),
'Benvolio: Tell me in sadness',
'Ben: Groan!');
}
# HTML encoding.
$text = q{I am hurt.
<em>A plague o' both your houses!</em> I am sped.
};
xpath_test(get_page('action=edit-paragraph title=Mercutio paragraph="' . UrlEncode($text) . '"'),
qq'//textarea[text()="$text"]');
# Make sure the link at the very end is shown.
$text = q{
{{{
BENVOLIO Romeo, away, be gone!
The citizens are up, and Tybalt slain.
Stand not amazed: the prince will doom thee death,
If thou art taken: hence, be gone, away!
ROMEO O, I am fortune's fool!
}}}
----
William Shakespeare, Romeo and Juliet, Act III, Scene I
};
$page = update_page('Benvolio_and_Romeo', $text);
test_page_negative($page, '</pre><p><a ', '<p><a ');
test_page($page, 'fool!<a ', '</pre><hr ?/><p>William', 'Scene I<a');
# Mixed lists.
$text = q{* One
## Two
};
$page = update_page('Alex_Daniel', $text);
for my $item (split(/\n(?=[\*\#])/, $text)) {
my $str = UrlEncode($item);
$str =~ s/\*/\\*/g;
test_page($page, 'action=edit-paragraph;title=Alex_Daniel;around=\d*;paragraph='
. $str);
}
# Comments, the last element in particular.
$text = q{Test
-- Anonymous
----
Test
-- Real Anonymous
};
$page = update_page('Comments_on_Alex_Daniel', $text);
test_page($page, 'action=edit-paragraph;title=Comments_on_Alex_Daniel;around=\d*;paragraph=--%20Real%20Anonymous%0a');
# More than one newline at the end.
$text = q{one
two
};
$page = update_page('Alex_Daniel', $text);
for my $item (split(/\n+/, $text)) {
my $str = UrlEncode($item);
$str =~ s/\*/\\*/g;
test_page($page, 'action=edit-paragraph;title=Alex_Daniel;around=\d*;paragraph='
. $str);
}
# More HTML encoding.
$text = q{Test.
<test1>
<test2>
<test3>
};
$page = update_page('Test', $text);
test_page_negative(get_page('action=browse id=Test raw=1'), '&lt;');
# HTML encoded text is longer: every < > counts adds 3. Thus 33 + 6x3 = 51.
my $action = 'action=edit-paragraph;title=Test;around=51;paragraph=' . UrlEncode("<test3>\n");
test_page($page, $action);
test_page(get_page(join(' ', split(';', $action))), QuoteHtml("<test3>\n"));
# test error
test_page(get_page('action=edit-paragraph title=Test'
. ' paragraph=' . UrlEncode("<test3>\n") # old
. ' text=' . UrlEncode("<test30>\n") # new
. ' around=1'),
'Could not identify the paragraph you were editing',
'<pre>' . QuoteHtml("<test3>\n") . '</pre>');
test_page(get_page('action=edit-paragraph title=Test'
. ' paragraph=' . UrlEncode("<test3>\n") # old
. ' text=' . UrlEncode("<test30>\n") # new
. ' around=51'), 'Status: 302');
$text =~ s/test3/test30/;
test_page(get_page('action=browse id=Test raw=1'), $text);
# ampersand in a pagename
$text = q{d4
d5
d8
d10
};
$page = update_page('D%26D', $text);
my $action = 'action=edit-paragraph;title=D%26D;around=8;paragraph=d5%0a%0a';
test_page($page, $action);
test_page(get_page(join(' ', split(';', $action))), 'd5');
# test error
test_page(get_page('action=edit-paragraph title=D%26D'
. ' paragraph=d5%0a%0a' # old
. ' text=d6%0a%0a' # new
. ' around=8'),
'Status: 302');
$text =~ s/d5/d6/;
test_page(get_page('action=browse id=D%26D raw=1'), $text);
# questionmark, square brackets, bullet lists in the edited text
$text = q{* who?
* [where]?
* why?
* what?
};
$page = update_page('Questions', $text);
my $action = 'action=edit-paragraph;title=Questions;around=18;paragraph=*%20%5bwhere%5d%3f%0a';
test_page($page, quotemeta($action));
test_page(get_page(join(' ', split(';', $action))), '\[where\]\?');
# test error
test_page(get_page('action=edit-paragraph title=Questions'
. ' paragraph=*%20%5bwhere%5d%3f%0a' # old
. ' text=*%20how%3f%0a' # new
. ' around=18'),
'Status: 302');
$text =~ s/\[where\]\?/how?/;
test_page(get_page('action=browse id=Questions raw=1'), quotemeta($text));

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

282
wiki.pl
View File

@@ -29,41 +29,36 @@
package OddMuse;
use strict;
use utf8; # in case anybody ever addes UTF8 characters to the source
use CGI qw/-utf8/;
use CGI::Carp qw(fatalsToBrowser);
use File::Glob ':glob';
use File::Basename;
local $| = 1; # Do not buffer output (localized for mod_perl)
# Options:
use vars qw($RssLicense $RssCacheHours @RcDays $TempDir $LockDir $DataDir
$KeepDir $PageDir $RcOldFile $IndexFile $BannedContent $NoEditFile $BannedHosts
$ConfigFile $FullUrl $SiteName $HomePage $LogoUrl $RcDefault $RssDir
$IndentLimit $RecentTop $RecentLink $EditAllowed $UseDiff $KeepDays $KeepMajor
$EmbedWiki $BracketText $UseConfig $AdminPass $EditPass
$PassHashFunction $PassSalt $NetworkFile
$BracketWiki $FreeLinks $WikiLinks $SummaryHours $FreeLinkPattern $RCName
$RunCGI $ShowEdits $LinkPattern $RssExclude $InterLinkPattern $MaxPost $UseGrep
$UrlPattern $UrlProtocols $ImageExtensions $InterSitePattern $FS $CookieName
$SiteBase $StyleSheet $NotFoundPg $FooterNote $NewText $EditNote $UserGotoBar
$VisitorFile $RcFile %Smilies %SpecialDays $InterWikiMoniker $SiteDescription
$RssImageUrl $ReadMe $RssRights $BannedCanRead $SurgeProtection $TopLinkBar
$LanguageLimit $SurgeProtectionTime $SurgeProtectionViews $DeletedPage
%Languages $InterMap $ValidatorLink %LockOnCreation $RssStyleSheet
%CookieParameters @UserGotoBarPages $NewComment $HtmlHeaders $StyleSheetPage
$ConfigPage $ScriptName $CommentsPrefix $CommentsPattern @UploadTypes $AllNetworkFiles
$UsePathInfo $UploadAllowed $LastUpdate $PageCluster %PlainTextPages
$RssInterwikiTranslate $UseCache $Counter $ModuleDir $FullUrlPattern
$SummaryDefaultLength $FreeInterLinkPattern %InvisibleCookieParameters
%AdminPages $UseQuestionmark $JournalLimit $LockExpiration $RssStrip
%LockExpires @IndexOptions @Debugging $DocumentHeader %HtmlEnvironmentContainers
@MyAdminCode @MyFooters @MyInitVariables @MyMacros @MyMaintenance @MyRules);
use vars qw($RssLicense $RssCacheHours @RcDays $TempDir $LockDir $DataDir $KeepDir $PageDir $FileDir $RcOldFile $IndexFile
$BannedContent $NoEditFile $BannedHosts $ConfigFile $FullUrl $SiteName $HomePage $LogoUrl $RcDefault $RssDir
$IndentLimit $RecentTop $RecentLink $EditAllowed $UseDiff $KeepDays $KeepMajor $EmbedWiki $BracketText $UseConfig
$AdminPass $EditPass $PassHashFunction $PassSalt $NetworkFile $BracketWiki $FreeLinks $WikiLinks $SummaryHours
$FreeLinkPattern $RCName $RunCGI $ShowEdits $LinkPattern $RssExclude $InterLinkPattern $MaxPost $UseGrep $UrlPattern
$UrlProtocols $ImageExtensions $InterSitePattern $FS $CookieName $SiteBase $StyleSheet $NotFoundPg $FooterNote $NewText
$EditNote $UserGotoBar $VisitorFile $RcFile %Smilies %SpecialDays $InterWikiMoniker $SiteDescription $RssImageUrl
$ReadMe $RssRights $BannedCanRead $SurgeProtection $TopLinkBar $TopSearchForm $MatchingPages $LanguageLimit
$SurgeProtectionTime $SurgeProtectionViews $DeletedPage %Languages $InterMap $ValidatorLink %LockOnCreation
$RssStyleSheet %CookieParameters @UserGotoBarPages $NewComment $HtmlHeaders $StyleSheetPage $ConfigPage $ScriptName
$CommentsPrefix $CommentsPattern @UploadTypes $AllNetworkFiles $UsePathInfo $UploadAllowed $FilenameWhitelist @AdditionalChars
$LastUpdate $PageCluster
%PlainTextPages $RssInterwikiTranslate $UseCache $Counter $ModuleDir $FullUrlPattern $SummaryDefaultLength
$FreeInterLinkPattern %InvisibleCookieParameters %AdminPages $UseQuestionmark $JournalLimit $LockExpiration $RssStrip
%LockExpires @IndexOptions @Debugging $DocumentHeader %HtmlEnvironmentContainers @MyAdminCode @MyFooters
@MyInitVariables @MyMacros @MyMaintenance @MyRules);
# Internal variables:
use vars qw(%Page %InterSite %IndexHash %Translate %OldCookie $FootnoteNumber
$OpenPageName @IndexList $Message $q $Now %RecentVisitors @HtmlStack
@HtmlAttrStack $ReplaceForm %MyInc $CollectingJournal $bol $WikiDescription
$PrintedHeader %Locks $Fragment @Blocks @Flags $Today @KnownLocks
$ModulesDescription %Action %RuleOrder %Includes %RssInterwikiTranslate);
use vars qw(%Page %InterSite %IndexHash %Translate %OldCookie $FootnoteNumber $OpenPageName @IndexList $Message $q $Now
%RecentVisitors @HtmlStack @HtmlAttrStack $ReplaceForm %MyInc $CollectingJournal $bol $WikiDescription $PrintedHeader
%Locks $Fragment @Blocks @Flags $Today @KnownLocks $ModulesDescription %Action %RuleOrder %Includes
%RssInterwikiTranslate);
# Can be set outside the script: $DataDir, $UseConfig, $ConfigFile, $ModuleDir,
# $ConfigPage, $AdminPass, $EditPass, $ScriptName, $FullUrl, $RunCGI.
@@ -97,7 +92,7 @@ $LogoUrl = ''; # URL for site logo ('' for no logo)
$NotFoundPg = ''; # Page for not-found links ('' for blank pg)
$NewText = T('This page is empty.') . "\n"; # New page text
$NewComment = T('Add your comment here:') . "\n"; # New comment text
$NewComment = T('Add your comment here:'); # New comment text
$EditAllowed = 1; # 0 = no, 1 = yes, 2 = comments pages only, 3 = comments only
$AdminPass //= ''; # Whitespace separated passwords.
@@ -135,33 +130,37 @@ $ShowEdits = 0; # 1 = major and show minor edits in recent chang
$RecentTop = 1; # 1 = most recent entries at the top of the list
$RecentLink = 1; # 1 = link to usernames
$PageCluster = ''; # name of cluster page, eg. 'Cluster' to enable
$InterWikiMoniker = ''; # InterWiki prefix for this wiki for RSS
$SiteDescription = ''; # RSS Description of this wiki
$InterWikiMoniker = ''; # InterWiki prefix for this wiki for RSS
$SiteDescription = ''; # RSS Description of this wiki
$RssStrip = '^\d\d\d\d-\d\d-\d\d_'; # Regexp to strip from feed item titles
$RssImageUrl = $LogoUrl; # URL to image to associate with your RSS feed
$RssRights = ''; # Copyright notice for RSS, usually an URL to the appropriate text
$RssImageUrl = $LogoUrl; # URL to image to associate with your RSS feed
$RssRights = ''; # Copyright notice for RSS, usually an URL to the appropriate text
$RssExclude = 'RssExclude'; # name of the page that lists pages to be excluded from the feed
$RssCacheHours = 1; # How many hours to cache remote RSS files
$RssStyleSheet = ''; # External style sheet for RSS files
$UploadAllowed = 0; # 1 = yes, 0 = administrators only
$RssCacheHours = 1; # How many hours to cache remote RSS files
$RssStyleSheet = ''; # External style sheet for RSS files
$UploadAllowed = 0; # 1 = yes, 0 = administrators only
@UploadTypes = ('image/jpeg', 'image/png'); # MIME types allowed, all allowed if empty list
$EmbedWiki = 0; # 1 = no headers/footers
$FooterNote = ''; # HTML for bottom of every page
$EditNote = ''; # HTML notice above buttons on edit page
$TopLinkBar = 1; # 1 = add a goto bar at the top of the page
@UserGotoBarPages = (); # List of pagenames
$UserGotoBar = ''; # HTML added to end of goto bar
$ValidatorLink = 0; # 1 = Link to the W3C HTML validator service
$CommentsPrefix = ''; # prefix for comment pages, eg. 'Comments_on_' to enable
$CommentsPattern = undef; # regex used to match comment pages
$HtmlHeaders = ''; # Additional stuff to put in the HTML <head> section
$IndentLimit = 20; # Maximum depth of nested lists
$LanguageLimit = 3; # Number of matches req. for each language
$JournalLimit = 200; # how many pages can be collected in one go?
$FilenameWhitelist = 'a-zA-Z0-9_.-'; # Other characters will be removed from the filenames (uploaded files only)
@AdditionalChars = ('A'..'Z', 'a'..'z', '0'..'9'); # These characters will be appended if the file already exists
$EmbedWiki = 0; # 1 = no headers/footers
$FooterNote = ''; # HTML for bottom of every page
$EditNote = ''; # HTML notice above buttons on edit page
$TopLinkBar = 1; # 0 = goto bar both at the top and bottom; 1 = top, 2 = bottom
$TopSearchForm = 1; # 0 = search form both at the top and bottom; 1 = top, 2 = bottom
$MatchingPages = 0; # 1 = search page content and page titles
@UserGotoBarPages = (); # List of pagenames
$UserGotoBar = ''; # HTML added to end of goto bar
$ValidatorLink = 0; # 1 = Link to the W3C HTML validator service
$CommentsPrefix = ''; # prefix for comment pages, eg. 'Comments_on_' to enable
$CommentsPattern = undef; # regex used to match comment pages
$HtmlHeaders = ''; # Additional stuff to put in the HTML <head> section
$IndentLimit = 20; # Maximum depth of nested lists
$LanguageLimit = 3; # Number of matches req. for each language
$JournalLimit = 200; # how many pages can be collected in one go?
$DocumentHeader = qq(<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN")
. qq( "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n)
. qq(<html xmlns="http://www.w3.org/1999/xhtml">);
# Checkboxes at the end of the index.
# Checkboxes at the end of the index.
@IndexOptions = ();
# Display short comments below the GotoBar for special days
# Example: %SpecialDays = ('1-1' => 'New Year', '1-2' => 'Next Day');
@@ -175,8 +174,8 @@ $DocumentHeader = qq(<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN")
@KnownLocks = qw(main diff index merge visitors); # locks to remove
$LockExpiration = 60; # How long before expirable locks are expired
%LockExpires = (diff=>1, index=>1, merge=>1, visitors=>1); # locks to expire after some time
%CookieParameters = (username=>'', pwd=>'', homepage=>'', theme=>'', css=>'', msg=>'',
lang=>'', toplinkbar=>$TopLinkBar, embed=>$EmbedWiki, );
%CookieParameters = (username=>'', pwd=>'', homepage=>'', theme=>'', css=>'', msg=>'', lang=>'', embed=>$EmbedWiki,
toplinkbar=>$TopLinkBar, topsearchform=>$TopSearchForm, matchingpages=>$MatchingPages, );
%InvisibleCookieParameters = (msg=>1, pwd=>1,);
%Action = (rc => \&BrowseRc, rollback => \&DoRollback,
browse => \&BrowseResolvedPage, maintain => \&DoMaintain,
@@ -214,7 +213,8 @@ sub ReportError { # fatal!
}
sub Init {
binmode(STDOUT, ':utf8');
binmode(STDOUT, ':utf8'); # this is where the HTML gets printed
binmode(STDERR, ':utf8'); # just in case somebody prints debug info to stderr
InitDirConfig();
$FS = "\x1e"; # The FS character is the RECORD SEPARATOR control char in ASCII
$Message = ''; # Warnings and non-fatal errors.
@@ -252,6 +252,7 @@ sub InitConfig {
sub InitDirConfig {
utf8::decode($DataDir); # just in case, eg. "WikiDataDir=/tmp/Zürich♥ perl wiki.pl"
$PageDir = "$DataDir/page"; # Stores page data
$FileDir = "$DataDir/file"; # Stores uploaded files
$KeepDir = "$DataDir/keep"; # Stores kept (old) page data
$TempDir = "$DataDir/temp"; # Temporary files and locks
$LockDir = "$TempDir/lock"; # DB is locked if this exists
@@ -361,6 +362,7 @@ sub CookieRollbackFix {
sub GetParam {
my ($name, $default) = @_;
utf8::encode($name); # turn to byte string
my $result = $q->param($name);
$result //= $default;
return QuoteHtml($result); # you need to unquote anything that can have <tags>
@@ -424,7 +426,8 @@ sub ApplyRules {
Clean(CloseHtmlEnvironments() . $q->pre($text));
} elsif (my ($type) = TextIsFile($text)) { # TODO? $type defined here??
Clean(CloseHtmlEnvironments() . $q->p(T('This page contains an uploaded file:'))
. $q->p(GetDownloadLink($OpenPageName, (substr($type, 0, 6) eq 'image/'), $revision)));
. $q->p(GetDownloadLink($OpenPageName, (substr($type, 0, 6) eq 'image/'), $revision))
. (length $Page{summary} > 0 ? $q->blockquote(QuoteHtml($Page{summary})) : $q->p(T('No summary was provided for this file.'))));
} else {
my $smileyregex = join "|", keys %Smilies;
$smileyregex = qr/(?=$smileyregex)/;
@@ -1260,11 +1263,13 @@ sub PageHtml {
local *STDOUT;
OpenPage($id);
open(STDOUT, '>', \$diff) or die "Can't open memory file: $!";
binmode(STDOUT); # works whether STDOUT already has the UTF8 layer or not
binmode(STDOUT, ":utf8");
PrintPageDiff();
utf8::decode($diff);
return $error if $limit and length($diff) > $limit;
open(STDOUT, '>', \$page) or die "Can't open memory file: $!";
binmode(STDOUT); # works whether STDOUT already has the UTF8 layer or not
binmode(STDOUT, ":utf8");
PrintPageHtml();
utf8::decode($page);
@@ -1319,16 +1324,18 @@ sub DoBrowseRequest {
my $id = GetId();
my $action = lc(GetParam('action', '')); # script?action=foo;id=bar
$action = 'download' if GetParam('download', '') and not $action; # script/download/id
my $search = GetParam('search', '');
if ($Action{$action}) {
&{$Action{$action}}($id);
} elsif ($action and defined &MyActions) {
eval { local $SIG{__DIE__}; MyActions(); };
} elsif ($action) {
ReportError(Ts('Invalid action parameter %s', $action), '501 NOT IMPLEMENTED');
} elsif ($search ne '') { # allow search for "0"
SetParam('action', 'search'); # fake it
DoSearch($search);
} elsif (GetParam('match', '') ne '') {
SetParam('action', 'index'); # make sure this gets a NOINDEX
DoIndex();
} elsif (GetParam('search', '') ne '') { # allow search for "0"
SetParam('action', 'search'); # make sure this gets a NOINDEX
DoSearch();
} elsif (GetParam('title', '') and not GetParam('Cancel', '')) {
DoPost(GetParam('title', ''));
} else {
@@ -1453,7 +1460,7 @@ sub PageFresh { # pages can depend on other pages (ie. last update), admin statu
sub PageEtag {
my ($changed, $visible, %params) = CookieData();
return UrlEncode(join($FS, $LastUpdate, sort(values %params))); # no CTL in field values
return UrlEncode(join($FS, $LastUpdate||$Now, sort(values %params))); # no CTL in field values
}
sub FileFresh { # old files are never stale, current files are stale when the page was modified
@@ -1861,8 +1868,7 @@ sub GetRcRss {
<channel>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
};
my $title = QuoteHtml($SiteName) . ': '
. GetParam('title', QuoteHtml(NormalToFree($HomePage)));
my $title = QuoteHtml($SiteName) . ': ' . GetParam('title', QuoteHtml(NormalToFree($HomePage)));
$rss .= "<title>$title</title>\n";
$rss .= "<link>" . ScriptUrl($HomePage) . "</link>\n";
$rss .= qq{<atom:link href="} . GetScriptUrlWithRcParameters()
@@ -2086,11 +2092,20 @@ sub DoAdminPage {
my @menu = ();
push(@menu, ScriptLink('action=index', T('Index of all pages'), 'index')) if $Action{index};
push(@menu, ScriptLink('action=version', T('Wiki Version'), 'version')) if $Action{version};
push(@menu, ScriptLink('action=unlock', T('Unlock Wiki'), 'unlock')) if $Action{unlock};
push(@menu, ScriptLink('action=password', T('Password'), 'password')) if $Action{password};
push(@menu, ScriptLink('action=password', T('Password'), 'password')) if $Action{password};
push(@menu, ScriptLink('action=maintain', T('Run maintenance'), 'maintain')) if $Action{maintain};
my @locks;
for my $pattern (@KnownLocks) {
for my $name (bsd_glob $pattern) {
if (-d $LockDir . $name) {
push(@locks, $name);
}
}
}
if (@locks and $Action{unlock}) {
push(@menu, ScriptLink('action=unlock', T('Unlock Wiki'), 'unlock') . ' (' . join(', ', @locks) . ')');
};
if (UserIsAdmin()) {
push(@menu, ScriptLink('action=clear', T('Clear Cache'), 'clear')) if $Action{clear};
if ($Action{editlock}) {
if (-f "$DataDir/noedit") {
push(@menu, ScriptLink('action=editlock;set=0', T('Unlock site'), 'editlock 0'));
@@ -2108,6 +2123,7 @@ sub DoAdminPage {
Ts('Lock %s', $title), 'pagelock 1'));
}
}
push(@menu, ScriptLink('action=clear', T('Clear Cache'), 'clear')) if $Action{clear};
}
foreach my $sub (@MyAdminCode) {
&$sub($id, \@menu, \@rest);
@@ -2208,10 +2224,10 @@ sub GetHeader {
$result .= $q->start_div({-class=>'header'});
if (not $embed and $LogoUrl) {
my $url = $IndexHash{$LogoUrl} ? GetDownloadLink($LogoUrl, 2) : $LogoUrl;
$result .= ScriptLink(UrlEncode($HomePage),
$q->img({-src=>$url, -alt=>$alt, -class=>'logo'}), 'logo');
$result .= ScriptLink(UrlEncode($HomePage), $q->img({-src=>$url, -alt=>$alt, -class=>'logo'}), 'logo');
}
if (GetParam('toplinkbar', $TopLinkBar)) {
$result .= $q->start_div({-class=>'menu'});
if (GetParam('toplinkbar', $TopLinkBar) != 2) {
$result .= GetGotoBar($id);
if (%SpecialDays) {
my ($sec, $min, $hour, $mday, $mon, $year) = gmtime($Now);
@@ -2221,6 +2237,8 @@ sub GetHeader {
}
}
}
$result .= GetSearchForm() if GetParam('topsearchform', $TopSearchForm) != 2;
$result .= $q->end_div();
$result .= $q->div({-class=>'message'}, $Message) if $Message;
$result .= GetHeaderTitle($id, $title, $oldId);
return $result . $q->end_div() . $q->start_div({-class=>'wrapper'});
@@ -2235,11 +2253,17 @@ sub GetHeaderTitle {
sub GetHttpHeader {
return if $PrintedHeader;
$PrintedHeader = 1;
my ($type, $ts, $status, $encoding) = @_; # $ts is undef, a ts, or 'nocache'
my ($type, $ts, $status, $encoding) = @_;
$q->charset($type =~ m!^(text/|application/xml)! ? 'utf-8' : ''); # text/plain, text/html, application/xml: UTF-8
my %headers = (-cache_control=>($UseCache < 0 ? 'no-cache' : 'max-age=10'));
$headers{-etag} = $ts || PageEtag() if GetParam('cache', $UseCache) >= 2;
$headers{'-last-modified'} = TimeToRFC822($ts) if $ts and $ts ne 'nocache'; # RFC 2616 section 13.3.4
# Set $ts when serving raw content that cannot be modified by cookie parameters; or 'nocache'; or undef. If you
# provide a $ts, the last-modiefied header generated will by used by HTTP/1.0 clients. If you provide no $ts, the etag
# header generated will be used by HTTP/1.1 clients. In this situation, cookie parameters can influence the look of
# the page and we cannot rely on $LastUpdate. HTTP/1.0 clients will ignore etags. See RFC 2616 section 13.3.4.
if (GetParam('cache', $UseCache) >= 2 and $ts ne 'nocache') {
$headers{'-last-modified'} = TimeToRFC822($ts) if $ts;
$headers{-etag} = PageEtag();
}
$headers{-type} = GetParam('mime-type', $type);
$headers{-status} = $status if $status;
$headers{-Content_Encoding} = $encoding if $encoding;
@@ -2355,9 +2379,11 @@ sub PrintFooter {
return;
}
print GetCommentForm($id, $rev, $comment),
$q->start_div({-class=>'wrapper close'}), $q->end_div(), $q->end_div(),
$q->start_div({-class=>'footer'}), $q->hr(), GetGotoBar($id),
GetFooterLinks($id, $rev), GetFooterTimestamp($id, $rev), GetSearchForm();
$q->start_div({-class=>'wrapper close'}), $q->end_div(), $q->end_div();
print $q->start_div({-class=>'footer'}), $q->hr();
print GetGotoBar($id) if GetParam('toplinkbar', $TopLinkBar) != 1;
print GetFooterLinks($id, $rev), GetFooterTimestamp($id, $rev);
print GetSearchForm() if GetParam('topsearchform', $TopSearchForm) != 1;
if ($DataDir =~ m|/tmp/|) {
print $q->p($q->strong(T('Warning') . ': ')
. Ts('Database is stored in temporary directory %s', $DataDir));
@@ -2376,10 +2402,10 @@ sub PrintFooter {
sub GetFooterTimestamp {
my ($id, $rev) = @_;
if ($id and $rev ne 'history' and $rev ne 'edit' and $Page{revision}) {
my @elements = ($q->br(), ($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($Page{ts}),
my @elements = (($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($Page{ts}),
Ts('by %s', GetAuthorLink($Page{host}, $Page{username})));
push(@elements, ScriptLinkDiff(2, $id, T('(diff)'), $rev)) if $UseDiff and $Page{revision} > 1;
return $q->span({-class=>'time'}, @elements);
return $q->div({-class=>'time'}, @elements);
}
return '';
}
@@ -2416,7 +2442,7 @@ sub GetFooterLinks {
$action .= ';id=' . UrlEncode($id) if $id;
push(@elements, ScriptLink($action, T('Administration'), 'admin'));
}
return @elements ? $q->span({-class=>'edit bar'}, $q->br(), @elements) : '';
return @elements ? $q->div({-class=>'edit bar'}, @elements) : '';
}
sub GetCommentForm {
@@ -2452,21 +2478,24 @@ sub GetFormStart {
}
sub GetSearchForm {
my $form = $q->label({-for=>'search'}, T('Search:')) . ' '
. $q->textfield(-name=>'search', -id=>'search', -size=>20,
-accesskey=>T('f')) . ' ';
my $html = GetFormStart(undef, 'get', 'search') . $q->start_p;
$html .= $q->label({-for=>'search'}, T('Search:')) . ' '
. $q->textfield(-name=>'search', -id=>'search', -size=>20, -accesskey=>T('f')) . ' ';
if ($ReplaceForm) {
$form .= $q->label({-for=>'replace'}, T('Replace:')) . ' '
. $q->textfield(-name=>'replace', -id=>'replace', -size=>20) . ' '
$html .= $q->label({-for=>'replace'}, T('Replace:')) . ' '
. $q->textfield(-name=>'replace', -id=>'replace', -size=>20) . ' '
. $q->checkbox(-name=>'delete', -label=>T('Delete')) . ' ';
}
if (%Languages) {
$form .= $q->label({-for=>'searchlang'}, T('Language:')) . ' '
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>10,
-default=>GetParam('lang', '')) . ' ';
if (GetParam('matchingpages', $MatchingPages)) {
$html .= $q->label({-for=>'matchingpage'}, T('Filter:')) . ' '
. $q->textfield(-name=>'match', -id=>'matchingpage', -size=>20) . ' ';
}
return GetFormStart(undef, 'get', 'search')
. $q->p($form . $q->submit('dosearch', T('Go!'))) . $q->end_form;
if (%Languages) {
$html .= $q->label({-for=>'searchlang'}, T('Language:')) . ' '
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>10, -default=>GetParam('lang', '')) . ' ';
}
$html .= $q->submit('dosearch', T('Go!')) . $q->end_p . $q->end_form;
return $html;
}
sub GetValidatorLink {
@@ -2628,22 +2657,14 @@ sub DiffAddPrefix {
return $q->div({-class=>$class}, $q->p(join($q->br(), @lines)));
}
sub ParseData { # called a lot during search, so it was optimized
my $data = shift; # by eliminating non-trivial regular expressions
sub ParseData {
my $data = shift;
my %result;
my $end = index($data, ': ');
my $key = substr($data, 0, $end);
my $start = $end += 2; # skip ': '
while ($end = index($data, "\n", $end) + 1) { # include \n
next if substr($data, $end, 1) eq "\t"; # continue after \n\t
$result{$key} = substr($data, $start, $end - $start - 1); # strip last \n
$start = index($data, ': ', $end); # starting at $end begins the new key
last if $start == -1;
$key = substr($data, $end, $start - $end);
$end = $start += 2; # skip ': '
while ($data =~ /(\S+?): (.*?)(?=\n[^ \t]|\Z)/sg) {
my ($key, $value) = ($1, $2);
$value =~ s/\n\t/\n/g;
$result{$key} = $value;
}
$result{$key} .= substr($data, $end, -1); # strip last \n
$result{$_} =~ s/\n\t/\n/g foreach (keys %result);
return %result;
}
@@ -3055,24 +3076,24 @@ sub DoDownload {
OpenPage($id) if ValidIdOrDie($id);
print $q->header(-status=>'304 NOT MODIFIED') and return if FileFresh(); # FileFresh needs an OpenPage!
my ($text, $revision) = GetTextRevision(GetParam('revision', '')); # maybe revision reset!
my $ts = $Page{ts};
if (my ($type, $encoding) = TextIsFile($text)) {
my ($data) = $text =~ /^[^\n]*\n(.*)/s;
my %allowed = map {$_ => 1} @UploadTypes;
if (@UploadTypes and not $allowed{$type}) {
ReportError(Ts('Files of type %s are not allowed.', $type), '415 UNSUPPORTED MEDIA TYPE');
}
print GetHttpHeader($type, $ts, undef, $encoding);
print GetHttpHeader($type, $Page{ts}, undef, $encoding);
require MIME::Base64;
binmode(STDOUT, ":pop:raw"); # need to pop utf8 for Windows users!?
print MIME::Base64::decode($data);
} else {
print GetHttpHeader('text/plain', $ts);
print GetHttpHeader('text/plain', $Page{ts});
print $text;
}
}
sub DoPassword {
my $id = shift;
print GetHeader('', T('Password')), $q->start_div({-class=>'content password'});
print $q->p(T('Your password is saved in a cookie, if you have cookies enabled. Cookies may get lost if you connect from another machine, from another account, or using another software.'));
if (UserIsAdmin()) {
@@ -3082,17 +3103,21 @@ sub DoPassword {
} else {
print $q->p(T('You are a normal user on this site.'));
if ($AdminPass or $EditPass) {
print $q->p(T('Your password does not match any of the administrator or editor passwords.'));
print $q->p(T('Your password does not match any of the administrator or editor passwords.'));
}
}
if ($AdminPass or $EditPass) {
print GetFormStart(undef, undef, 'password'),
$q->p(GetHiddenValue('action', 'password'), T('Password:'), ' ',
$q->password_field(-name=>'pwd', -size=>20, -maxlength=>50),
$q->hidden(-name=>'id', -value=>$id),
$q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save'))), $q->end_form;
} else {
print $q->p(T('This site does not use admin or editor passwords.'));
}
if ($id) {
print $q->p(ScriptLink('action=browse;id=' . UrlEncode($id) . '&time=' . time, T('Return to ' . NormalToFree($id))));
}
print $q->end_div();
PrintFooter();
}
@@ -3275,7 +3300,7 @@ sub AllPagesList {
}
sub DoSearch {
my $string = shift;
my $string = shift || GetParam('search', '');;
return DoIndex() if $string eq '';
eval { qr/$string/ }
or $@ and ReportError(Ts('Malformed regular expression in %s', $string),
@@ -3370,6 +3395,7 @@ sub GrepFiltered { # grep is so much faster!!
push(@result, $1) if m/.*\/(.*)\.pg/ and not $found{$1};
}
close(F);
return @pages if $?;
return sort @result;
}
@@ -3481,7 +3507,13 @@ sub Replace {
next if (@languages and not grep(/$lang/, @languages));
}
$_ = $Page{text};
if (eval "s{$from}{$to}gi") { # allows use of backreferences
my $replacement = sub {
my ($o1, $o2, $o3, $o4, $o5, $o6, $o7, $o8, $o9) = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $str = $to;
$str =~ s/\$([1-9])/'$o' . $1/gee;
$str
};
if (s/$from/$replacement->()/gei) { # allows use of backreferences
push (@result, $id);
Save($id, $_, $from . ' → ' . $to, 1, ($Page{host} ne GetRemoteHost()));
}
@@ -3490,6 +3522,27 @@ sub Replace {
return @result;
}
sub SaveUploadedFile {
my ($filename, $file) = @_;
my ($name, $path, $extension) = fileparse($filename, '\..*');
$name =~ tr/ /_/;
$name =~ s/[^$FilenameWhitelist]//g;
$extension =~ tr/ /_/;
$extension =~ s/[^$FilenameWhitelist]//g;
my $curFilename = $name . $extension;
while (-e "$FileDir/$curFilename") { # keep adding random characters until we get unique filename
die 'Error: Cannot save file with such filename' if length $curFilename >= 150; # cannot find available filename after so many attempts
$name .= $AdditionalChars[rand @AdditionalChars];
$curFilename = $name . $extension;
}
CreateDir($FileDir);
open(UPLOADFILE, '>', "$FileDir/$curFilename") or die "$!";
binmode UPLOADFILE;
print UPLOADFILE while <$file>;
close UPLOADFILE;
return $curFilename;
}
sub DoPost {
my $id = FreeToNormal(shift);
UserCanEditOrDie($id);
@@ -3506,9 +3559,9 @@ sub DoPost {
}
my $comment = UnquoteHtml(GetParam('aftertext', undef));
$comment =~ s/(\r|$FS)//go;
if (defined($comment) and (not $comment or $comment eq $NewComment)) {
if (defined $comment and $comment eq '') {
ReleaseLock();
ReBrowsePage($id);
return ReBrowsePage($id);
}
if ($filename) { # upload file
my $file = $q->upload('file');
@@ -3518,11 +3571,8 @@ sub DoPost {
ReportError(T('Browser reports no file info.'), '500 INTERNAL SERVER ERROR') unless $q->uploadInfo($filename);
$type = $q->uploadInfo($filename)->{'Content-Type'};
ReportError(T('Browser reports no file type.'), '415 UNSUPPORTED MEDIA TYPE') unless $type;
local $/ = undef; # Read complete files
my $content = <$file>; # Apparently we cannot count on <$file> to always work within the eval!?
my $encoding = 'gzip' if substr($content, 0, 2) eq "\x1f\x8b";
eval { require MIME::Base64; $_ = MIME::Base64::encode($content) };
$string = "#FILE $type $encoding\n" . $_;
my $savedFile = SaveUploadedFile($filename, $file);
$string = "Uploaded file: [[File:$savedFile]]\n";
} else { # ordinary text edit
$string = AddComment($old, $comment) if $comment;
if ($comment and substr($string, 0, length($DeletedPage)) eq $DeletedPage) { # look ma, no regexp!
@@ -3604,6 +3654,7 @@ sub DoPost {
sub GetSummary {
my $text = GetParam('aftertext', '') || ($Page{revision} > 0 ? '' : GetParam('text', ''));
return '' if $text =~ /^#FILE /;
if ($SummaryDefaultLength and length($text) > $SummaryDefaultLength) {
$text = substr($text, 0, $SummaryDefaultLength);
$text =~ s/\s*\S*$/ . . ./;
@@ -3620,7 +3671,7 @@ sub AddComment {
my ($string, $comment) = @_;
$comment =~ s/\r//g; # Remove "\r"-s (0x0d) from the string
$comment =~ s/\s+$//g; # Remove whitespace at the end
if ($comment ne '' and $comment ne $NewComment) {
if ($comment ne '') {
my $author = GetParam('username', T('Anonymous'));
my $homepage = GetParam('homepage', '');
$homepage = 'http://' . $homepage if $homepage and $homepage !~ /^($UrlProtocols):/;
@@ -3873,8 +3924,7 @@ sub DoDebug {
sub DoSurgeProtection {
return unless $SurgeProtection;
my $name = GetParam('username', '');
$name = GetRemoteHost() if not $name and $SurgeProtection;
my $name = GetParam('username', GetRemoteHost());
return unless $name;
ReadRecentVisitors();
AddRecentVisitor($name);