forked from github/kensanata.oddmuse
Compare commits
92 Commits
use-warnin
...
2.3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
764c0ffcf1 | ||
|
|
5962745937 | ||
|
|
d380062ec6 | ||
|
|
cd8066233c | ||
|
|
6f04d2044f | ||
|
|
f41ded592b | ||
|
|
8a2c9eca9c | ||
|
|
d712a17f82 | ||
|
|
504190b752 | ||
|
|
56e515a791 | ||
|
|
36feb62052 | ||
|
|
239f15cdbc | ||
|
|
f39cfd3235 | ||
|
|
329699a6aa | ||
|
|
28965bdaa6 | ||
|
|
ed42d2dad5 | ||
|
|
45b21cbdb8 | ||
|
|
b540093c2c | ||
|
|
d164d47e24 | ||
|
|
a56b92ecb3 | ||
|
|
114d914754 | ||
|
|
875051ea84 | ||
|
|
003357acad | ||
|
|
e1c77c4ba6 | ||
|
|
88475c3e41 | ||
|
|
e80f05301d | ||
|
|
a624e78975 | ||
|
|
8cd869f0f9 | ||
|
|
b09b3f8f8e | ||
|
|
b9043ffd98 | ||
|
|
54d3dc400a | ||
|
|
3962068385 | ||
|
|
a0b74ac3c6 | ||
|
|
870d75ac64 | ||
|
|
42d8260ce4 | ||
|
|
bfda4abe54 | ||
|
|
4b0d411564 | ||
|
|
6790de2d6a | ||
|
|
2784628544 | ||
|
|
dd8c687b2b | ||
|
|
9f4ceb2d72 | ||
|
|
0f8a4fa1df | ||
|
|
3b16b58880 | ||
|
|
192a902932 | ||
|
|
aedf77cff8 | ||
|
|
728547f309 | ||
|
|
33f5484441 | ||
|
|
a225486709 | ||
|
|
3f7f9ec1eb | ||
|
|
174aac5570 | ||
|
|
954232f7c8 | ||
|
|
067658fd10 | ||
|
|
b2b2b0f6cc | ||
|
|
408d169729 | ||
|
|
0eddbd5806 | ||
|
|
210a28afd4 | ||
|
|
94a16bd463 | ||
|
|
4005e246f7 | ||
|
|
6b3cd0437f | ||
|
|
c961748b49 | ||
|
|
7327eb8e0c | ||
|
|
1b0d595945 | ||
|
|
b7e2a04bb4 | ||
|
|
848eb65ad0 | ||
|
|
7ae98f4ed9 | ||
|
|
dc792691d4 | ||
|
|
0a57a8e89b | ||
|
|
f75d415322 | ||
|
|
1654562236 | ||
|
|
ba0535f39d | ||
|
|
e5b069f70b | ||
|
|
d34b9f669b | ||
|
|
ae2061fcaf | ||
|
|
65475cf2e8 | ||
|
|
ce2e63be6b | ||
|
|
007ce8db86 | ||
|
|
5997c3ea02 | ||
|
|
6895428844 | ||
|
|
2bc2d1f927 | ||
|
|
873ce10ced | ||
|
|
3855c83a7e | ||
|
|
281736a082 | ||
|
|
63d8e24c2f | ||
|
|
c66f1a6f8e | ||
|
|
6418cab98c | ||
|
|
040d51bc93 | ||
|
|
41c3245a51 | ||
|
|
ffc2a0b12f | ||
|
|
90c632f4ab | ||
|
|
9e8def306a | ||
|
|
f4e551111a | ||
|
|
1ead545561 |
@@ -112,7 +112,7 @@ USERNAME, your optional username to provide. It defaults to
|
||||
;;; Variables
|
||||
|
||||
(defvar oddmuse-get-command
|
||||
"curl --silent %w --form action=browse --form raw=2 --form id=%t"
|
||||
"curl --silent %w --form action=browse --form raw=2 --form id='%t'"
|
||||
"Command to use for publishing pages.
|
||||
It must print the page to stdout.
|
||||
|
||||
@@ -254,6 +254,9 @@ Example:
|
||||
|
||||
((\"Alex\" ((\"Contact\" . \"58\"))))")
|
||||
|
||||
(defvar oddmuse-revision nil
|
||||
"A variable to bind dynamically when calling `oddmuse-format-command'.")
|
||||
|
||||
(defun oddmuse-revision-put (wiki page rev)
|
||||
"Store REV for WIKI and PAGE in `oddmuse-revisions'."
|
||||
(let ((w (assoc wiki oddmuse-revisions)))
|
||||
@@ -285,10 +288,10 @@ Example:
|
||||
(file-name-directory file))))
|
||||
|
||||
(defmacro with-oddmuse-file (file &rest body)
|
||||
"Bind `oddmuse-wiki' and `oddmuse-page-name' based on FILE
|
||||
and execute BODY."
|
||||
`(let ((oddmuse-page-name (oddmuse-page-name ,file))
|
||||
(oddmuse-wiki (oddmuse-wiki ,file)))
|
||||
"Bind `wiki' and `pagename' based on FILE and execute BODY."
|
||||
(declare (debug (symbolp &rest form)))
|
||||
`(let ((pagename (oddmuse-page-name ,file))
|
||||
(wiki (oddmuse-wiki ,file)))
|
||||
,@body))
|
||||
|
||||
(put 'with-oddmuse-file 'lisp-indent-function 1)
|
||||
@@ -407,6 +410,7 @@ It's either a [[free link]] or a WikiWord based on
|
||||
%p `oddmuse-password'
|
||||
%q `question' as provided by `oddmuse-wikis'
|
||||
%o `oddmuse-ts'
|
||||
%v `oddmuse-revision'
|
||||
%r `regexp' as provided by the user"
|
||||
(dolist (pair '(("%w" . url)
|
||||
("%t" . pagename)
|
||||
@@ -416,6 +420,7 @@ It's either a [[free link]] or a WikiWord based on
|
||||
("%p" . oddmuse-password)
|
||||
("%q" . question)
|
||||
("%o" . oddmuse-ts)
|
||||
("%v" . oddmuse-revision)
|
||||
("%r" . regexp)))
|
||||
(let* ((key (car pair))
|
||||
(sym (cdr pair))
|
||||
@@ -477,15 +482,13 @@ well."
|
||||
(message "%s using %s..." mesg command)
|
||||
(when (numberp expected-code)
|
||||
(setq expected-code (number-to-string expected-code)))
|
||||
;; If SEND-BUFFER, the resulting HTTP CODE is found in BUF, so check
|
||||
;; that, too.
|
||||
(let* ((errno (if send-buffer
|
||||
(shell-command-on-region (point-min) (point-max) command buf)
|
||||
(shell-command command buf)))
|
||||
(status (with-current-buffer buf (buffer-string))))
|
||||
(cond ((not (zerop errno))
|
||||
(error "Error %s: non-zero return value" mesg))
|
||||
((and send-buffer expected-code (not (string= expected-code status)))
|
||||
(if send-buffer
|
||||
(shell-command-on-region (point-min) (point-max) command buf)
|
||||
(shell-command command buf))
|
||||
(let ((status (with-current-buffer buf (buffer-string))))
|
||||
(cond ((and send-buffer
|
||||
expected-code
|
||||
(not (string= expected-code status)))
|
||||
(error "Error %s: HTTP Status Code %s" mesg status))
|
||||
((string-match "<title>Error</title>" status)
|
||||
(if (string-match "<h1>\\(.*\\)</h1>" status)
|
||||
@@ -901,9 +904,10 @@ node as returned by `libxml-parse-html-region' or
|
||||
(defun oddmuse-search (regexp)
|
||||
"Search the wiki for REGEXP.
|
||||
REGEXP must be a regular expression understood by the
|
||||
wiki (ie. it must use Perl syntax)."
|
||||
wiki (ie. it must use Perl syntax).
|
||||
Use a prefix argument to search a different wiki."
|
||||
(interactive "sSearch term: ")
|
||||
(let* ((wiki (or oddmuse-wiki
|
||||
(let* ((wiki (or (and (not current-prefix-arg) oddmuse-wiki)
|
||||
(completing-read "Wiki: " oddmuse-wikis nil t)))
|
||||
(name (concat "*" wiki ": search for '" regexp "'*")))
|
||||
(if (and (get-buffer name)
|
||||
@@ -921,9 +925,10 @@ wiki (ie. it must use Perl syntax)."
|
||||
(defun oddmuse-match (regexp)
|
||||
"Search the wiki for page names matching REGEXP.
|
||||
REGEXP must be a regular expression understood by the
|
||||
wiki (ie. it must use Perl syntax)."
|
||||
wiki (ie. it must use Perl syntax).
|
||||
Use a prefix argument to search a different wiki."
|
||||
(interactive "sPages matching: ")
|
||||
(let* ((wiki (or oddmuse-wiki
|
||||
(let* ((wiki (or (and (not current-prefix-arg) oddmuse-wiki)
|
||||
(completing-read "Wiki: " oddmuse-wikis nil t)))
|
||||
(name (concat "*" wiki ": matches for '" regexp "'*")))
|
||||
(if (and (get-buffer name)
|
||||
@@ -1026,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
108
contrib/oddmuse_stats
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/perl -w
|
||||
# -*- perl -*-
|
||||
|
||||
=head1 NAME
|
||||
|
||||
oddmuse-stats - Plugin to monitor Oddmuse edits
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
Set env.parent_dirs in the config file. The directories in this list
|
||||
are searched for data directories containing rc.log files. No
|
||||
whitespace in the directory names, sorry.
|
||||
|
||||
Example:
|
||||
|
||||
[oddmuse_stats]
|
||||
user www-data
|
||||
env.parent_dirs /home/alex /home/alex/campaignwiki
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Original Author: Alex Schroeder
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
GPLv3
|
||||
|
||||
=head1 MAGIC MARKERS
|
||||
|
||||
#%# family=auto
|
||||
#%# capabilities=autoconf
|
||||
|
||||
=cut
|
||||
|
||||
use Munin::Plugin;
|
||||
use File::Basename;
|
||||
|
||||
# The wiki directories may not contain any spaces.
|
||||
# Use the config file to set the environment variable!
|
||||
my @parent_dirs = ();
|
||||
my %logfiles = ();
|
||||
my %names = ();
|
||||
my $debug = $ENV{MUNIN_DEBUG};
|
||||
|
||||
if ($ENV{'parent_dirs'}) {
|
||||
@parent_dirs = split(/ /, $ENV{'parent_dirs'});
|
||||
} else {
|
||||
die "The parent_dirs environment variable must be set.\n";
|
||||
}
|
||||
|
||||
for my $parent_dir (@parent_dirs) {
|
||||
warn "opening $parent_dir\n" if $debug;
|
||||
if (opendir(my $dh, $parent_dir)) {
|
||||
while(readdir $dh) {
|
||||
next if $_ eq '.' or $_ eq '..';
|
||||
if (-r "$parent_dir/$_/rc.log") {
|
||||
my $basename = basename($_);
|
||||
$names{clean_fieldname($basename)}
|
||||
= $basename;
|
||||
$logfiles{clean_fieldname($basename)}
|
||||
= "$parent_dir/$_/rc.log";
|
||||
} else {
|
||||
warn "discarding $_\n" if $debug;
|
||||
}
|
||||
}
|
||||
closedir $dh;
|
||||
}
|
||||
}
|
||||
|
||||
my $yesterday = time() - 86400;
|
||||
|
||||
if ($ARGV[0]) {
|
||||
if ($ARGV[0] eq 'autoconf') {
|
||||
if (keys %logfiles) {
|
||||
print "yes\n";
|
||||
exit 0;
|
||||
} else {
|
||||
print "no (no logfiles found in " . join(", ", @parent_dirs) . ")\n";
|
||||
exit 0;
|
||||
}
|
||||
} elsif ($ARGV[0] eq 'config') {
|
||||
print "graph_title Oddmuse Wikis\n";
|
||||
print "graph_category wikis\n";
|
||||
print "graph_info This graph shows how many edits the wiki had in the last 24h.\n";
|
||||
print "graph_vlabel edits/day\n";
|
||||
print "graph_order";
|
||||
for my $wiki (sort keys %logfiles) {
|
||||
print " $wiki";
|
||||
};
|
||||
print "\n";
|
||||
for my $wiki (sort keys %logfiles) {
|
||||
my $name = $names{$wiki};
|
||||
print "$wiki.label $name\n";
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
|
||||
for my $wiki (sort keys %logfiles) {
|
||||
open (my $fh, '<', $logfiles{$wiki})
|
||||
or die "cannot open " . $logfiles{$wiki} . ": $!";
|
||||
my $value = 0;
|
||||
while (<$fh>) {
|
||||
my ($ts) = split(/\x1e/);
|
||||
$value++ if $ts and $ts >= $yesterday;
|
||||
}
|
||||
print "$wiki.value $value\n";
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
(add-to-list 'vc-handled-backends 'oddmuse)
|
||||
|
||||
(require 'oddmuse)
|
||||
(require 'oddmuse-curl)
|
||||
(require 'diff)
|
||||
|
||||
(defun vc-oddmuse-revision-granularity () 'file)
|
||||
@@ -94,14 +94,19 @@ See `oddmuse-format-command' for the formatting options.")
|
||||
(error "This is not supported."))
|
||||
|
||||
(defvar vc-oddmuse-get-revision-command
|
||||
"curl --silent %w\"?action=browse;id=%t;revision=%o;raw=1\""
|
||||
(concat "curl --silent"
|
||||
" --form action=browse"
|
||||
" --form id=%t"
|
||||
" --form revision=%v"
|
||||
" --form raw=1"
|
||||
" '%w'")
|
||||
"Command to use to get older revisions of a page.
|
||||
It must print the page to stdout.
|
||||
|
||||
%? '?' character
|
||||
%w URL of the wiki as provided by `oddmuse-wikis'
|
||||
%t Page title as provided by `oddmuse-page-name'
|
||||
%o Revision to retrieve as provided by `oddmuse-revision'")
|
||||
%v Revision to retrieve as provided by `oddmuse-revision'")
|
||||
|
||||
(defun oddmuse-revision-filename (rev)
|
||||
"Return filename for revision REV.
|
||||
@@ -117,14 +122,17 @@ This uses `oddmuse-directory', `oddmuse-wiki' and
|
||||
(setq buffer (or buffer (get-buffer-create "*vc-diff*")))
|
||||
(dolist (file files)
|
||||
(with-oddmuse-file file
|
||||
(setq rev1 (or rev1 (oddmuse-get-latest-revision)))
|
||||
(setq rev1 (or rev1 (oddmuse-get-latest-revision wiki pagename)))
|
||||
(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 (oddmuse-format-command
|
||||
vc-oddmuse-get-revision-command))
|
||||
(filename (oddmuse-revision-filename rev)))
|
||||
(with-temp-buffer
|
||||
(oddmuse-run (concat "Downloading revision " rev) command)
|
||||
(oddmuse-run
|
||||
(concat "Downloading revision " rev)
|
||||
command wiki)
|
||||
(write-file filename)))))
|
||||
(diff-no-select
|
||||
(if rev1 (oddmuse-revision-filename rev1) file)
|
||||
@@ -156,6 +164,6 @@ used as a check-in comment."
|
||||
(buf (get-buffer-create " *oddmuse-response*")))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(oddmuse-run "Posting" command nil nil buf t 302))))))
|
||||
(oddmuse-run "Posting" command wiki pagename buf t 302))))))
|
||||
|
||||
(provide 'vc-oddmuse)
|
||||
|
||||
@@ -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
592
css/alex-2014.css
Normal 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
272
css/light.css
Normal 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; }
|
||||
}
|
||||
@@ -197,9 +197,17 @@ hr {
|
||||
div.footer hr {
|
||||
height:4px;
|
||||
margin: 2em 0 1ex 0;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
div.comment {
|
||||
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;
|
||||
}
|
||||
@@ -210,12 +218,13 @@ pre {
|
||||
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%;
|
||||
};
|
||||
|
||||
div.footer hr {
|
||||
clear:both;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2004, 2006, 2008 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2004, 2006, 2008, 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
|
||||
@@ -140,11 +140,11 @@ sub DoAtomSave {
|
||||
my $title = $entry->title();
|
||||
my $author = $entry->author();
|
||||
SetParam('username', $author->name) if $author; # Used in Save()
|
||||
my $id = FreeToNormal($title) if ValidIdOrDie($title);
|
||||
my $id = FreeToNormal($title);
|
||||
UserCanEditOrDie($id);
|
||||
$oldid = $id unless $oldid;
|
||||
ValidIdOrDie($oldid);
|
||||
my $summary = $entry->summary();
|
||||
ReportError(Ts('Editing not allowed for %s.', $id), '403 FORBIDDEN') unless UserCanEdit($id, 1);
|
||||
# Lock before getting old page to prevent races
|
||||
RequestLockOrError(); # fatal
|
||||
OpenPage($oldid);
|
||||
|
||||
@@ -40,8 +40,8 @@ $PrintTOCAnchor = 0;
|
||||
|
||||
%ClusterMap = ();
|
||||
|
||||
*OldDoRc = *DoRc;
|
||||
*DoRc = *ClusterMapDoRc;
|
||||
*OldPrintRcHtml = *PrintRcHtml;
|
||||
*PrintRcHtml = *ClusterMapPrintRcHtml;
|
||||
|
||||
push(@MyAdminCode, \&ClusterMapAdminRule);
|
||||
|
||||
@@ -178,7 +178,7 @@ sub CreateClusterMap {
|
||||
}
|
||||
}
|
||||
|
||||
sub ClusterMapDoRc {
|
||||
sub ClusterMapPrintRcHtml {
|
||||
my ( @options ) = @_;
|
||||
my $page = "";
|
||||
my $cluster = GetParam(rcclusteronly);
|
||||
@@ -195,7 +195,7 @@ sub ClusterMapDoRc {
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
OldDoRc(@options);
|
||||
OldPrintRcHtml(@options);
|
||||
}
|
||||
|
||||
sub PrintUnclusteredMap {
|
||||
|
||||
@@ -25,14 +25,14 @@ sub CommentDivWrapper {
|
||||
if (substr($OpenPageName, 0, length($CommentsPrefix)) eq $CommentsPrefix) {
|
||||
if (pos == 0 and not $CommentDiv) {
|
||||
$CommentDiv = 1;
|
||||
return '<div class="userComment">';
|
||||
return $q->start_div({-class=>'userComment'});
|
||||
}
|
||||
}
|
||||
if ($OpenPageName =~ /$CommentsPattern/o) {
|
||||
if ($bol and m/\G(\s*\n)*----+[ \t]*\n?/cg) {
|
||||
my $html = CloseHtmlEnvironments()
|
||||
. ($CommentDiv++ > 0 ? '</div>' : '<h2 id="commentsHeading">' . T('Comments:') . '</h2>') . '<div class="userComment">'
|
||||
. AddHtmlEnvironment('p');
|
||||
. ($CommentDiv++ > 0 ? $q->end_div() : $q->h2({-class=>'commentsHeading'}, T('Comments:'))) . $q->start_div({-class=>'userComment'})
|
||||
. AddHtmlEnvironment('p');
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,8 @@ sub CommentDivWrapper {
|
||||
sub NewCommentDivApplyRules {
|
||||
my ($blocks, $flags) = OldCommentDivApplyRules(@_);
|
||||
if ($CommentDiv) {
|
||||
print '</div>';
|
||||
$blocks .= $FS . '</div>';
|
||||
print $q->end_div();
|
||||
$blocks .= $FS . $q->end_div();
|
||||
$flags .= $FS . 0;
|
||||
$CommentDiv = 0;
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ sub CreoleRule {
|
||||
-class=> 'image outside'},
|
||||
$q->img({-src=> UnquoteHtml($1),
|
||||
-alt=> UnquoteHtml($3),
|
||||
-title=> UnquoteHtml($3),
|
||||
-class=> 'url outside'})));
|
||||
}
|
||||
# image link: [[link|{{pic}}]] and [[link|{{pic|text}}]]
|
||||
@@ -252,6 +253,7 @@ sub CreoleRule {
|
||||
ScriptLink(UrlEncode(FreeToNormal($2)),
|
||||
$q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
|
||||
-alt=> UnquoteHtml($text),
|
||||
-title=> UnquoteHtml($text),
|
||||
-class=> 'upload'}), 'image')), $text);
|
||||
}
|
||||
# image link: [[link|{{url}}]] and [[link|{{url|text}}]]
|
||||
@@ -262,6 +264,7 @@ sub CreoleRule {
|
||||
ScriptLink(UrlEncode(FreeToNormal($2)),
|
||||
$q->img({-src=> UnquoteHtml($3),
|
||||
-alt=> UnquoteHtml($text),
|
||||
-title=> UnquoteHtml($text),
|
||||
-class=> 'url outside'}), 'image')), $text);
|
||||
}
|
||||
# image link: [[url|{{pic}}]] and [[url|{{pic|text}}]]
|
||||
@@ -272,6 +275,7 @@ sub CreoleRule {
|
||||
$q->a({-href=> UnquoteHtml($2), -class=> 'image outside'},
|
||||
$q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
|
||||
-alt=> UnquoteHtml($text),
|
||||
-title=> UnquoteHtml($text),
|
||||
-class=> 'upload'}))), $text);
|
||||
}
|
||||
# image link: [[url|{{url}}]] and [[url|{{url|text}}]]
|
||||
@@ -281,6 +285,7 @@ sub CreoleRule {
|
||||
$q->a({-href=> UnquoteHtml($1), -class=> 'image outside'},
|
||||
$q->img({-src=> UnquoteHtml($2),
|
||||
-alt=> UnquoteHtml($4),
|
||||
-title=> UnquoteHtml($4),
|
||||
-class=> 'url outside'})));
|
||||
}
|
||||
# link: [[url]] and [[url|text]]
|
||||
|
||||
35
modules/div-foo.pl
Normal file
35
modules/div-foo.pl
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2014 Alex-Daniel Jakimenko <alex.jakimenko@gmail.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 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('div-foo.pl', 'Div Foo Extension');
|
||||
|
||||
push(@MyRules, \&DivFooRule);
|
||||
|
||||
sub DivFooRule {
|
||||
if (m/\G\<([\w ]+)\>\s*\n/cg) {
|
||||
return CloseHtmlEnvironment('p') . AddHtmlEnvironment('div', qq{class="$1"});
|
||||
}
|
||||
if (m/\G\<([\w ]+)\>/cg) {
|
||||
return AddHtmlEnvironment('span', qq{class="$1"});
|
||||
}
|
||||
if (m/\G\<\/\/\>/cg) {
|
||||
return CloseHtmlEnvironment('div') . (InElement('div') ? '' : AddHtmlEnvironment('p'));
|
||||
}
|
||||
if (m/\G\<\/\>/cg) {
|
||||
return CloseHtmlEnvironment('span');
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
75
modules/edit-paragraphs.js
Normal file
75
modules/edit-paragraphs.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Copyright 2014 Alex Schroeder <alex@gnu.org>
|
||||
based on http://git.savannah.gnu.org/cgit/oddmuse.git/plain/plinks.js
|
||||
for more information see http://oddmuse.org/wiki/Purple_Numbers_Extension
|
||||
based on http://simon.incutio.com/archive/2004/05/30/plinks#p-13
|
||||
Copyright 2004 Simon Willison
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function add_edit_links() {
|
||||
/* Only show edit links on ordinary pages: They either use
|
||||
* path_info or keywords in the URL, not parameters. */
|
||||
if (/=/.test(document.location.href)) {
|
||||
return;
|
||||
}
|
||||
// find all the pencil links
|
||||
var links = new Array;
|
||||
var elem = document.getElementsByTagName('a');
|
||||
for (var i = 0; i < elem.length; i++) {
|
||||
var atr = elem[i].getAttribute('class');
|
||||
if (atr != null) {
|
||||
var classes = atr.split(" ");
|
||||
for (var j = 0; j < classes.length; j++) {
|
||||
if (classes[j] == 'pencil') {
|
||||
links.push(elem[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// make them invisible
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
var func = function(thislink) {
|
||||
return function() {
|
||||
if (thislink.style.visibility == "visible") {
|
||||
thislink.style.transition = "visibility 0s 1s, opacity 1s linear";
|
||||
thislink.style.visibility = "hidden";
|
||||
thislink.style.opacity = "0";
|
||||
} else {
|
||||
thislink.style.transition = "opacity 1s linear";
|
||||
thislink.style.visibility = "visible";
|
||||
thislink.style.opacity = "1";
|
||||
};
|
||||
}
|
||||
};
|
||||
link.style.transition = "visibility 0s 1s, opacity 1s linear";
|
||||
link.style.visibility = "hidden";
|
||||
link.style.opacity = "0";
|
||||
link.parentNode.onclick = func(link);
|
||||
}
|
||||
}
|
||||
|
||||
function add_load_event(func) {
|
||||
var oldonload = window.onload;
|
||||
if (typeof window.onload != 'function') {
|
||||
window.onload = func;
|
||||
} else {
|
||||
window.onload = function() {
|
||||
oldonload();
|
||||
func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_load_event(add_edit_links);
|
||||
198
modules/edit-paragraphs.pl
Normal file
198
modules/edit-paragraphs.pl
Normal 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 = '✎';
|
||||
|
||||
# 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 ...>✎</a></p>
|
||||
# Usually this would look as follows:
|
||||
# <h2>...</h2><p></p>
|
||||
# This is eliminated in Dirty. But it won't be eliminated if we leave the link in there. What we want is this:
|
||||
# <h2>...<a ...>✎</a></h2><p></p>
|
||||
#
|
||||
# The same issue arises for other block level elements. What happens at the end of a table? Without this fix we'd
|
||||
# see something like this:
|
||||
# <table><tr><td>...</td></tr></table><p><a ...>✎</a></p>
|
||||
# What we want, I guess, is this:
|
||||
# <table><tr><td>...<a ...>✎</a></td></tr></table></p>
|
||||
|
||||
$pos = $pos || length(QuoteHtml($Page{text})); # make sure we have an around value
|
||||
my $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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ push(@MyRules, \&ImageSupportRule);
|
||||
|
||||
sub ImageSupportRule {
|
||||
my $result = undef;
|
||||
if (m!\G\[\[image((/[a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\]!gc) {
|
||||
if (m!\G\[\[image((/[a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\](\{([^}]+)\})?!gc) {
|
||||
my $oldpos = pos;
|
||||
my $class = 'image' . $1;
|
||||
my $external = $3;
|
||||
@@ -42,6 +42,7 @@ sub ImageSupportRule {
|
||||
my $link = $6 ? substr($6, 1) : '';
|
||||
my $caption = $7 ? substr($7, 1) : '';
|
||||
my $reference = $8 ? substr($8, 1) : '';
|
||||
my $comments = $10;
|
||||
my $id = FreeToNormal($name);
|
||||
$class =~ s!/! !g;
|
||||
my $linkclass = $class;
|
||||
@@ -66,6 +67,16 @@ sub ImageSupportRule {
|
||||
if ($found) {
|
||||
$result = $q->img({-src=>$src, -alt=>$alt, -title=>$alt, -class=>'upload'});
|
||||
$result = $q->a({-href=>$link, -class=>$linkclass}, $result);
|
||||
if ($comments) {
|
||||
for (split '\n', $comments) {
|
||||
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 = CloseHtmlEnvironments() . $q->div({-class=>"imageholder", -style=>"position: relative"}, $result);
|
||||
}
|
||||
} else {
|
||||
$result = GetDownloadLink($src, 1, undef, $alt);
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ sub JoinerDoRegister {
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
|
||||
print $q->table($table);
|
||||
print JoinerGetQuestion();
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
@@ -530,7 +530,7 @@ sub JoinerDoLogin {
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
|
||||
print $q->table($table);
|
||||
print JoinerGetQuestion();
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->start_p();
|
||||
print ScriptLink('action=joiner_forgot_password', T('Forgot your password?'));
|
||||
@@ -705,7 +705,7 @@ sub JoinerDoChangePassword {
|
||||
$q->td($q->password_field(-name=>'joiner_repeat_new_password', -id=>'joiner_repeat_new_password')));
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
|
||||
print $q->table($table);
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
@@ -790,7 +790,7 @@ sub JoinerDoForgotPassword {
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
|
||||
print $q->table($table);
|
||||
print JoinerGetQuestion();
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
@@ -891,7 +891,7 @@ sub JoinerDoChangeEmail {
|
||||
$q->td($q->password_field(-name=>'joiner_password', -id=>'joiner_password')));
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
|
||||
print $q->table($table);
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
@@ -1082,7 +1082,7 @@ sub JoinerDoBan {
|
||||
$q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Ban', -value=>T('Ban'))));
|
||||
print $q->table($table);
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->start_p();
|
||||
print T('Enter username of the account to unban:');
|
||||
@@ -1096,7 +1096,7 @@ sub JoinerDoBan {
|
||||
$q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
|
||||
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Unban', -value=>T('Unban'))));
|
||||
print $q->table($table);
|
||||
print $q->endform;
|
||||
print $q->end_form;
|
||||
|
||||
print $q->end_div();
|
||||
PrintFooter();
|
||||
|
||||
@@ -247,7 +247,7 @@ sub DoMailSubscriptions {
|
||||
print $q->p(ScriptLink('action=subscriptions;mail=', T('Change email address'),
|
||||
'change subscriptions'));
|
||||
}
|
||||
print $q->endform(), $q->end_div();
|
||||
print $q->end_form(), $q->end_div();
|
||||
PrintFooter();
|
||||
}
|
||||
|
||||
|
||||
137
modules/module-bisect.pl
Normal file
137
modules/module-bisect.pl
Normal file
@@ -0,0 +1,137 @@
|
||||
# Copyright (C) 2014 Alex-Daniel Jakimenko <alex.jakimenko@gmail.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 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 File::Basename;
|
||||
use File::Copy;
|
||||
|
||||
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'), '', '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 {
|
||||
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 = '';
|
||||
BisectEnableAll();
|
||||
my @files = bsd_glob("$ModuleDir/*.p[ml]");
|
||||
for (my $i = @files - 1; $i >= 0; $i--) { # handle user choices
|
||||
if (GetParam("m$i") eq 'on') {
|
||||
$parameterHandover .= GetHiddenValue("m$i", GetParam("m$i"));
|
||||
splice @files, $i, 1;
|
||||
} elsif (GetParam("m$i") eq 'off') {
|
||||
$parameterHandover .= GetHiddenValue("m$i", GetParam("m$i"));
|
||||
move($files[$i], $files[$i] . '.disabled');
|
||||
splice @files, $i, 1;
|
||||
}
|
||||
}
|
||||
my $start = GetParam('start', 1) - 1; # $start and $end are indexes
|
||||
my $end = GetParam('end', @files * 2) - 1;
|
||||
if ($end - $start <= 1) {
|
||||
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'));
|
||||
print $q->end_form();
|
||||
return;
|
||||
}
|
||||
print T('Module count (only testable modules): '), $q->strong(scalar @files), $q->br();
|
||||
print $q->br(), T('Current module statuses:'), $q->br();
|
||||
my $halfsize = ($end - $start + 1) / 2.0; # + 1 because it is count
|
||||
$end -= int($halfsize) unless $isGood;
|
||||
$start += int($halfsize + 0.51) if $isGood; # ceil
|
||||
$halfsize = ($end - $start + 1) / 2.0;
|
||||
for (my $i = 0; $i < @files; $i++) {
|
||||
if ($i >= $start and $i <= $end - int($halfsize)) {
|
||||
print $q->strong('> + '), (fileparse($files[$i]))[0], $q->br();
|
||||
} elsif ($i >= $start and $i <= $end) {
|
||||
print $q->strong('> - '), (fileparse($files[$i]))[0], $q->br();
|
||||
move($files[$i], $files[$i] . '.disabled');
|
||||
} else {
|
||||
print $q->strong('- '), (fileparse($files[$i]))[0], $q->br();
|
||||
move($files[$i], $files[$i] . '.disabled');
|
||||
}
|
||||
}
|
||||
print GetFormStart(undef, 'get', 'bisect');
|
||||
print GetHiddenValue('action', 'bisect');
|
||||
print GetHiddenValue('start', $start + 1);
|
||||
print GetHiddenValue('end', $end + 1);
|
||||
print $parameterHandover;
|
||||
print $q->submit(-name=>'good', -value=>T('Good')), ' ';
|
||||
print $q->submit(-name=>'bad', -value=>T('Bad')), ' ';
|
||||
print $q->submit(-name=>'stop', -value=>T('Stop'));
|
||||
print $q->end_form();
|
||||
}
|
||||
|
||||
sub BisectEnableAll {
|
||||
for (bsd_glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
|
||||
my $oldName = $_;
|
||||
s/\.disabled$//;
|
||||
print Ts('Enabling %s', (fileparse($_))[0]), '...', $q->br() if $_[0];
|
||||
move($oldName, $_);
|
||||
}
|
||||
}
|
||||
@@ -31,54 +31,78 @@ sub ModuleUpdaterMenu {
|
||||
}
|
||||
|
||||
sub ModuleUpdaterAction {
|
||||
return unless UserIsAdminOrError();
|
||||
UserIsAdminOrError();
|
||||
RequestLockOrError();
|
||||
print GetHeader('', T('Module Updater'), '');
|
||||
for (bsd_glob("$ModuleDir/*.p[ml]")) {
|
||||
my $curModule = fileparse($_);
|
||||
ProcessModule($curModule);
|
||||
print GetHeader('', T('Module Updater'), '', 'nocache');
|
||||
|
||||
if (GetParam('ok')) {
|
||||
ModuleUpdaterApply();
|
||||
} else {
|
||||
unlink bsd_glob("$TempDir/*.p[ml]"); # XXX is it correct to use $TempDir for such stuff? What if something else puts .pm files there?
|
||||
for (bsd_glob("$ModuleDir/*.p[ml]")) {
|
||||
my $curModule = fileparse($_);
|
||||
ProcessModule($curModule);
|
||||
}
|
||||
|
||||
print $q->br();
|
||||
print GetFormStart(undef, 'get');
|
||||
print GetHiddenValue('action', 'updatemodules');
|
||||
print $q->submit(-name=>'ok', -value=>T('Looks good. Update modules now!'));
|
||||
print $q->end_form();
|
||||
}
|
||||
print '<strong>Done!</strong>';
|
||||
|
||||
PrintFooter();
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
sub ModuleUpdaterApply {
|
||||
for (bsd_glob("$TempDir/*.p[ml]")) {
|
||||
my $moduleName = fileparse($_);
|
||||
if (move($_, "$ModuleDir/$moduleName")) {
|
||||
print $q->strong("Module $moduleName updated successfully!"), $q->br();
|
||||
} else {
|
||||
print $q->strong("Unable to replace module $moduleName: $!"), $q->br();
|
||||
}
|
||||
}
|
||||
unlink bsd_glob("$TempDir/*.p[ml]"); # XXX same as above
|
||||
print $q->br(), $q->strong('Done!');
|
||||
}
|
||||
|
||||
sub ProcessModule() {
|
||||
my $module = shift;
|
||||
CreateDir($TempDir);
|
||||
print "<hr/>";
|
||||
print "<strong>Updating $module ...</strong><br/>";
|
||||
if (system('wget', '-O', "$TempDir/newmodule", '--', "$OddmuseModulesUrl/$module") != 0) {
|
||||
if ($? >> 8 == 8) { # wget usually returns 8 if server response is NOT FOUND
|
||||
# TODO maybe there is any better way to do this?
|
||||
print '<strong>There is no such module in git repository. If this is your own module, please contribute it to Oddmuse! If it is not, then it was probably removed.</strong><br/>';
|
||||
return;
|
||||
}
|
||||
print 'There was an error downloading this module.<br/>';
|
||||
print $q->hr();
|
||||
print $q->strong("Diffing $module ..."), $q->br();
|
||||
my $moduleData = GetRaw("$OddmuseModulesUrl/$module");
|
||||
if (not $moduleData) {
|
||||
print $q->strong('There was an error downloading this module.'
|
||||
. ' If this is your own module, please contribute it to Oddmuse!'), $q->br();
|
||||
return;
|
||||
}
|
||||
my $diff = DoModuleDiff("$ModuleDir/$module", "$TempDir/newmodule");
|
||||
open my $fh, ">", "$TempDir/$module" or die("Could not open file. $!");
|
||||
print $fh $moduleData;
|
||||
close $fh;
|
||||
|
||||
my $diff = DoModuleDiff("$ModuleDir/$module", "$TempDir/$module");
|
||||
if (not $diff) {
|
||||
print '<strong>This module is up to date, there is no need to update it.</strong><br/>';
|
||||
print $q->strong('This module is up to date, there is no need to update it.'), $q->br();
|
||||
unlink "$TempDir/$module";
|
||||
return;
|
||||
}
|
||||
print '<strong>There is a newer version of this module. Here is a diff:</strong><br/>';
|
||||
print $q->strong('There is a newer version of this module. Here is a diff:'), $q->br();
|
||||
|
||||
$diff = QuoteHtml($diff);
|
||||
$diff =~ tr/\r//d; # TODO is this required? # probably not
|
||||
$diff =~ tr/\r//d; # TODO is this required? # probably not # but maybe it is there to fix problems with dos newlines?
|
||||
for (split /\n/, $diff) {
|
||||
my ($type) = /(.)/;
|
||||
if ($type eq '+') {
|
||||
print '<span class="updaternew">';
|
||||
} elsif ($type eq '-') {
|
||||
print '<span class="updaterold">';
|
||||
if ($type =~ /[+-]/) {
|
||||
my $class = $type eq '+' ? 'updaternew' : 'updaterold';
|
||||
print $q->span({-class => $class}, $q->code($_));
|
||||
} else {
|
||||
print $q->span($q->code($_));
|
||||
}
|
||||
print '<code>' . $_ . '</code>';
|
||||
print '</span>' if $type =~ /[+-]/;
|
||||
print '<br/>';
|
||||
print $q->br();
|
||||
}
|
||||
move("$TempDir/newmodule", "$ModuleDir/$module") or print "<strong>Unable to replace module: $! </strong><br/>";
|
||||
print '<strong>Module updated successfully!</strong><br/>';
|
||||
}
|
||||
|
||||
sub DoModuleDiff {
|
||||
|
||||
@@ -167,7 +167,8 @@ anchors.
|
||||
*DeletePage = *NewPermanentAnchorsDeletePage;
|
||||
|
||||
sub NewPermanentAnchorsDeletePage {
|
||||
OldPermanentAnchorsDeletePage(@_);
|
||||
my $status = OldPermanentAnchorsDeletePage(@_);
|
||||
return $status if $status; # this would be the error message
|
||||
DeletePermanentAnchors(@_); # the only parameter is $id
|
||||
}
|
||||
|
||||
|
||||
@@ -546,7 +546,7 @@ sub NewSearchFreePrintFooter {
|
||||
$q->p(GetHiddenValue('id', $id), GetHiddenValue('action', 'retag'),
|
||||
T('Tags:'), $q->br(), GetTextArea('tags', join(' ', @tags), 2),
|
||||
$q->br(), $q->submit(-name=>'Save', -value=>T('Save'))),
|
||||
$q->endform());
|
||||
$q->end_form());
|
||||
} elsif ($id and @tags) {
|
||||
print $q->div({-class=>'tags'},
|
||||
$q->p(T('Tags:'), map { $_ = "\[\[tag:$_\]\]";
|
||||
|
||||
@@ -368,7 +368,7 @@ sub StaticGetCommentForm {
|
||||
-override=>1, -size=>40, -maxlength=>100)),
|
||||
$q->p($q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save')), ' ',
|
||||
$q->submit(-name=>'Preview', -value=>T('Preview'))),
|
||||
$q->endform());
|
||||
$q->end_form());
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ sub ThreadAdd {
|
||||
. '</td></tr></table>'
|
||||
. '<p>'
|
||||
. $q->p($q->submit(-name=>'Save', -value=>T('Save')))
|
||||
. $q->endform());
|
||||
. $q->end_form());
|
||||
print $q->end_html;
|
||||
} else {
|
||||
my ($page, $thread) = ThreadExtract($id);
|
||||
|
||||
@@ -75,6 +75,6 @@ sub DoTZ {
|
||||
-values=>\@names,
|
||||
-default=>GetParam('time', $defaultTZ)),
|
||||
$q->submit('dotz', T('Set')));
|
||||
print $q->endform . $q->end_div();
|
||||
print $q->end_form . $q->end_div();
|
||||
PrintFooter();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ sub GetTocHtml {
|
||||
# By Usemod convention, all headers begin with depth 2. This algorithm,
|
||||
# however, expects headers to begin with depth 1. Thus, to "streamline"
|
||||
# things, we transform it appropriately. ;-)
|
||||
if (defined &UsemodRule) { $header_depth--; }
|
||||
$header_depth-- if defined &UsemodRule or defined &CreoleRule;
|
||||
|
||||
# If this is the first header and if this header's depth is deeper than 1,
|
||||
# we manually clamp this header's depth to 1 so as to ensure the first list
|
||||
@@ -280,7 +280,7 @@ sub GetTocHtml {
|
||||
|
||||
# Close ordered lists and list items for prior headings deeper than this
|
||||
# heading's depth.
|
||||
while ($list_depth > $header_depth) {
|
||||
while ($list_depth > $header_depth and $list_depth != 1) {
|
||||
$list_depth--;
|
||||
$toc_html .= '</li></ol>';
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ sub DoTranslationLink {
|
||||
$q->input({-type=>'hidden', -name=>'missing',
|
||||
-value=>GetParam('missing', '')}),
|
||||
$q->submit('dotranslate', T('Go!')));
|
||||
print $q->endform, $q->end_div();
|
||||
print $q->end_form, $q->end_div();
|
||||
PrintFooter();
|
||||
}
|
||||
}
|
||||
|
||||
0
t/aggregate.t
Executable file → Normal file
0
t/aggregate.t
Executable file → Normal file
0
t/anchors.t
Executable file → Normal file
0
t/anchors.t
Executable file → Normal file
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2013 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2013-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
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 3;
|
||||
use Test::More tests => 4;
|
||||
|
||||
clear_pages();
|
||||
|
||||
@@ -28,7 +28,8 @@ add_module('ban-quick-editors.pl');
|
||||
get_page('Test');
|
||||
test_page(update_page('Test', 'cannot edit'),
|
||||
'This page is empty');
|
||||
test_page($redirect, 'Editing not allowed');
|
||||
test_page($redirect, 'Editing not allowed',
|
||||
'fast editing spam bot');
|
||||
sleep 5;
|
||||
test_page(update_page('Test', 'edit succeeded'),
|
||||
'edit succeeded');
|
||||
|
||||
46
t/cache.t
Executable file → Normal file
46
t/cache.t
Executable file → Normal 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.';
|
||||
|
||||
|
||||
0
t/calendar.t
Executable file → Normal file
0
t/calendar.t
Executable file → Normal file
0
t/clusters.t
Executable file → Normal file
0
t/clusters.t
Executable file → Normal file
0
t/comments.t
Executable file → Normal file
0
t/comments.t
Executable file → Normal file
0
t/config-page.t
Executable file → Normal file
0
t/config-page.t
Executable file → Normal file
0
t/conflict.t
Executable file → Normal file
0
t/conflict.t
Executable file → Normal file
15
t/cookie.t
15
t/cookie.t
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2009, 2012 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2009–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
|
||||
@@ -46,7 +46,6 @@ AppendStringToFile($ConfigFile, "\$WikiLinks = 0;\n");
|
||||
test_page(get_page('action=browse id=HomePage username=Alex'),
|
||||
'username=Alex');
|
||||
|
||||
|
||||
SKIP: {
|
||||
|
||||
eval { require LWP::UserAgent; };
|
||||
@@ -68,21 +67,23 @@ SKIP: {
|
||||
# Set the cookie
|
||||
$response = $ua->get("$wiki?action=debug;pwd=foo");
|
||||
ok($response->is_success, 'request the page');
|
||||
test_page($ua->cookie_jar->as_string, 'Set-Cookie.*: Wiki=pwd%251efoo');
|
||||
$ua->cookie_jar->as_string =~ /Set-Cookie.*: ([^=]+)=pwd%251efoo/;
|
||||
my $cookie = $1;
|
||||
ok($cookie, 'pwd was set in the cookie');
|
||||
test_page_negative($response->content, 'pwd');
|
||||
|
||||
# Change the cookie
|
||||
$response = $ua->get("$wiki?action=debug;pwd=test");
|
||||
test_page($ua->cookie_jar->as_string, 'Set-Cookie.*: Wiki=pwd%251etest');
|
||||
test_page($ua->cookie_jar->as_string, qq{Set-Cookie.*: $cookie=pwd%251etest});
|
||||
|
||||
# Delete the cookie
|
||||
$response = $ua->get("$wiki?action=debug;pwd=");
|
||||
test_page($ua->cookie_jar->as_string, 'Set-Cookie.*: Wiki=""');
|
||||
test_page($ua->cookie_jar->as_string, qq{Set-Cookie.*: $cookie=""});
|
||||
|
||||
# Encoding issues
|
||||
$response = $ua->get("$wiki?action=rc;username=Alex\%20Schr\%C3\%B6der");
|
||||
test_page($ua->cookie_jar->as_string,
|
||||
'Set-Cookie.*: Wiki=username%251eAlex%20Schr%C3%B6der');
|
||||
qq{Set-Cookie.*: $cookie=username%251eAlex%20Schr%C3%B6der});
|
||||
test_page($response->decoded_content,
|
||||
'Cookie: Wiki, username=Alex Schröder');
|
||||
qq{Cookie: $cookie, username=Alex Schröder});
|
||||
};
|
||||
|
||||
10
t/creole.t
10
t/creole.t
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env perl
|
||||
# Copyright (C) 2006-2013 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2006-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
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 108;
|
||||
use Test::More tests => 111;
|
||||
clear_pages();
|
||||
|
||||
add_module('creole.pl');
|
||||
@@ -217,6 +217,12 @@ http://www.wikicreole.org/.
|
||||
//a[@class="url http outside"][@href="http://www.wikicreole.org/"][em[text()="Visit the WikiCreole website"]]
|
||||
[[http://www.wikicreole.org/ | Visit the WikiCreole website]]
|
||||
//a[@class="url http outside"][@href="http://www.wikicreole.org/"][text()="Visit the WikiCreole website"]
|
||||
[[foo bar]]
|
||||
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
|
||||
[[foo_bar]]
|
||||
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
|
||||
[[foo bar|text]]
|
||||
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="|text]]"]]
|
||||
[[link]]
|
||||
//a[text()="link"]
|
||||
[[link|Go to my page]]
|
||||
|
||||
0
t/creoleaddition.t
Normal file → Executable file
0
t/creoleaddition.t
Normal file → Executable file
1
t/crossbar.t
Normal file → Executable file
1
t/crossbar.t
Normal file → Executable file
@@ -1,4 +1,3 @@
|
||||
|
||||
#!/usr/bin/env perl
|
||||
# ====================[ crossbar.t ]====================
|
||||
|
||||
|
||||
0
t/crumbs.t
Executable file → Normal file
0
t/crumbs.t
Executable file → Normal file
6
t/default-links.t
Executable file → Normal file
6
t/default-links.t
Executable file → Normal file
@@ -15,7 +15,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 61;
|
||||
use Test::More tests => 63;
|
||||
|
||||
clear_pages();
|
||||
|
||||
@@ -34,6 +34,10 @@ xpath_run_tests(split('\n',<<'EOT'));
|
||||
//div[text()="[[0]]"]
|
||||
[[0a]]
|
||||
//a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=0a"][text()="?"]
|
||||
[[foo bar]]
|
||||
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
|
||||
[[foo_bar]]
|
||||
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
|
||||
file://home/foo/tutorial.pdf
|
||||
//a[@class="url file"][@href="file://home/foo/tutorial.pdf"][text()="file://home/foo/tutorial.pdf"]
|
||||
file:///home/foo/tutorial.pdf
|
||||
|
||||
0
t/download.t
Executable file → Normal file
0
t/download.t
Executable file → Normal file
254
t/edit-paragraphs.t
Normal file
254
t/edit-paragraphs.t
Normal 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'), '<');
|
||||
# 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);
|
||||
7
t/git.t
7
t/git.t
@@ -15,7 +15,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 15;
|
||||
use Test::More tests => 16;
|
||||
use utf8; # test data is UTF-8 and it matters
|
||||
|
||||
SKIP: {
|
||||
@@ -27,7 +27,7 @@ SKIP: {
|
||||
add_module('git.pl');
|
||||
|
||||
if (qx($GitBinary --version) !~ /git version/) {
|
||||
skip "$GitBinary not found", 15;
|
||||
skip "$GitBinary not found", 16;
|
||||
}
|
||||
GitInitVariables();
|
||||
|
||||
@@ -48,8 +48,7 @@ SKIP: {
|
||||
$GitResult = '';
|
||||
|
||||
GitRun(qw(status));
|
||||
test_page($GitResult,
|
||||
'nothing to commit, working directory clean');
|
||||
test_page($GitResult, 'nothing to commit', 'working directory clean');
|
||||
|
||||
GitRun(qw(log -- Test));
|
||||
test_page($GitResult,
|
||||
|
||||
@@ -35,7 +35,7 @@ xpath_test($page,
|
||||
'//span[@class="portrait gravatar"]',
|
||||
'//p[contains(text(),"This is my comment")]',
|
||||
'//a[@href="http://oddmuse.org/"][text()="Alex Schroeder"]',
|
||||
'//img[@src="http://www.gravatar.com/avatar/' . $gravatar . '"]');
|
||||
'//img[@src="https://secure.gravatar.com/avatar/' . $gravatar . '"]');
|
||||
|
||||
# without homepage
|
||||
|
||||
@@ -51,7 +51,7 @@ xpath_test($page,
|
||||
'//span[@class="portrait gravatar"]',
|
||||
'//p[contains(text(),"This is my comment")]',
|
||||
'//a[@href="http://localhost/wiki.pl/Alex_Schroeder"][text()="Alex Schroeder"]',
|
||||
'//img[@src="http://www.gravatar.com/avatar/' . $gravatar . '"]');
|
||||
'//img[@src="https://secure.gravatar.com/avatar/' . $gravatar . '"]');
|
||||
|
||||
# with homepage an no email
|
||||
|
||||
@@ -68,4 +68,4 @@ xpath_test($page,
|
||||
'//a[@href="http://oddmuse.org/"][text()="Alex Schroeder"]');
|
||||
negative_xpath_test($page,
|
||||
'//span[@class="portrait gravatar"]',
|
||||
'//img[@src="http://www.gravatar.com/avatar/' . $gravatar . '"]');
|
||||
'//img[@src="https://secure.gravatar.com/avatar/' . $gravatar . '"]');
|
||||
|
||||
0
t/history.t
Executable file → Normal file
0
t/history.t
Executable file → Normal file
0
t/include.t
Executable file → Normal file
0
t/include.t
Executable file → Normal file
0
t/indexed-search.t
Executable file → Normal file
0
t/indexed-search.t
Executable file → Normal file
0
t/journal.t
Executable file → Normal file
0
t/journal.t
Executable file → Normal file
0
t/link-all.t
Executable file → Normal file
0
t/link-all.t
Executable file → Normal file
0
t/localnames.t
Executable file → Normal file
0
t/localnames.t
Executable file → Normal file
2
t/lock.t
2
t/lock.t
@@ -25,7 +25,7 @@ test_page(get_page('action=editlock'), 'operation is restricted');
|
||||
test_page(get_page('action=editlock pwd=foo'), 'Edit lock created');
|
||||
xpath_test(update_page('TestLock', 'mu!'),
|
||||
'//a[@href="http://localhost/wiki.pl?action=password"][@class="password"][text()="This page is read-only"]');
|
||||
test_page($redirect, '403 FORBIDDEN', 'Editing not allowed for TestLock');
|
||||
test_page($redirect, '403 FORBIDDEN', 'Editing not allowed: TestLock is read-only');
|
||||
test_page(get_page('action=editlock set=0'), 'operation is restricted');
|
||||
test_page(get_page('action=editlock set=0 pwd=foo'), 'Edit lock removed');
|
||||
RequestLockDir('main');
|
||||
|
||||
0
t/long-tables.t
Executable file → Normal file
0
t/long-tables.t
Executable file → Normal file
18
t/maintain.t
18
t/maintain.t
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2009 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2009, 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
|
||||
@@ -15,9 +15,10 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 6;
|
||||
use Test::More tests => 9;
|
||||
clear_pages();
|
||||
|
||||
# old log entry to be moved
|
||||
my $log = join($FS, '1235079422', 'Ganz_und_Gar', '',
|
||||
'Ladenbeschreibung und Preisliste', '62.12.165.34',
|
||||
'Alex', '1', '', '');
|
||||
@@ -33,3 +34,16 @@ test_page($log,
|
||||
"${FS}test${FS}",
|
||||
"${FS}this is a test${FS}");
|
||||
test_page_negative($log, "^\n");
|
||||
|
||||
# old page to be deleted
|
||||
OpenPage('test');
|
||||
$Page{ts} = 1;
|
||||
$Page{revision} = 1;
|
||||
$Page{text} = $DeletedPage;
|
||||
SavePage();
|
||||
ok(-f GetPageFile($OpenPageName), GetPageFile($OpenPageName)
|
||||
. " exists");
|
||||
xpath_test(get_page('action=maintain pwd=foo'),
|
||||
'//a[text()="test"]/following-sibling::text()[.=" deleted"]');
|
||||
ok(! -e GetPageFile($OpenPageName), GetPageFile($OpenPageName)
|
||||
. " was deleted");
|
||||
|
||||
0
t/markdown-rule.t
Normal file → Executable file
0
t/markdown-rule.t
Normal file → Executable file
0
t/markup.t
Executable file → Normal file
0
t/markup.t
Executable file → Normal file
0
t/oddmuse-2.2.6.pl
Normal file → Executable file
0
t/oddmuse-2.2.6.pl
Normal file → Executable file
0
t/pagenames.t
Executable file → Normal file
0
t/pagenames.t
Executable file → Normal file
0
t/portrait.t
Normal file → Executable file
0
t/portrait.t
Normal file → Executable file
0
t/questionasker.t
Executable file → Normal file
0
t/questionasker.t
Executable file → Normal file
0
t/redirection.t
Executable file → Normal file
0
t/redirection.t
Executable file → Normal file
@@ -32,8 +32,13 @@ SKIP: {
|
||||
my $response = $ua->get("$wiki?action=version");
|
||||
skip("No wiki running at $wiki", 12)
|
||||
unless $response->is_success;
|
||||
skip("Wiki running at $wiki doesn't have the referrer-tracking extension installed", 12)
|
||||
# check that the wiki is capable of running these tests
|
||||
skip("Wiki running at $wiki doesn't have the Referrer-Tracking Extension installed", 12)
|
||||
unless $response->content =~ /referrer-tracking\.pl/;
|
||||
# if we're running in some random environment where localhost is not
|
||||
# a wiki for us to interact with
|
||||
skip("Wiki running at $wiki has the Question Asker Extension installed", 12)
|
||||
if $response->content =~ /questionasker\.pl/;
|
||||
|
||||
my $id = 'Random' . time;
|
||||
# make sure we're not being fooled by 404 errors
|
||||
|
||||
0
t/revisions.t
Executable file → Normal file
0
t/revisions.t
Executable file → Normal file
40
t/rollback.t
Executable file → Normal file
40
t/rollback.t
Executable file → Normal file
@@ -1,21 +1,20 @@
|
||||
# Copyright (C) 2006, 2007, 2008 Alex Schroeder <alex@emacswiki.org>
|
||||
# Copyright (C) 2006–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 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, see <http://www.gnu.org/licenses/>.
|
||||
# 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 => 63;
|
||||
use Test::More tests => 69;
|
||||
use utf8; # tests contain UTF-8 characters and it matters
|
||||
|
||||
clear_pages();
|
||||
@@ -195,3 +194,20 @@ $ts = $Now - $KeepDays * 86400 + 100;
|
||||
get_page("action=rollback to=$ts username=Alex pwd=foo");
|
||||
AppendStringToFile($ConfigFile, "\$KeepDays = 7;\n");
|
||||
test_page_negative(get_page("action=rc raw=1"), '[[rollback]]');
|
||||
|
||||
# Avoid Save button for comments
|
||||
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
|
||||
update_page('Comments_on_Test', 'no spam');
|
||||
ok(get_page('action=browse id=Test raw=2')
|
||||
=~ /(\d+) # Do not delete this line/,
|
||||
'raw=2 returns timestamp');
|
||||
$to = $1;
|
||||
ok($to, 'timestamp stored');
|
||||
sleep(1);
|
||||
|
||||
get_page('title=Comments_on_Test aftertext=http://spam/amoxil/');
|
||||
test_page(get_page('Comments_on_Test'), 'spam');
|
||||
# rollback without password
|
||||
$page = get_page("action=rollback id=Comments_on_Test to=$to username=Alex");
|
||||
test_page($page, 'Rolling back changes');
|
||||
test_page_negative($page, 'Add your comment here', 'Save');
|
||||
|
||||
7
t/search.t
Executable file → Normal file
7
t/search.t
Executable file → Normal file
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2006, 2007, 2009 Alex Schroeder <alex@gnu.org>
|
||||
# Copyright (C) 2006–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
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
require 't/test.pl';
|
||||
package OddMuse;
|
||||
use Test::More tests => 38;
|
||||
use Test::More tests => 39;
|
||||
use utf8; # tests contain UTF-8 characters and it matters
|
||||
|
||||
clear_pages();
|
||||
@@ -102,6 +102,9 @@ xpath_test(update_page('IncludeSearch',
|
||||
'//div[@class="search"]/p/span[@class="result"]/a[@class="local"][@href="http://localhost/wiki.pl/NegativeSearchTestTwo"][text()="NegativeSearchTestTwo"]',
|
||||
'//p[text()=" last line"]'); # note the NL -> SPC
|
||||
|
||||
xpath_test(get_page('search=Schröder'),
|
||||
'//input[@name="search"][@value="Schröder"]');
|
||||
|
||||
# Search for zero
|
||||
|
||||
update_page("Zero", "This is about 0 and the empty string ''.");
|
||||
|
||||
0
t/setext.t
Executable file → Normal file
0
t/setext.t
Executable file → Normal file
0
t/sidebar.t
Normal file → Executable file
0
t/sidebar.t
Normal file → Executable file
0
t/subscribe.t
Executable file → Normal file
0
t/subscribe.t
Executable file → Normal file
0
t/summary.t
Executable file → Normal file
0
t/summary.t
Executable file → Normal file
@@ -87,6 +87,7 @@ clear_pages();
|
||||
my $dir = `/bin/pwd`;
|
||||
chop($dir);
|
||||
my $mod = 'namespaces-2.2.6.pl';
|
||||
mkdir($ModuleDir);
|
||||
symlink("$dir/t/$mod", "$ModuleDir/$mod");
|
||||
ok(-e "$ModuleDir/$mod", "old namespaces.pl installed");
|
||||
|
||||
|
||||
0
t/upload.t
Executable file → Normal file
0
t/upload.t
Executable file → Normal file
0
t/usemod-1.0.4.pl
Executable file → Normal file
0
t/usemod-1.0.4.pl
Executable file → Normal file
0
t/usemod-options.t
Executable file → Normal file
0
t/usemod-options.t
Executable file → Normal file
155
wiki.pl
155
wiki.pl
@@ -29,11 +29,9 @@
|
||||
|
||||
package OddMuse;
|
||||
use strict;
|
||||
use warnings;
|
||||
#use diagnostics;
|
||||
|
||||
use CGI;
|
||||
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
|
||||
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';
|
||||
local $| = 1; # Do not buffer output (localized for mod_perl)
|
||||
|
||||
@@ -100,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.
|
||||
@@ -217,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.
|
||||
@@ -305,11 +304,10 @@ sub InitVariables { # Init global session variables for mod_perl!
|
||||
CreateDir($DataDir); # Create directory if it doesn't exist
|
||||
$Now = time; # Reset in case script is persistent
|
||||
my $ts = (stat($IndexFile))[9]; # always stat for multiple server processes
|
||||
ReInit() if not $ts or not $LastUpdate or $LastUpdate != $ts; # reinit if another process changed files (requires $DataDir)
|
||||
ReInit() if not $ts or $LastUpdate != $ts; # reinit if another process changed files (requires $DataDir)
|
||||
$LastUpdate = $ts;
|
||||
unshift(@MyRules, \&MyRules) if defined(&MyRules) && (not @MyRules or $MyRules[0] != \&MyRules);
|
||||
$RuleOrder{$_} //= 0 for @MyRules; # default is 0
|
||||
@MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules;
|
||||
@MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules; # default is 0
|
||||
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR') unless -d $DataDir;
|
||||
@IndexOptions = (['pages', T('Include normal pages'), 1, \&AllPagesList]);
|
||||
foreach my $sub (@MyInitVariables) {
|
||||
@@ -328,9 +326,8 @@ sub ReInit { # init everything we need if we want to link to stuff
|
||||
sub InitCookie {
|
||||
undef $q->{'.cookies'}; # Clear cache if it exists (for SpeedyCGI)
|
||||
my $cookie = $q->cookie($CookieName);
|
||||
utf8::decode($cookie); # make sure it's decoded as UTF-8
|
||||
%OldCookie = split(/$FS/o, UrlDecode($cookie));
|
||||
my %provided = map { utf8::decode($_); $_ => 1 } $q->param;
|
||||
my %provided = map { $_ => 1 } $q->param;
|
||||
for my $key (keys %OldCookie) {
|
||||
SetParam($key, $OldCookie{$key}) unless $provided{$key};
|
||||
}
|
||||
@@ -366,10 +363,9 @@ sub CookieRollbackFix {
|
||||
|
||||
sub GetParam {
|
||||
my ($name, $default) = @_;
|
||||
utf8::encode($name); # may fail
|
||||
utf8::encode($name); # turn to byte string
|
||||
my $result = $q->param($name);
|
||||
$result //= $default;
|
||||
utf8::decode($result) if defined $result; # may fail, avoid turning undef to ''
|
||||
return QuoteHtml($result); # you need to unquote anything that can have <tags>
|
||||
}
|
||||
|
||||
@@ -457,7 +453,7 @@ sub ApplyRules {
|
||||
} else {
|
||||
$Includes{$OpenPageName} = 1;
|
||||
local $OpenPageName = FreeToNormal($uri);
|
||||
if ($type and $type eq 'text') {
|
||||
if ($type eq 'text') {
|
||||
print $q->pre({class=>"include $OpenPageName"}, QuoteHtml(GetPageContent($OpenPageName)));
|
||||
} elsif (not $Includes{$OpenPageName}) { # with a starting tag, watch out for recursion
|
||||
print $q->start_div({class=>"include $OpenPageName"});
|
||||
@@ -527,7 +523,7 @@ sub ApplyRules {
|
||||
$bol = (substr($_, pos() - 1, 1) eq "\n");
|
||||
}
|
||||
}
|
||||
pos = length $_ if $_; # notify module functions we've completed rule handling
|
||||
pos = length $_; # notify module functions we've completed rule handling
|
||||
Clean(CloseHtmlEnvironments()); # last block -- close it, cache it
|
||||
if ($Fragment ne '') {
|
||||
$Fragment =~ s|<p>\s*</p>||g; # clean up extra paragraphs (see end Dirty())
|
||||
@@ -677,7 +673,7 @@ sub OpenHtmlEnvironment { # close the previous $html_tag and open a new one
|
||||
}
|
||||
|
||||
sub CloseHtmlEnvironments { # close all -- remember to use AddHtmlEnvironment('p') if required!
|
||||
return CloseHtmlEnvironmentUntil() if $_ and pos($_) == length($_); # close all HTML environments if we're are at the end of this page
|
||||
return CloseHtmlEnvironmentUntil() if pos($_) == length($_); # close all HTML environments if we're are at the end of this page
|
||||
my $html = '';
|
||||
while (@HtmlStack) {
|
||||
defined $HtmlEnvironmentContainers{$HtmlStack[0]} and # avoid closing block level elements
|
||||
@@ -773,7 +769,6 @@ sub DoClearCache {
|
||||
|
||||
sub QuoteHtml {
|
||||
my $html = shift;
|
||||
return '' if not $html;
|
||||
$html =~ s/&/&/g;
|
||||
$html =~ s/</</g;
|
||||
$html =~ s/>/>/g;
|
||||
@@ -844,9 +839,9 @@ sub PrintJournal {
|
||||
$offset ||= 0;
|
||||
# FIXME: Should pass filtered list of pages to SearchTitleAndBody to save time?
|
||||
my @pages = sort JournalSort (grep(/$regexp/, $search ? SearchTitleAndBody($search) : AllPagesList()));
|
||||
@pages = reverse @pages if $mode and ($mode eq 'reverse' or $mode eq 'future');
|
||||
@pages = reverse @pages if $mode eq 'reverse' or $mode eq 'future';
|
||||
$b = $Today // CalcDay($Now);
|
||||
if ($mode and ($mode eq 'future' or $mode eq 'past')) {
|
||||
if ($mode eq 'future' || $mode eq 'past') {
|
||||
my $compare = $mode eq 'future' ? -1 : 1;
|
||||
for (my $i = 0; $i < @pages; $i++) {
|
||||
$a = $pages[$i];
|
||||
@@ -1156,7 +1151,7 @@ sub GetPageOrEditLink { # use GetPageLink and GetEditLink if you know the result
|
||||
} else { # reproduce markup if $UseQuestionmark
|
||||
return GetEditLink($id, UnquoteHtml($bracket ? "[$link]" : $link)) unless $UseQuestionmark;
|
||||
$link = QuoteHtml($id) . GetEditLink($id, '?');
|
||||
$link .= ($free ? '|' : ' ') . $text if $text and $text ne $id;
|
||||
$link .= ($free ? '|' : ' ') . $text if $text and FreeToNormal($text) ne $id;
|
||||
$link = "[[$link]]" if $free;
|
||||
$link = "[$link]" if $bracket or not $free and $text;
|
||||
return $link;
|
||||
@@ -1168,7 +1163,7 @@ sub GetPageLink { # use if you want to force a link to local pages, whether it e
|
||||
$id = FreeToNormal($id);
|
||||
$name ||= $id;
|
||||
$class .= ' ' if $class;
|
||||
return ScriptLink(UrlEncode($id), NormalToFree($name), ($class || '') . 'local',
|
||||
return ScriptLink(UrlEncode($id), NormalToFree($name), $class . 'local',
|
||||
undef, undef, $accesskey);
|
||||
}
|
||||
|
||||
@@ -1268,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);
|
||||
@@ -1461,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
|
||||
@@ -1582,8 +1579,8 @@ sub GetRcLinesFor {
|
||||
next if $idOnly and $idOnly ne $id;
|
||||
next if $filterOnly and not $match{$id};
|
||||
next if ($userOnly and $userOnly ne $username);
|
||||
next if $minor and $minor == 1 and not $showminoredit; # skip minor edits (if [[rollback]] this is bogus)
|
||||
next if not $minor and $showminoredit and $showminoredit == 2; # skip major edits
|
||||
next if $minor == 1 and not $showminoredit; # skip minor edits (if [[rollback]] this is bogus)
|
||||
next if not $minor and $showminoredit == 2; # skip major edits
|
||||
next if $match and $id !~ /$match/i;
|
||||
next if $hostOnly and $host !~ /$hostOnly/i;
|
||||
my @languages = split(/,/, $languages);
|
||||
@@ -1721,7 +1718,7 @@ sub GetFilterForm {
|
||||
-default=>GetParam('lang', ''))));
|
||||
}
|
||||
return GetFormStart(undef, 'get', 'filter') . $q->p($form) . $q->table($table)
|
||||
. $q->p($q->submit('dofilter', T('Go!'))) . $q->endform;
|
||||
. $q->p($q->submit('dofilter', T('Go!'))) . $q->end_form;
|
||||
}
|
||||
|
||||
sub RcHtml {
|
||||
@@ -1796,7 +1793,7 @@ sub RcHtml {
|
||||
$more .= ";$_=$val" if $val;
|
||||
}
|
||||
$html .= $q->p({-class=>'more'}, ScriptLink($more, T('More...'), 'more'));
|
||||
return GetFormStart(undef, 'get', 'rc') . $html . $q->endform;
|
||||
return GetFormStart(undef, 'get', 'rc') . $html . $q->end_form;
|
||||
}
|
||||
|
||||
sub PrintRcHtml { # to append RC to existing page, or action=rc directly
|
||||
@@ -2075,7 +2072,7 @@ sub DoRollback {
|
||||
if ($Page{text} eq $text) {
|
||||
print T("The two revisions are the same."), $q->br() if $page; # no message when doing mass revert
|
||||
} elsif (not UserCanEdit($id, 1)) {
|
||||
print Ts('Editing not allowed for %s.', $id), $q->br();
|
||||
print Ts('Editing not allowed: %s is read-only.', $id), $q->br();
|
||||
} elsif (not UserIsEditor() and my $rule = BannedContent($text)) {
|
||||
print Ts('Rollback of %s would restore banned content.', $id), $rule, $q->br();
|
||||
} else {
|
||||
@@ -2086,7 +2083,7 @@ sub DoRollback {
|
||||
WriteRcLog('[[rollback]]', $page, $to); # leave marker
|
||||
print $q->end_p() . $q->end_div();
|
||||
ReleaseLock();
|
||||
PrintFooter($page);
|
||||
PrintFooter($page, 'edit');
|
||||
}
|
||||
|
||||
sub DoAdminPage {
|
||||
@@ -2094,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'));
|
||||
@@ -2116,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);
|
||||
@@ -2243,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;
|
||||
@@ -2281,7 +2294,6 @@ sub Cookie {
|
||||
my ($changed, $visible, %params) = CookieData(); # params are URL encoded
|
||||
if ($changed) {
|
||||
my $cookie = join(UrlEncode($FS), %params); # no CTL in field values
|
||||
utf8::encode($cookie); # prevent casting to Latin 1
|
||||
my $result = $q->cookie(-name=>$CookieName, -value=>$cookie, -expires=>'+2y');
|
||||
if ($visible) {
|
||||
$Message .= $q->p(T('Cookie: ') . $CookieName . ', '
|
||||
@@ -2384,7 +2396,6 @@ sub PrintFooter {
|
||||
|
||||
sub GetFooterTimestamp {
|
||||
my ($id, $rev) = @_;
|
||||
$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}),
|
||||
Ts('by %s', GetAuthorLink($Page{host}, $Page{username})));
|
||||
@@ -2396,7 +2407,6 @@ sub GetFooterTimestamp {
|
||||
|
||||
sub GetFooterLinks {
|
||||
my ($id, $rev) = @_;
|
||||
$rev //= '';
|
||||
my @elements;
|
||||
if ($id and $rev ne 'history' and $rev ne 'edit') {
|
||||
if ($CommentsPattern) {
|
||||
@@ -2418,7 +2428,7 @@ sub GetFooterLinks {
|
||||
}
|
||||
push(@elements, GetHistoryLink($id, T('View other revisions'))) if $Action{history} and $id and $rev ne 'history';
|
||||
push(@elements, GetPageLink($id, T('View current revision')),
|
||||
GetRCLink($id, T('View all changes'))) if $Action{history} and $rev;
|
||||
GetRCLink($id, T('View all changes'))) if $Action{history} and $rev ne '';
|
||||
if ($Action{contrib} and $id and $rev eq 'history') {
|
||||
push(@elements, ScriptLink("action=contrib;id=" . UrlEncode($id), T('View contributors'), 'contrib'));
|
||||
}
|
||||
@@ -2432,7 +2442,6 @@ sub GetFooterLinks {
|
||||
|
||||
sub GetCommentForm {
|
||||
my ($id, $rev, $comment) = @_;
|
||||
$rev //= '';
|
||||
if ($CommentsPattern ne '' and $id and $rev ne 'history' and $rev ne 'edit'
|
||||
and $id =~ /$CommentsPattern/o and UserCanEdit($id, 0, 1)) {
|
||||
return $q->div({-class=>'comment'}, GetFormStart(undef, undef, 'comment'), # protected by questionasker
|
||||
@@ -2450,7 +2459,7 @@ sub GetCommentForm {
|
||||
-override=>1, -size=>40, -maxlength=>100))),
|
||||
$q->p($q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save')), ' ',
|
||||
$q->submit(-name=>'Preview', -accesskey=>T('p'), -value=>T('Preview'))),
|
||||
$q->endform());
|
||||
$q->end_form());
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -2478,7 +2487,7 @@ sub GetSearchForm {
|
||||
-default=>GetParam('lang', '')) . ' ';
|
||||
}
|
||||
return GetFormStart(undef, 'get', 'search')
|
||||
. $q->p($form . $q->submit('dosearch', T('Go!'))) . $q->endform;
|
||||
. $q->p($form . $q->submit('dosearch', T('Go!'))) . $q->end_form;
|
||||
}
|
||||
|
||||
sub GetValidatorLink {
|
||||
@@ -2986,21 +2995,9 @@ sub UnWiki {
|
||||
|
||||
sub DoEdit {
|
||||
my ($id, $newText, $preview) = @_;
|
||||
ValidIdOrDie($id);
|
||||
UserCanEditOrDie($id);
|
||||
my $upload = GetParam('upload', undef);
|
||||
if (not UserCanEdit($id, 1)) {
|
||||
my $rule = UserIsBanned();
|
||||
if ($rule) {
|
||||
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
|
||||
$q->p(T('Editing not allowed: user, ip, or network is blocked.')),
|
||||
$q->p(T('Contact the wiki administrator for more information.')),
|
||||
$q->p(Ts('The rule %s matched for you.', $rule) . ' '
|
||||
. Ts('See %s for more information.', GetPageLink($BannedHosts))));
|
||||
} else {
|
||||
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
|
||||
$q->p(Ts('Editing not allowed: %s is read-only.', NormalToFree($id))));
|
||||
}
|
||||
} elsif ($upload and not $UploadAllowed and not UserIsAdmin()) {
|
||||
if ($upload and not $UploadAllowed and not UserIsAdmin()) {
|
||||
ReportError(T('Only administrators can upload files.'), '403 FORBIDDEN');
|
||||
}
|
||||
OpenPage($id);
|
||||
@@ -3061,7 +3058,7 @@ sub GetEditForm {
|
||||
} elsif ($UploadAllowed or UserIsAdmin()) {
|
||||
$html .= $q->p(ScriptLink('action=edit;upload=1;id=' . UrlEncode($page_name), T('Replace this text with a file'), 'upload'));
|
||||
}
|
||||
$html .= $q->endform();
|
||||
$html .= $q->end_form();
|
||||
return $html;
|
||||
}
|
||||
|
||||
@@ -3079,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;
|
||||
}
|
||||
}
|
||||
@@ -3113,7 +3109,7 @@ sub DoPassword {
|
||||
print GetFormStart(undef, undef, 'password'),
|
||||
$q->p(GetHiddenValue('action', 'password'), T('Password:'), ' ',
|
||||
$q->password_field(-name=>'pwd', -size=>20, -maxlength=>50),
|
||||
$q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save'))), $q->endform;
|
||||
$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.'));
|
||||
}
|
||||
@@ -3133,6 +3129,24 @@ sub UserIsAdminOrError {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub UserCanEditOrDie {
|
||||
my $id = shift;
|
||||
ValidIdOrDie($id);
|
||||
if (not UserCanEdit($id, 1)) {
|
||||
my $rule = UserIsBanned();
|
||||
if ($rule) {
|
||||
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
|
||||
$q->p(T('Editing not allowed: user, ip, or network is blocked.')),
|
||||
$q->p(T('Contact the wiki administrator for more information.')),
|
||||
$q->p(Ts('The rule %s matched for you.', $rule) . ' '
|
||||
. Ts('See %s for more information.', GetPageLink($BannedHosts))));
|
||||
} else {
|
||||
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
|
||||
$q->p(Ts('Editing not allowed: %s is read-only.', NormalToFree($id))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub UserCanEdit {
|
||||
my ($id, $editing, $comment) = @_;
|
||||
return 0 if $id eq 'SampleUndefinedPage' or $id eq T('SampleUndefinedPage')
|
||||
@@ -3498,8 +3512,7 @@ sub Replace {
|
||||
|
||||
sub DoPost {
|
||||
my $id = FreeToNormal(shift);
|
||||
ValidIdOrDie($id);
|
||||
ReportError(Ts('Editing not allowed for %s.', $id), '403 FORBIDDEN') unless UserCanEdit($id, 1);
|
||||
UserCanEditOrDie($id);
|
||||
# Lock before getting old page to prevent races
|
||||
RequestLockOrError(); # fatal
|
||||
OpenPage($id);
|
||||
@@ -3513,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);
|
||||
}
|
||||
@@ -3627,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):/;
|
||||
@@ -3903,7 +3916,7 @@ sub DelayRequired {
|
||||
my $name = shift;
|
||||
my @entries = @{$RecentVisitors{$name}};
|
||||
my $ts = $entries[$SurgeProtectionViews];
|
||||
return $Now - ($ts || 0) < $SurgeProtectionTime;
|
||||
return ($Now - $ts) < $SurgeProtectionTime;
|
||||
}
|
||||
|
||||
sub AddRecentVisitor {
|
||||
@@ -3931,20 +3944,17 @@ sub WriteRecentVisitors {
|
||||
foreach my $name (keys %RecentVisitors) {
|
||||
my @entries = @{$RecentVisitors{$name}};
|
||||
if ($entries[0] >= $limit) { # if the most recent one is too old, do not keep
|
||||
$data .= join($FS, $name, @entries[0 .. $SurgeProtectionViews - 1]) . "\n";
|
||||
$data .= join($FS, $name, @entries[0 .. $SurgeProtectionViews - 1]) . "\n";
|
||||
}
|
||||
}
|
||||
WriteStringToFile($VisitorFile, $data);
|
||||
}
|
||||
|
||||
sub TextIsFile {
|
||||
return '' unless $_[0];
|
||||
$_[0] =~ /^#FILE (\S+) ?(\S+)?\n/
|
||||
}
|
||||
sub TextIsFile { $_[0] =~ /^#FILE (\S+) ?(\S+)?\n/ }
|
||||
|
||||
sub AddModuleDescription { # cannot use $q here because this is module init time
|
||||
my ($filename, $page, $dir, $tag) = @_;
|
||||
my $src = "http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/" . ($dir || '') . UrlEncode($filename) . ($tag ? '?' . $tag : '');
|
||||
my $src = "http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/$dir" . UrlEncode($filename) . ($tag ? '?' . $tag : '');
|
||||
my $doc = 'http://www.oddmuse.org/cgi-bin/oddmuse/' . UrlEncode(FreeToNormal($page));
|
||||
$ModulesDescription .= "<p><a href=\"$src\">" . QuoteHtml($filename) . "</a>" . ($tag ? " ($tag)" : '');
|
||||
$ModulesDescription .= T(', see ') . "<a href=\"$doc\">" . QuoteHtml($page) . "</a>" if $page;
|
||||
@@ -3952,5 +3962,4 @@ sub AddModuleDescription { # cannot use $q here because this is module init time
|
||||
}
|
||||
|
||||
DoWikiRequest() if $RunCGI and not exists $ENV{MOD_PERL}; # Do everything.
|
||||
warningsToBrowser(1);
|
||||
1; # In case we are loaded from elsewhere
|
||||
|
||||
Reference in New Issue
Block a user