Compare commits

...

45 Commits
2.3.1 ... 2.3.3

Author SHA1 Message Date
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
14 changed files with 1650 additions and 63 deletions

View File

@@ -1031,6 +1031,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)

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

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

@@ -0,0 +1,592 @@
@font-face {
font-family: "EB Garamond";
font-style: normal;
src: local('EB Garamond 12'), url('EBGaramond12-Regular.ttf') format("truetype");
}
@font-face {
font-family: "EB Garamond";
font-style: italic;
src: local('EB Garamond 12'), url('EBGaramond12-Italic.ttf') format("truetype");
}
@font-face {
font-family: 'EB Garamond All SC';
src: local('EB Garamond 12 All SC'), url('EBGaramond12-AllSC.ttf') format("truetype");
}
@font-face {
font-family:"Symbola";
src: local('Symbola'), url("Symbola.woff") format("woff"), url("Symbola.ttf") format("truetype");
font-weight:normal;
font-style:normal;
}
body, rss {
font-family: "EB Garamond", Symbola, serif;
font-style: normal;
font-size: 18pt;
line-height: 22pt;
margin:1em 3em;
padding:0;
}
/* headings: we can use larger sizes if we use a lighter color.
we cannot inherit the font-family because header and footer use a narrow font. */
h1, h2, h3, title {
font-family: "EB Garamond", Symbola, serif;
font-weight: normal;
line-height: 100%;
}
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 {
display:none;
}
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 */
form.tiny, form.tiny p {
display: inline;
white-space: nowrap;
}
form.tiny input {
padding: 0;
width: 10ex;
font-size: 80%;
}
/* bold = small caps */
b, strong {
font-family: "EB Garamond All SC";
font-weight: normal;
}
/* code */
pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace;
font-size: 80%;
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. */
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
line-height: 16pt;
}
div.footer form.search {
display: none;
}
div.rc li {
line-height: 110%;
}
div.rc li + li {
margin-top: 1em;
}
div.rc li strong, table.history strong, strong.description {
font-weight: inherit;
}
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
line-height: 14pt;
color: #000;
}
div.old {
background-color: #ffffaf;
}
div.new {
background-color: #cfffcf;
}
div.refer {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
line-height: 13pt;
}
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 {
background-color:#ffc;
padding-bottom:1ex;
}
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 { line-height: 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;
}
} */
@media print {
body {
font-size: 12pt;
line-height: 13pt;
color: #000;
background-color: #fff;
}
/* hide all the crap */
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone {
display:none;
}
a,
a:visited,
div.content a.near:link,
div.content a.near:visited,
div.content a.near:active {
color:inherit;
font-weight: bold;
}
div.content a.feed {
display: none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
/* no difference */
pre, code, tt {
font-size: inherit;
line-height: inherit;
}
/* no dotted underlines */
acronym, abbr {
border: none;
text-decoration: none;
}
/* headings */
h1 {
color: inherit;
margin-top: 2em;
}
h2 {
color:inherit;
margin: 1em 0;
font-variant: small-caps;
font-family: "EB Garamond All SC", "EB Garamond", Garamond, serif;
}
h3 {
font-weight:inherit;
font-style:italic;
color:inherit;
margin: 1em 0;
}
h1 a, h2 a, h3 a {
color: inherit;
}
div.journal h1 a:visited,
div.journal h2 a:visited,
div.journal h3 a:visited {
color: inherit;
}
}
/* rss */
channel * { display: block; }
channel title {
margin-top: 30pt;
}
copyright {
font-size: 14pt;
line-height: 16pt;
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;
}

272
css/light.css Normal file
View File

@@ -0,0 +1,272 @@
/* 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);
body {
font-family: "Noticia Text", Times, serif;
font-size: 14pt;
color: #000;
background-color: #eed;
margin:1em 2em;
}
@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 */
div.browse {
min-height: 3em;
}
div.footer {
clear:both;
font-size: 90%;
}
/* 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 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; }
}

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

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

@@ -0,0 +1,198 @@
# 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 = 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 {
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) {
$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 $link = ScriptLink("action=edit-paragraph;title=$OpenPageName;around=$pos;paragraph="
. UrlEncode(UnquoteHtml($text)), $EditParagraphPencil, 'pencil');
if ($Fragment =~ s!((:?</h[1-6]>|</t[dh]></tr></table>|</pre>)<p>)$!!) {
# $Fragment .= '<!-- 1 -->';
$Fragment .= $link . $1;
} elsif ($pos and $Fragment =~ /<p>$/) {
# Do nothing: this will result in <p></p> and get eliminated.
# $Fragment .= '<!-- 2 -->';
} else {
# This is the default: add the link.
# $Fragment .= '<!-- 3 -->';
$Fragment .= $link;
}
}
}

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

View File

@@ -87,6 +87,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

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

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

@@ -0,0 +1,254 @@
# 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 => 48;
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'
. ' text=' . UrlEncode("<test30>\n") # new
. ' paragraph=' . UrlEncode("<test3>\n") # old
. ' around=1'),
'Could not identify the paragraph you were editing',
'<pre>' . QuoteHtml("<test3>\n") . '</pre>');
test_page(get_page('action=edit-paragraph title=Test'
. ' text=' . UrlEncode("<test30>\n") # new
. ' paragraph=' . UrlEncode("<test3>\n") # old
. ' around=51'), 'Status: 302');
$text =~ s/test3/test30/;
test_page(get_page('action=browse id=Test raw=1'), $text);

48
wiki.pl
View File

@@ -29,6 +29,7 @@
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';
@@ -97,7 +98,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.
@@ -214,7 +215,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.
@@ -361,6 +363,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>
@@ -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);
@@ -1453,7 +1458,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
@@ -2086,11 +2091,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 +2122,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);
@@ -2235,11 +2250,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;
@@ -3055,19 +3076,18 @@ 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;
}
}
@@ -3506,7 +3526,7 @@ 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);
}
@@ -3620,7 +3640,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):/;