Compare commits

...

109 Commits

Author SHA1 Message Date
Alex Schroeder
ec4987e817 rc.t: fix copyright year 2018-07-13 00:20:09 +02:00
Alex Schroeder
655d3b47c6 checkbox.t: new tests for checkbox.pl 2018-06-28 10:31:36 +02:00
Alex Schroeder
8b85c6e9dd Save Anonymous username without translation
When saving, write "Anonymous" without translating it. When displaying
"Anonymous", translate it.
2018-06-28 00:51:46 +02:00
Alex Schroeder
ef35e4eda6 rc2mail: update timestamp in any case 2018-06-28 00:49:46 +02:00
Alex Schroeder
7bb1ca239b search-inclusion: new test and fix
I must have misunderstood how 'local' works because it turns out that
you need to move it out of the block for it to work as intended.
2018-06-28 00:49:38 +02:00
Alex Schroeder
a7625b35f4 README: mention problem with non-existing $DataDir 2018-06-28 00:49:23 +02:00
Alex Schroeder
1d239aa9a6 latex.pl: don't read and write; rename instead
Using ReadFileOrDie and WriteStringToFile on a PNG file borks it
because we're decoding and encoding the file as UTF-8. Let's use
Rename instead.
2018-06-28 00:49:10 +02:00
Alex Schroeder
3f1e5caaad bootstrap.js: bootstrap integration example 2018-06-28 00:48:58 +02:00
Alex Schroeder
bdbd6c8ba7 wiki: allow mixing of search and match
Move search up in the list of actions so that it takes precedence over
match if both parameters are provided and add matching to the Filtered
function so that it is taken into account when searching.
2018-06-28 00:48:43 +02:00
Alex Schroeder
1a898ee589 wiki: anonymize rc.log entries even if they are not moved 2018-06-28 00:34:08 +02:00
Alex Schroeder
9609adb575 gopher-server: own implementation of NormaltoFree
We want to avoid turning & into & when printing a menu, so we don't
want to call NormalToFree because it callse QuoteHtml. Replaced all
calls with normal_to_free which only translates underscores to spaces.
2018-06-28 00:31:29 +02:00
Alex Schroeder
34c10202b0 wiki.pl: anonymous entries report as Anonymous
Two changes: First, when moving entries from rc.log to oldrc.log, make
sure we use the string "Anonymous" for the host instead of translating
it. Second, when printing the author link using GetAuthorLink, don't
call ColorCode for anonymous authors if the IP number has been replaced
by the string "Anonymous". In this case, just return "Anonymous".
Otherwise we're getting the color code of the string "Anonymous" which
is confusing.
2018-06-28 00:31:29 +02:00
Alex Schroeder
1457fad242 wiki.pl: update copyright year 2018-06-28 00:31:29 +02:00
Alex Schroeder
ce355499c7 oddtrans: fix missing . in @INC 2018-06-28 00:31:29 +02:00
Alex Schroeder
2bb3d2f967 strip-hosts: also strip ip keys 2018-06-28 00:31:29 +02:00
Alex Schroeder
a301ab5e1c strip-host: fix data corruption 2018-06-28 00:31:29 +02:00
Alex Schroeder
d74d29fa13 Anonymize IPs that we do show using a color code. 2018-06-28 00:31:29 +02:00
Alex Schroeder
96c54d9d66 New: strip-host.pl for DSGVO 2018-06-28 00:31:29 +02:00
Alex Schroeder
9a0700cac1 Getting ready for #DSGVO
Removing IP number ("host") from page files and keep files. The IP
numbers remain in the RC file. GetAuthor and GetAuthorLink now take
username as a first argument and host as an optional second argument.
Sorry for the incompatible change.
2018-06-28 00:31:29 +02:00
Alex Schroeder
864890c168 Added logo previews 2018-06-28 00:31:29 +02:00
Alex Schroeder
a93873d1d4 Logo updates 2018-06-28 00:31:29 +02:00
Alex Schroeder
b0a8fa2141 Logo files
Added old logo by Murray Altheim and new logo by Falky.
2018-06-28 00:31:29 +02:00
Alex Schroeder
88799b3ebc gopher-server: better handle links gopher urls
Accept links without path, i.e. gopher://alexschroeder.ch.
2018-06-28 00:31:29 +02:00
Alex Schroeder
4032aa563c gopher-server: ignore the period issues of RFC 1436
As discussed in issue #38 of VF-1 and on the #gopherproject channel,
newer software does not double leading periods and does send final
periods, so this commit simplifies the code and no longer does that,
either. https://github.com/solderpunk/VF-1/issues/38
2018-06-28 00:31:29 +02:00
Alex Schroeder
2b9fd67dbd gopher-server: support / 2018-06-28 00:31:29 +02:00
Alex Schroeder
62056409de gopher-server: add RSS support 2018-06-28 00:31:29 +02:00
Alex Schroeder
f45f4eb49f gopher-server: add support or external images 2018-06-28 00:31:29 +02:00
Alex Schroeder
acd4d42561 gopher-server: Add support for HTML links 2018-06-28 00:31:29 +02:00
Alex Schroeder
8375c3a842 gopher-server: add support for adding maps
Gopher maps can be added to the main menu.
2018-06-28 00:31:29 +02:00
Alex Schroeder
54138b7998 alex-2018.css: small changes 2018-06-28 00:31:29 +02:00
Alex Schroeder
3816567543 gopher-server: redesign of main menu 2018-06-28 00:31:29 +02:00
Alex Schroeder
c6954437ea gopher-server: URL escape all ids in the log 2018-06-28 00:31:29 +02:00
Alex Schroeder
f3725a4938 gopher-server: header and footer for html items 2018-06-28 00:31:29 +02:00
Alex Schroeder
fc8f6b4a42 gopher_server: Two separate options for SSL
I replaced --wiki_pem_file with two options, --wiki_key_file for your
private key and --wiki_cert_file for your full certificate chain.
2018-06-28 00:31:29 +02:00
Alex Schroeder
b6109e37ad gopher-server: support TLS 2018-06-28 00:31:29 +02:00
Alex Schroeder
b3b98e2b82 gopher-server: handle gopher links 2018-06-28 00:31:29 +02:00
Alex Schroeder
74f6a4b314 gopher-server: url escape selectors 2018-06-28 00:31:29 +02:00
Alex Schroeder
19a9ad3da0 gopher-server: fix handling of image:link 2018-06-28 00:31:29 +02:00
Alex Schroeder
2d99025024 css: added CSS files for alexschroeder.ch/wiki 2018-06-28 00:31:29 +02:00
Alex Schroeder
63370f31d7 markdown-rule: robuster _underline_ and /italic/ 2018-06-28 00:31:29 +02:00
Alex Schroeder
752daa81b5 gopher-server: don't need to decode text 2018-06-28 00:31:29 +02:00
Alex Schroeder
429ead8c69 gopher-server: host and port are arrays
$self->{server}->{host}->[0] instead of $self->{server}->{host}
2018-06-28 00:31:29 +02:00
Alex Schroeder
52f4aad356 gopher-server: don't just use sockaddr 2018-06-28 00:31:29 +02:00
Alex Schroeder
4abd0a26cf gopher-server: switch back to Net::Server again
I'm hoping that this works better than the async framework.
2018-06-28 00:31:29 +02:00
Alex Schroeder
c8173cac04 gopher-server: refactor
Format code, remove unnecessary test for $continue_reading at the end.
2018-06-28 00:31:29 +02:00
Alex Schroeder
be4b141c43 gopher-server: more debug logging 2018-06-28 00:31:29 +02:00
Alex Schroeder
f9d6258744 gopher-server: make sure to use correct info items
Also other improvements, hopefully. There are still problems regarding
feedback. When uploading a file twice in succession, for example, the
second call produces no output until a few seconds have passed.

echo -e "Alex/image/png/write/file\t"`wc -c < test.png` \
| cat - test.png   | nc localhost 7070

echo -e "Alex/image/png/write/file\t"`wc -c < test.png` \
| cat - test.png   | nc localhost 7070

I really have no idea why this is. The log output is correct but
printing to the stream just doesn't work anymore. Has it been closed
in the mean time?
2018-06-28 00:31:29 +02:00
Alex Schroeder
66234d7785 gopher-server: upload binary files with content-length 2018-06-28 00:31:29 +02:00
Alex Schroeder
c67b4a7f12 gopher-server: final period and line breaks
Not sure what we should be stripping, here?
2018-06-28 00:31:29 +02:00
Alex Schroeder
4a94023be2 gopher-server: final period required on uploads
When uploading big files, they get sent in chunks we therefore have to
detect the end of the file being sent. A final period on a line by
itself ("\n.\n") will have to do -- even for binary uploads.
2018-06-28 00:31:29 +02:00
Alex Schroeder
9535f45647 gopher-server: refactor info printing
We're now printing (useless) host and port data on info lines as well,
just because these lines might otherwise not get parsed correctly by
clients. It's a useless waste of bandwidth, though.
2018-06-28 00:31:29 +02:00
Alex Schroeder
8f585bcd29 gopher-server: remove unnecessary Init call 2018-06-28 00:31:29 +02:00
Alex Schroeder
d95d7b0674 wiki.pl: shorten search fields
The search fields at the top were shortened such that they all fit on
one line when all of them are shown (set %Languages and $MatchingPages
to show them all), on a terminal 80 characters wide, using a text
browser like Lynx or w3m.
2018-06-28 00:31:29 +02:00
Alex Schroeder
50fcd7eb0b gopher-server: add a test for large uploads 2018-06-28 00:31:29 +02:00
Alex Schroeder
e5b46fe1a4 gopher-server: refactor test 2018-01-05 11:21:20 +01:00
Alex Schroeder
28872646d0 gopher-server: more fiddling with menu labels 2018-01-05 11:05:43 +01:00
Alex Schroeder
d059e09104 gopher-server: fixed menu labels 2018-01-05 11:03:47 +01:00
Alex Schroeder
545cd78805 gopher-server: refactor comment and history link 2018-01-05 10:57:53 +01:00
Alex Schroeder
1e7a7d2fa7 gopher-server: change the order of links 2018-01-05 10:53:22 +01:00
Alex Schroeder
1b540fc294 gopher-server: after page save, link back to page 2018-01-05 10:45:21 +01:00
Alex Schroeder
493ddc233c markdown-rule: space after list item marker
Common Marks agrees. There, one to four spaces are required after * or
- or 1. to make it a list item.
2018-01-05 08:52:56 +01:00
Alex Schroeder
07b3169c5b Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2018-01-04 14:02:26 +01:00
Alex Schroeder
e73707a16f gopher-server: log level error for tests 2018-01-04 13:57:41 +01:00
Alex Schroeder
5323399bc8 gopher-server: clear metadata on each loop
This resets $q on every call such that no parameters are carried over
to the next request.

Also, refactor printing of menu lines.
2018-01-04 13:55:09 +01:00
Alex Schroeder
13ac083542 gopher-server: serve non-existing pages 2018-01-04 13:55:08 +01:00
Alex Schroeder
3dee191328 gopher-server: changed how new files are created 2018-01-04 13:55:01 +01:00
Alex Schroeder
e13524c1d3 gopher-server: prepare for appending text 2018-01-04 13:51:51 +01:00
Alex Schroeder
dc82b7d64f gopher-server: serve non-existing pages 2018-01-03 22:45:31 +01:00
Alex Schroeder
d68163ee90 gopher-server: changed how new files are created 2018-01-03 18:46:23 +01:00
Alex Schroeder
349ed2722c gopher-server: allow creating new pages 2018-01-03 14:53:30 +01:00
Alex Schroeder
e01b39edf6 gopher-server: link to comment page 2018-01-03 13:25:17 +01:00
Alex Schroeder
783325509a gopher-server: fix tests 2018-01-03 08:54:49 +01:00
Alex Schroeder
2db3736a70 gopher-server: fix port and host handling 2018-01-03 08:53:55 +01:00
Alex Schroeder
22cf00c28f gopher-server: handle encoding issues 2018-01-02 12:42:29 +01:00
Alex Schroeder
3fe2736ad4 gopher-server: remove pid file handling
Usage no suggests using an external tool like daemonize instead.
2018-01-02 11:55:46 +01:00
Alex Schroeder
03b38673f7 gopher-server: fix log setup 2018-01-01 22:36:39 +01:00
Alex Schroeder
3590bb96dd gopher-server: switch to Mojo::IOLoop
This makes it possible to use telnet and nc as a client, and this
makes a separate gopher-client unnecessary.
2018-01-01 22:00:52 +01:00
Alex Schroeder
5051b9602a gopher: client and server, with file uploads 2017-12-31 14:32:39 +01:00
Alex Schroeder
af9da2be34 gopher-server: posting to the wiki 2017-12-30 20:42:24 +01:00
Alex Schroeder
0c17454a0c gopher-server: refactor routing
Use regular expression matching instead of substrings to identify page
names.
2017-12-30 18:56:59 +01:00
Alex Schroeder
d789bc40f0 gopher-server: finished unit tests and more
Fixed the serving of text and html versions of older revisions.
2017-12-30 17:26:41 +01:00
Alex Schroeder
3a97171320 gopher-server: added revisions and tests 2017-12-29 23:20:47 +01:00
Alex Schroeder
110970f310 gopher-server: add toggle for minor edits to rc 2017-12-29 15:28:10 +01:00
Alex Schroeder
9986552ffb gopher-server: simple recent changes 2017-12-29 15:18:55 +01:00
Alex Schroeder
64277e26ed gopher-server: sort search results newest first 2017-12-29 14:55:12 +01:00
Alex Schroeder
b35e867b55 gopher-server: include search 2017-12-29 11:37:56 +01:00
Alex Schroeder
67c6db4b03 gopher-server: more tag support 2017-12-28 21:40:21 +01:00
Alex Schroeder
eec5307bc3 gopher-server; print more entries in main menu 2017-12-28 21:03:50 +01:00
Alex Schroeder
6a3b9a9916 Merge branch 'master' of github.com:kensanata/oddmuse 2017-12-28 19:43:56 +01:00
Alex Schroeder
e6880ae469 Summary: gopher-server more stringent
While testing with the cgo client, I ran into a few problems which are
now fixed. No more mixing of text and menu!
https://github.com/kieselsteini/cgo
2017-12-28 19:39:26 +01:00
Alex Schroeder
40de3ea9a1 gopher-server: image support 2017-12-28 19:09:11 +01:00
Alex Schroeder
93d40b022f Sort tag searches newest first 2017-12-28 13:15:44 +01:00
Alex Schroeder
dec6acf354 gopher-server: Net::Server personality
Switch from Net::Server::PreFork to Net::Server::Fork because I think
all the children will exit due to timeouts anyway.
2017-12-28 13:06:26 +01:00
Alex Schroeder
062cd9b5b9 gopher-server: just link to tag page 2017-12-28 12:54:54 +01:00
Alex Schroeder
31574e3606 gopher-server: handle [[tag:foo]] links 2017-12-28 12:48:34 +01:00
Alex Schroeder
ca01d9d3d6 gopher-server: handle [[foo|bar]] links 2017-12-28 12:16:25 +01:00
Alex Schroeder
9c69322289 gopher-server now with links 2017-12-28 11:07:03 +01:00
Alex Schroeder
c13841e30a gopher-server: add --wiki_pages 2017-12-27 15:26:32 +01:00
Alex Schroeder
9ae1ff22c7 gopher-server: better first page 2017-12-27 15:14:03 +01:00
Alex Schroeder
8db5a45dcd gopher-server: fix copyright year 2017-12-27 12:52:06 +01:00
Alex Schroeder
b3e2485cd0 gopher-server: changes to make it work 2017-12-27 11:11:06 +01:00
Alex Schroeder
6c135be248 gopher-server: new 2017-12-27 09:45:43 +01:00
Alex Schroeder
b5a4af9656 usemod: fix tables and ''''' 2017-12-22 00:18:13 +01:00
Alex Schroeder
b23f1d777b markdown-rule.pl: small change to code 2017-12-21 23:28:56 +01:00
Alex Schroeder
2f49adf605 Merge git.sv.gnu.org:/srv/git/oddmuse 2017-12-17 22:52:39 +01:00
Alex Schroeder
76f9eb7945 markdown-rule: added test 2017-12-17 22:41:40 +01:00
Alex Schroeder
0c2718ca8c markdown-rule: fix /emphasis/
Sometimes I write things like 5./month and that would have
triggered (?<=\P{Word})\/ so I decided to switch that the more
demanding (?<=\s)\/. In order to have italic items start table cells,
however, \G(?<=[|[:space:]])\/ instead.
2017-12-17 22:38:23 +01:00
Alex Schroeder
7109c5be9c markdown-rule: fix /emphasis/
Sometimes I write things like 5./month and that would have
triggered (?<=\P{Word})\/ so I decided to switch that the more
demanding (?<=\s)\/.
2017-12-17 22:34:54 +01:00
Alex Schroeder
e9fad88a10 markdown-rule: fix setext headers
In this situation, the old order caused a problem:

```
---
pandoc: variable
---
```

The result would be <h2>```</h2> and worse to follow.
2017-12-17 22:25:36 +01:00
43 changed files with 2957 additions and 83 deletions

View File

@@ -156,8 +156,11 @@ WikiDataDir=$HOME/oddmuse perl server.pl daemon
}}}
This makes the server available on {{{http://localhost:3000/wiki}}}.
Make sure you create the directory before starting the server!
If you don't, you'll get a strange error:
`STDERR: : No such file or directory at ... perl5/Mojolicious/Plugin/CGI.pm`.
If it works for you, feel free to upgrade to Hypnotoad.
If it works, feel free to upgrade to Hypnotoad.
{{{
WikiDataDir=$HOME/oddmuse hypnotoad server.pl

93
contrib/emacswiki/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,93 @@
// Public Domain
// initial version by Alex Schroeder <alex@gnu.org>
// with many improvements by Evgkeni Sampelnikof
$(function(){
// add fancy classes
$('div.header' ).addClass('container');
$('div.wrapper').addClass('container');
$('div.footer' ).addClass('container');
$('div.footer > .navbar' ).remove();
$('.message > p' ).addClass('alert');
$('img.portrait').addClass('img-polaroid');
$('input:text').addClass('input-medium search-query');
$('textarea').addClass('span12');
$('input:submit').addClass('btn');
$('.download a').addClass('btn btn-success');
$('.footer .gotobar').remove();
$('.footer br').first().remove();
var $gotobar = $('.gotobar')
.after($('<div>').attr('class','navbar')
.append($('<div>').attr('class','navbar-inner')
.append($('<ul>').attr('class', 'nav'))));
var $id = $('h1 a').first().text();
var $list = $('.nav')
.append($('<li>')
.append($('<a>').attr('class', 'brand').attr('href', 'http://www.emacswiki.org/')
.append('Emacs Wiki')));
$('.gotobar a').each(function() {
var $item = $('<li>');
$(this).appendTo($item);
$item.appendTo($list);
if ($(this).text() == $id) {
$item.addClass('active');
}
});
$gotobar.remove();
// search without labels, without button, without language field
$('form.search input[type=submit]').remove();
$('form.search label').remove();
$('form.search input#searchlang').remove();
$('form.search')
.css({'float': 'right',
'margin-top': '10px'});
$('.navbar').append($('form.search'));
// add button style to some links
$('.edit.bar a').addClass('btn');
// add color to Talk button for a non-existing page
$('a.btn.comment.edit').addClass('btn-warning');
// move article link and talk link below title
var $link = $('a.original').add('a.comment');
if ($link) {
$('.header h1').after($('<p>').append($link));
}
// toc
if ($('title').text() == "EmacsWiki: Wikified Emacs Lisp List") {
$('.content').addClass('ell');
}
// tables
$('table').addClass('table');
// minor edit checkbox
$('input[type=checkbox]').addClass('checkbox');
$('input[type=checkbox]').parent().addClass('checkbox');
// clean up admin page
$('li a.clear').parent().remove();
$('li a.index').parent().remove();
$('a[href="http://creativecommons.org/licenses/GPL/2.0/"]')
.parent()
.css({'margin-right': '120px',
'opacity': 0.3,
'padding-top': '1em'});
$('.footer .bar')
.after('<hr />');
var footer_wrapper = $('<div/>')
.addClass('footer_wrapper');
var footer = $('.footer.container');
footer.after(footer_wrapper);
footer_wrapper.append(footer);
var logo_image = $('<img />')
.attr('src', 'http://emacswiki.org/ew_logo.png');
$('.header .navbar .brand').html(logo_image);
});

544
css/alex-2017.css Normal file
View File

@@ -0,0 +1,544 @@
/* This file is in the public domain. */
html{ text-align: center; }
body, rss {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
font-style: normal;
font-size: 14pt;
padding: 1em 3em;
max-width: 72ex;
display: inline-block;
text-align: left;
color: #000;
background-color: #fff;
}
@import url(file:///home/alex/alexschroeder.ch/css/alex-2017.css) print;
@media print {
body {
font-size: 12pt;
}
/* hide all the crap */
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone, div.content a.feed {
display:none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
img[alt="RSS"] { display: none }
a.rss { font-size: 8pt }
}
/* headings: we can use larger sizes if we use a lighter color.
we cannot inherit the font-family because header and footer use a narrow font. */
h1, h2, h3, title {
font-family: inherit;
font-weight: normal;
}
h1, channel title {
font-size: 32pt;
margin: 1em 0 0.5em 0;
padding: 0.4em 0;
}
h2 {
font-size: 18pt;
margin: 2em 0 0 0;
padding: 0;
}
h3 {
font-size: inherit;
font-weight: bold;
padding: 0;
margin: 1em 0 0 0;
clear: both;
}
/* headers in the journal are smaller */
div.journal h1, item title {
font-size: inherit;
padding: 0;
clear: both;
border-bottom: 1px solid #000;
}
div.journal h2 {
font-family: inherit;
font-size: inherit;
}
div.journal h3 {
font-family: inherit;
font-size: inherit;
font-weight: inherit;
font-style: italic;
}
div.journal hr {
visibility: hidden;
}
p.more {
margin-top: 3em;
}
/* Links in headings appear on journal pages. */
h1 a, h2 a, h3 a {
color:inherit;
text-decoration:none;
font-weight: normal;
}
h1 a:visited, h2 a:visited, h3 a:visited {
color: inherit;
}
/* for download buttons and the like */
.button {
display: inline-block;
font-size: 120%;
cursor: pointer;
padding: 0.4em 0.6em;
text-shadow: 0px -1px 0px #ccc;
background-color: #cfa;
border: 1px solid #9d8;
border-radius: 5px;
box-shadow: 0px 1px 3px white inset, 0px 1px 3px black;
}
.button .icon {
color: #363;
text-shadow: 0px -1px 1px white, 0px 1px 3px #666;
}
.button a {
text-decoration: none;
font-weight: normal;
}
/* links */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visibility: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visibility: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
a.number {
text-decoration: none;
}
/* stop floating content from flowing over the footer */
hr {
clear: both;
}
/* the distance between links in the navigation bars */
span.bar a {
margin-right: 1ex;
}
a img {
border: none;
}
/* search box in the top bar */
.header form, .header p {
display: inline;
white-space: nowrap;
}
label[for="searchlang"], #searchlang, .header input[type="submit"] {
/* don't use display: none! http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
visibility: hidden; position: absolute;
}
/* wrap on the iphone */
@media only screen and (max-device-width: 480px) {
}
.header input {
width: 10ex;
}
/* other form fields */
input[type="text"] {
padding: 0;
font-size: 80%;
line-height: 125%;
}
/* code */
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 80%;
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
}
@media print {
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 8pt;
}
}
div.footer form.search {
display: none;
}
div.rc li + li {
margin-top: 1em;
}
div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;
}
div.new {
background-color: #cfffcf;
}
div.refer {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
}
div.message {
background-color:#fee;
color:#000;
}
img.xml {
border:none;
padding:1px;
}
a.small img {
max-width:300px;
}
a.large img {
max-width:600px;
}
div.sister {
margin-right:1ex;
background-color:inherit;
}
div.sister p {
margin-top:0;
}
div.sister hr {
display:none;
}
div.sister img {
border:none;
}
div.near, div.definition {
background-color:#efe;
}
div.sidebar {
float:right;
border:1px dotted #000;
padding:0 1em;
}
div.sidebar ul {
padding-left:1em;
}
/* replacements, features */
ins {
font-style: italic;
text-decoration: none;
}
acronym, abbr {
letter-spacing:0.1em;
font-variant:small-caps;
}
/* Interlink prefix not shown */
a .site, a .separator {
display: none;
}
a cite { font:inherit; }
/* browser borkage */
textarea[name="text"] { width:97%; height:80%; }
textarea[name="summary"] { width:97%; height:3em; }
/* comments */
textarea[name="aftertext"] { width:97%; height:10em; }
div.commentshown {
font-size: 12pt;
padding: 2em 0;
}
div.commenthidden {
display:none;
}
div.commentshown {
display:block;
}
p.comment {
margin-bottom: 0;
}
div.comment {
font-size: 14pt;
}
div.comment h2 {
margin-top: 5em;
}
/* comment pages with username, homepage, and email subscription */
.comment form span { display: block; }
.comment form span label { display: inline-block; width: 10em; }
/* IE sucks */
.comment input#username,
.comment input#homepage,
.comment input#mail { width: 20em; }
/* cal */
div.month { padding:0; margin:0 2ex; }
body > div.month {
float:right;
background-color: inherit;
border:solid thin;
padding:0 1ex;
}
.year > .month {
float:left;
}
.footer {
clear:both;
}
.month .title a.local {
background-color: inherit;
}
.month a.local {
background-color: #ddf;
}
.month a.today {
background-color: #fdd;
}
.month a {
color:inherit;
font-weight:inherit;
text-decoration: none;
background-color: #eee;
}
/* history tables and other tables */
table.history {
border: none;
}
td.history {
border: none;
}
table {
border: none;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 1em;
margin: 1em 2em;
}
table tr td, table 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 img { height: 50%; width: 50%; }
.face img { width: 200px; }
div.left .left, div.right .right {
float:none;
}
.center { text-align:center; }
table.aside {
float:right;
width:40%;
margin-left: 1em;
padding: 1ex;
border: 1px dotted #666;
}
table.aside td {
text-align:left;
}
div.sidebar {
float:right; width: 250px;
text-align: right;
border: none;
margin: 1ex;
}
.bigsidebar {
float:right;
width: 500px;
border: none;
margin-left: 1ex;
font-size: 80%;
}
dl.irc dt { width:20ex; float:left; text-align:right; clear:left; }
dl.irc dt span.time { float:left; }
dl.irc dd { margin-left:22ex; }
/* portrait */
div.footer, div.comment, hr { clear: both; }
.portrait { float: left; font-size: small; margin-right: 1em; }
.portrait a { color: #999; }
div.left { float:left; margin:1em; padding: 0.5em; }
div.left p { display:table-cell; }
div.left p + p { display:table-caption; caption-side:bottom; }
p.table a { float:left; width:20ex; }
p.table + p { clear:both; }
/* mastodon */
div.mastodon { padding: 0 2em }
div.mastodon .status {padding-top: 1ex; border-bottom: 1px solid grey;}
div.mastodon .status:first-child {border-top: 1px solid grey;}
/* rss */
channel * { display: block; }
channel title {
margin-top: 30pt;
}
copyright {
font-size: 14pt;
margin-top: 1em;
}
channel:before {
font-size: 14pt;
display: block;
margin: 1em;
padding: 0.5em;
content: "This document is to be read in a feed reader. The item content is escaped HTML, which makes it hard to read for humans. Sorry!";
color: red;
border: 1px solid red;
}
license {
font-size: 11pt;
margin-bottom: 9pt;
}
contributor:before { content: "Last edited by "; }
contributor:after { content: "."; }
generator:before { content: "Feed generated by "; }
generator:after { content: "."; }
channel description {
font-weight: bold;
}
item description {
font-weight: normal;
margin-bottom: 1em;
}
link, managingEditor, webMaster, license, url,
docs, language,
pubDate, lastBuildDate, ttl, guid, category, comments,
docs, image title, image link,
status, version, diff, history, importance {
display: none;
}

562
css/alex-2018.css Normal file
View File

@@ -0,0 +1,562 @@
/* This file is in the public domain. */
html{ text-align: center; }
body, rss {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
font-style: normal;
font-size: 14pt;
padding: 1em 3em;
max-width: 72ex;
display: inline-block;
text-align: left;
color: #000;
background-color: #fff;
}
@media only screen and (max-device-width: 480px) {
body {
padding: 1ex;
}
}
@import url(file:///home/alex/alexschroeder.ch/css/alex-2017.css) print;
@media print {
body {
font-size: 12pt;
}
/* hide all the crap */
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone, div.content a.feed {
display:none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
img[alt="RSS"] { display: none }
a.rss { font-size: 8pt }
}
/* headings: we can use larger sizes if we use a lighter color.
we cannot inherit the font-family because header and footer use a narrow font. */
h1, h2, h3, title {
font-family: inherit;
font-weight: normal;
}
h1, channel title {
font-size: 32pt;
margin: 1em 0 0.5em 0;
padding: 0.4em 0;
}
h2 {
font-size: 18pt;
margin: 2em 0 0 0;
padding: 0;
}
h3 {
font-size: inherit;
font-weight: bold;
padding: 0;
margin: 1em 0 0 0;
clear: both;
}
/* headers in the journal are smaller */
div.journal h1, item title {
font-size: inherit;
padding: 0;
clear: both;
border-bottom: 1px solid #000;
}
div.journal h2 {
font-family: inherit;
font-size: inherit;
}
div.journal h3 {
font-family: inherit;
font-size: inherit;
font-weight: inherit;
font-style: italic;
}
div.journal hr {
visibility: hidden;
}
p.more {
margin-top: 3em;
}
/* Links in headings appear on journal pages. */
h1 a, h2 a, h3 a {
color:inherit;
text-decoration:none;
font-weight: normal;
}
h1 a:visited, h2 a:visited, h3 a:visited {
color: inherit;
}
/* for download buttons and the like */
.button {
display: inline-block;
font-size: 120%;
cursor: pointer;
padding: 0.4em 0.6em;
text-shadow: 0px -1px 0px #ccc;
background-color: #cfa;
border: 1px solid #9d8;
border-radius: 5px;
box-shadow: 0px 1px 3px white inset, 0px 1px 3px black;
}
.button .icon {
color: #363;
text-shadow: 0px -1px 1px white, 0px 1px 3px #666;
}
.button a {
text-decoration: none;
font-weight: normal;
}
/* links */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visibility: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visibility: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
a.number {
text-decoration: none;
}
/* stop floating content from flowing over the footer */
hr {
clear: both;
}
/* the distance between links in the navigation bars */
span.bar a {
margin-right: 1ex;
}
a img {
border: none;
}
/* search box in the top bar */
.header form, .header p {
display: inline;
white-space: nowrap;
}
label[for="searchlang"], #searchlang, .header input[type="submit"] {
/* don't use display: none! http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
visibility: hidden; position: absolute;
}
.header input {
width: 10ex;
}
/* other form fields */
input[type="text"] {
padding: 0;
font-size: 80%;
line-height: 125%;
}
/* code */
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 75%; /* fits 80ex */
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
}
@media print {
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 8pt;
}
}
div.footer form.search {
display: none;
}
div.rc {
overflow: hidden;
}
div.rc li + li {
margin-top: 1em;
}
div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;
}
div.new {
background-color: #cfffcf;
}
div.refer {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
}
div.message {
background-color:#fee;
color:#000;
}
img.xml {
border:none;
padding:1px;
}
a.small img {
max-width:300px;
}
a.large img {
max-width:600px;
}
div.sister {
margin-right:1ex;
background-color:inherit;
}
div.sister p {
margin-top:0;
}
div.sister hr {
display:none;
}
div.sister img {
border:none;
}
div.near, div.definition {
background-color:#efe;
}
div.sidebar {
float:right;
border:1px dotted #000;
padding:0 1em;
}
div.sidebar ul {
padding-left:1em;
}
/* replacements, features */
ins {
font-style: italic;
text-decoration: none;
}
acronym, abbr {
letter-spacing:0.1em;
font-variant:small-caps;
}
/* Interlink prefix not shown */
a .site, a .separator {
display: none;
}
a cite { font:inherit; }
/* browser borkage */
textarea[name="text"] { width:97%; height:80%; }
textarea[name="summary"] { width:97%; height:3em; }
/* comments */
textarea[name="aftertext"] { width:97%; height:10em; }
div.commentshown {
font-size: 12pt;
padding: 2em 0;
}
div.commenthidden {
display:none;
}
div.commentshown {
display:block;
}
p.comment {
margin-bottom: 0;
}
div.comment {
font-size: 14pt;
}
div.comment h2 {
margin-top: 5em;
}
/* comment pages with username, homepage, and email subscription */
.comment form span { display: block; }
.comment form span label { display: inline-block; width: 10em; }
/* IE sucks */
.comment input#username,
.comment input#homepage,
.comment input#mail { width: 20em; }
/* cal */
div.month { padding:0; margin:0 2ex; }
body > div.month {
float:right;
background-color: inherit;
border:solid thin;
padding:0 1ex;
}
.year > .month {
float:left;
}
.footer {
clear:both;
}
.month .title a.local {
background-color: inherit;
}
.month a.local {
background-color: #ddf;
}
.month a.today {
background-color: #fdd;
}
.month a {
color:inherit;
font-weight:inherit;
text-decoration: none;
background-color: #eee;
}
/* history tables and other tables */
table.history {
border: none;
}
td.history {
border: none;
}
table {
border: none;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 1em;
margin: 1em 2em;
}
table tr td, table 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 img { height: 50%; width: 50%; }
.face img { width: 200px; }
div.left .left, div.right .right {
float:none;
}
.center { text-align:center; }
table.aside {
float:right;
width:40%;
margin-left: 1em;
padding: 1ex;
border: 1px dotted #666;
}
table.aside td {
text-align:left;
}
div.sidebar {
float:right; width: 250px;
text-align: right;
border: none;
margin: 1ex;
}
.bigsidebar {
float:right;
width: 500px;
border: none;
margin-left: 1ex;
font-size: 80%;
}
dl.irc dt { width:20ex; float:left; text-align:right; clear:left; }
dl.irc dt span.time { float:left; }
dl.irc dd { margin-left:22ex; }
/* portrait */
div.footer, div.comment, hr { clear: both; }
.portrait { float: left; font-size: small; margin-right: 1em; }
.portrait a { color: #999; }
div.left { float:left; margin:1em; padding: 0.5em; }
div.left p { display:table-cell; }
div.left p + p { display:table-caption; caption-side:bottom; }
p.table a { float:left; width:20ex; }
p.table + p { clear:both; }
/* mastodon */
div.mastodon { padding: 0 2em }
div.mastodon .status {padding-top: 1ex; border-bottom: 1px solid grey;}
div.mastodon .status:first-child {border-top: 1px solid grey;}
/* terminal "screenshots" */
.terminal {
width: 80%;
margin: 50px auto 100px auto;
padding: 5px;
font-size: 62%; /* fits 80ex */
border: 1px solid #999;
border-radius: 5px;
box-shadow: 0px 25px 50px #999;
}
/* rss */
channel * { display: block; }
channel title {
margin-top: 30pt;
}
copyright {
font-size: 14pt;
margin-top: 1em;
}
channel:before {
font-size: 14pt;
display: block;
margin: 1em;
padding: 0.5em;
content: "This document is to be read in a feed reader. The item content is escaped HTML, which makes it hard to read for humans. Sorry!";
color: red;
border: 1px solid red;
}
license {
font-size: 11pt;
margin-bottom: 9pt;
}
contributor:before { content: "Last edited by "; }
contributor:after { content: "."; }
generator:before { content: "Feed generated by "; }
generator:after { content: "."; }
channel description {
font-weight: bold;
}
item description {
font-weight: normal;
margin-bottom: 1em;
}
link, managingEditor, webMaster, license, url,
docs, language,
pubDate, lastBuildDate, ttl, guid, category, comments,
docs, image title, image link,
status, version, diff, history, importance {
display: none;
}

View File

@@ -172,6 +172,50 @@ span.bar a {
padding: 1ex 0;
}
.red {
background: red;
color: red;
}
.orange {
background: orange;
color: orange;
}
.yellow {
background: yellow;
color: yellow;
}
.green {
background: green;
color: green;
}
.blue {
background: blue;
color: blue;
}
.indigo {
background: indigo;
color: indigo;
}
.violet {
background: violet;
color: violet;
}
.white {
background: white;
color: white;
}
.ip-code {
border: 1px solid #666;
}
hr {
border: none;
color: black;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

15
logo/falky/README.md Normal file
View File

@@ -0,0 +1,15 @@
Falky schreibt:
Zum Thema Rechte am Logo möchte ich erstmal sagen, dass es mir primär
darum ging den Auftritt von oddmuse.org aufzuhübschen. Also du/ihr
sollt durch das Logo unterstützt werden, nicht eingeschränkt.
Welche Lizenz das ab besten leisten, da hab ich keinen Schimmer.
Schön wäre, wenn irgendwo ein verweis auf "Falky [falky.de]"
auftaucht, der soll aber nicht bei jeder Verwendung auftauchen,
sondern irgendwo 'auffindbar' an 'zentraler' Stelle - halt irgendwo
auf der Homepage, dass ich darauf verweisen kann und dass niemand was
anderes behaupten kann. Das wäre es schon. Und ja, falls jemand das
logo für ein anderes Projekt verwenden möchte, kann man ja höflich
nochmal fragen oder so.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-61 -1 177 62">
<rect fill="#FFF" x="-60" y="0" width="175" height="60"/>
<text style="font:400 6px Neris Black" fill="#000" x="2" y="18">powered by</text>
<text style="font:400 16px Neris Thin" fill="#000" x="2" y="34">Oddmuse<tspan style="font-family:Neris Black">.org</tspan></text>
<g id="new" transform="translate(-30,30) scale(.18,.18)" >
<circle fill="#FFF" stroke="#000" stroke-width="3" cx="0" cy="0" r="160.5"/>
<g id="new" transform="rotate(207.2)" >
<path fill="#000" d="m42 -77 q24 0 38 20 c14 -1 42 -18 52 -29 c-1 50 -26 68 -44 70 c0 90 -44 141 -88 141 c-44 0 -88 -51 -88 -141 c-18 -2 -43 -20 -44 -70 c10 11 38 28 52 29 q14 -20 38 -20z"/>
<path fill="#000" transform="translate(0,60) rotate(-14)" d="m -22 46q1 50 22 50q21 0 22 -50z"/>
<path stroke="#FFF" stroke-width="4" fill="#000" d="m0 17 c 30 0 36 -2 46 -2 c 18 0 32 16 32 51 c 0 34 -12 50 -32 50 c-16 0 -26 -5 -46 -5 c-20 0 -30 5 -46 5 c-20 0 -33 -18 -32 -50 c0 -36 16 -52 32 -51 c10 0 16 2 46 2z"/>
<circle fill="#FFF" cx="44" cy="49" r="12"/> <circle fill="#FFF" cx="-44" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#000" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g></g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
logo/falky/oddmuse.icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-100 -100 200 200" >
<rect fill="#D88" x="-100" y="-100" width="200" height="200"/>
<g id="new" transform="rotate(180) translate(0,-19)" >
<path fill="#A00" d="m42 -77 q18 0 33 14 q 12 -6 23 -16 q-2 25 -10 43 c0 110 -44 155 -88 155 c-44 0 -88 -44 -88 -155 q-8 -18 -10 -43 q11 10 23 16 q15 -14 33 -14z"/>
<path stroke="#FFF" stroke-width="6" fill="#A00" d="m0 16 c30 0 38 -2 48 -2 c17 0 33 16 33 52 c0 34 -12 52 -31 52 c-15 0 -30 -7 -50 -7 c-20 0 -35 7 -50 7 c-19 0 -31 -18 -31 -52 c0 -36 16 -52 33 -52 c10 0 18 2 48 2z"/>
<circle fill="#FFF" cx="45" cy="49" r="12"/> <circle fill="#FFF" cx="-45" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#666" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
logo/falky/oddmuse.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-162 -162 324 324">
<circle fill="#FEE" stroke="#000" stroke-width="3" cx="0" cy="0" r="160.5"/>
<g id="new" transform="rotate(210)" >
<path fill="#A00" d="m42 -77 q24 0 38 20 c14 -1 42 -18 52 -29 c-1 50 -26 68 -44 70 c0 90 -44 141 -88 141 c-44 0 -88 -51 -88 -141 c-18 -2 -43 -20 -44 -70 c10 11 38 28 52 29 q14 -20 38 -20z"/>
<path fill="#A00" transform="translate(0,60) rotate(-14)" d="m -22 46q1 50 22 50q21 0 22 -50z"/>
<path stroke="#FFF" stroke-width="4" fill="#D88" d="m0 17 c 30 0 36 -2 46 -2 c 18 0 32 16 32 51 c 0 34 -12 50 -32 50 c-16 0 -26 -5 -46 -5 c-20 0 -30 5 -46 5 c-20 0 -33 -18 -32 -50 c0 -36 16 -52 32 -51 c10 0 16 2 46 2z"/>
<circle fill="#FFF" cx="44" cy="49" r="12"/> <circle fill="#FFF" cx="-44" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#666" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-162 -162 324 324">
<circle fill="#FFF" stroke="#000" stroke-width="3" cx="0" cy="0" r="160.5"/>
<text style="font:400 27px Neris Black" fill="#000" text-anchor="middle" x="-5" y="137">Oddmuse</text>
<g id="new" transform="rotate(210)" >
<path fill="#000" d="m42 -77 q24 0 38 20 c14 -1 42 -18 52 -29 c-1 50 -26 68 -44 70 c0 90 -44 141 -88 141 c-44 0 -88 -51 -88 -141 c-18 -2 -43 -20 -44 -70 c10 11 38 28 52 29 q14 -20 38 -20z"/>
<path fill="#000" transform="translate(0,60) rotate(-14)" d="m -22 46q1 50 22 50q21 0 22 -50z"/>
<path stroke="#FFF" stroke-width="4" fill="#000" d="m0 17 c 30 0 36 -2 46 -2 c 18 0 32 16 32 51 c 0 34 -12 50 -32 50 c-16 0 -26 -5 -46 -5 c-20 0 -30 5 -46 5 c-20 0 -33 -18 -32 -50 c0 -36 16 -52 32 -51 c10 0 16 2 46 2z"/>
<circle fill="#FFF" cx="44" cy="49" r="12"/> <circle fill="#FFF" cx="-44" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#000" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
logo/falky/testmonial.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

4
logo/murray/README.md Normal file
View File

@@ -0,0 +1,4 @@
The logo is licensed under the [GNU Free Documentation
License](http://www.emacswiki.org/FDL) as well as the [CC BY
2.0](http://creativecommons.org/licenses/by/2.0/) license. The author
is [Murray Altheim](http://www.altheim.com/murray/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="0.41"
sodipodi:docname="oddmuse-logo.svg"
sodipodi:docbase="/Users/alex/Pictures/Oddmuse"
height="1000pt"
width="1000pt"
sodipodi:version="0.32"
id="svg1">
<metadata
id="metadata32">
<rdf:RDF
id="RDF34">
<cc:Work
id="Work36"
rdf:about="">
<dc:format
id="format38">image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
id="type40" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3" />
<sodipodi:namedview
inkscape:current-layer="svg1"
inkscape:window-y="22"
inkscape:window-x="0"
inkscape:window-height="535"
inkscape:window-width="640"
inkscape:cy="625.00000"
inkscape:cx="625.00000"
inkscape:zoom="0.28960000"
id="base" />
<g
transform="translate(-155.7935,22.13440)"
id="g176">
<path
transform="matrix(1.021740,0.000000,0.000000,1.021740,-299.4160,85.75640)"
sodipodi:ry="148.93437"
sodipodi:rx="142.30524"
sodipodi:cy="475.27606"
sodipodi:cx="1066.8474"
d="M 1209.1526 475.27606 A 142.30524 148.93437 0 1 1 924.54218,475.27606 A 142.30524 148.93437 0 1 1 1209.1526 475.27606 z"
id="path166"
style="fill:#ffde00;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:22.021299;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
<path
transform="matrix(0.591973,0.000000,0.000000,0.523224,159.0800,309.0390)"
sodipodi:ry="148.93437"
sodipodi:rx="142.30524"
sodipodi:cy="475.27606"
sodipodi:cx="1066.8474"
d="M 1209.1526 475.27606 A 142.30524 148.93437 0 1 1 924.54218,475.27606 A 142.30524 148.93437 0 1 1 1209.1526 475.27606 z"
id="path168"
style="fill:#ffde00;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:38.609402;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
<rect
transform="translate(-1.416020,0.000000)"
y="561.16870"
x="694.39026"
height="88.691116"
width="195.30150"
id="rect170"
style="font-size:12.000000;fill:#ffde00;fill-opacity:1.0000000;fill-rule:evenodd;stroke-width:1.0000000pt" />
<g
id="g173">
<path
transform="translate(185.0000,-158.7500)"
sodipodi:ry="31.250000"
sodipodi:rx="25.000000"
sodipodi:cy="783.75000"
sodipodi:cx="565.00000"
d="M 590.00000 783.75000 A 25.000000 31.250000 0 1 1 540.00000,783.75000 A 25.000000 31.250000 0 1 1 590.00000 783.75000 z"
id="path171"
style="fill:#000000;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
<path
transform="translate(266.2500,-158.7500)"
sodipodi:ry="31.250000"
sodipodi:rx="25.000000"
sodipodi:cy="783.75000"
sodipodi:cx="565.00000"
d="M 590.00000 783.75000 A 25.000000 31.250000 0 1 1 540.00000,783.75000 A 25.000000 31.250000 0 1 1 590.00000 783.75000 z"
id="path172"
style="fill:#000000;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -198,8 +198,7 @@ sub MakeLaTeX {
}
if (not $error and IsFile('srender1.png') and not ZeroSize('srender1.png')) {
my $png = ReadFileOrDie("srender1.png");
WriteStringToFile ("$LatexDir/$hash.png", $png);
Rename("srender1.png", "$LatexDir/$hash.png");
} else {
$error = "[Error retrieving image for $latex]";
}

View File

@@ -48,12 +48,6 @@ sub MarkdownRule {
return CloseHtmlEnvironments()
. AddHtmlEnvironment("p");
}
# setext headers
elsif ($bol and m/\G((\s*\n)*(.+?)[ \t]*\n(-+|=+)[ \t]*\n)/cg) {
return CloseHtmlEnvironments()
. (substr($4,0,1) eq '=' ? $q->h2($3) : $q->h3($3))
. AddHtmlEnvironment('p');
}
# > blockquote
# with continuation
elsif ($bol and m/\G&gt;/cg) {
@@ -65,8 +59,8 @@ sub MarkdownRule {
return CloseHtmlEnvironments() . $q->pre($1)
. AddHtmlEnvironment("p");
}
# ` = code
elsif (m/\G`([^`].*?)`/cg) {
# ` = code may not start with a newline
elsif (m/\G`([^\n`][^`]*)`/cg) {
return $q->code($1);
}
# ***bold and italic***
@@ -92,8 +86,8 @@ sub MarkdownRule {
return AddOrCloseHtmlEnvironment('del');
}
# indented lists = nested lists
elsif ($bol and m/\G(\s*\n)*()([*-]|\d+\.)[ \t]*/cg
or InElement('li') && m/\G(\s*\n)+( *)([*-]|\d+\.)[ \t]*/cg) {
elsif ($bol and m/\G(\s*\n)*()([*-]|\d+\.)[ \t]+/cg
or InElement('li') && m/\G(\s*\n)+( *)([*-]|\d+\.)[ \t]+/cg) {
my $nesting_goal = int(length($2)/4) + 1;
my $tag = ($3 eq '*' or $3 eq '-') ? 'ul' : 'ol';
my $nesting_current = 0;
@@ -176,6 +170,12 @@ sub MarkdownRule {
$params{-title} = $title if $title;
return $q->a(\%params, $text);
}
# setext headers (must come after block quotes)
elsif ($bol and m/\G((\s*\n)*(.+?)[ \t]*\n(-+|=+)[ \t]*\n)/cg) {
return CloseHtmlEnvironments()
. (substr($4,0,1) eq '=' ? $q->h2($3) : $q->h3($3))
. AddHtmlEnvironment('p');
}
return;
}
@@ -190,7 +190,7 @@ sub MarkdownExtraRule {
elsif (InElement('em', 'style="font-style: normal; text-decoration: underline"') and m/\G_/cg) {
return CloseHtmlEnvironment('em');
}
elsif ($bol and m/\G_/cg or m/\G(?<=\P{Word})_/cg) {
elsif ($bol and m/\G_/cg or m/\G(?<=\P{Word})_(?=\S)/cg) {
return AddHtmlEnvironment('em', 'style="font-style: normal; text-decoration: underline"');
}
# //italic//
@@ -201,7 +201,7 @@ sub MarkdownExtraRule {
elsif (InElement('em') and m/\G\//cg) {
return CloseHtmlEnvironment('em');
}
elsif ($bol and m/\G\//cg or m/\G(?<=\P{Word})\//cg) {
elsif ($bol and m/\G\//cg or m/\G(?<=[|[:space:]])\/(?=\S)/cg) {
return AddHtmlEnvironment('em');
}
return;

View File

@@ -206,10 +206,12 @@ sub send_mail {
sub main {
my $rss = get_rss();
return unless @{$rss->{items}};
my $subscribers = get_subscribers();
return unless %{$subscribers};
send_files($rss, $subscribers);
if (@{$rss->{items}}) {
my $subscribers = get_subscribers();
if (%{$subscribers}) {
send_files($rss, $subscribers);
}
}
update_timestamp();
}

87
scripts/strip-host.pl Executable file
View File

@@ -0,0 +1,87 @@
#! /usr/bin/perl -w
# Copyright (C) 2018 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/>.
sub ParseData {
my $data = shift;
my %result;
while ($data =~ /(\S+?): (.*?)(?=\n[^ \t]|\Z)/sg) {
my ($key, $value) = ($1, $2);
$value =~ s/\n\t/\n/g;
$result{$key} = $value;
}
return %result;
}
sub EncodePage {
my @data = @_;
my $result = '';
$result .= (shift @data) . ': ' . EscapeNewlines(shift @data) . "\n" while (@data);
return $result;
}
sub EscapeNewlines {
$_[0] =~ s/\n/\n\t/g; # modify original instead of copying
return $_[0];
}
sub main {
die "There is no temp directory, here.\n"
. "Perhaps this isn't an Oddmuse data directory?\n"
unless -d 'temp';
die "The main lock already exists in the temp directory.\n"
if -d "temp/lockmain";
mkdir "temp/lockmain" or die "Cannot create main lock in temp: $!\n";
local $/ = undef; # Read complete files
foreach my $dir (qw/keep page/) {
warn "Did not find the $dir directory.\n" unless -d $dir;
}
# include dotfiles!
my $t = 0;
my $n = 0;
foreach my $file (glob("page/*.pg page/.*.pg"),
glob("keep/*/*.kp keep/.*.kp")) {
$t++;
open(my $fh, '<', $file) or die "Cannot read $file file: $!\n";
my $data = <$fh>;
close($fh);
next unless $data;
my %result = ParseData($data);
if (exists($result{host}) or exists($result{ip})) {
delete($result{host});
delete($result{ip});
open($fh,'>', "$file~") or die "Cannot $file~: $!\n";
print $fh EncodePage(%result);
close($fh);
rename("$file~", $file) or die "Cannot rename $file~ to $file: $!\n";
$n++;
}
}
rmdir "temp/lockmain" or die "Cannot remove main lock: $!\n";
print "I looked at $t files and found $n host or ip keys which I removed.\n";
}
if (@ARGV) {
print qq{
Usage: $0 [--page DIR]
Goes through the wiki and removes the hostname or IP number from page and
keep files. Make a backup before running this script! Run this script in
your data directory.
}
} else {
main ();
}

882
stuff/gopher-server.pl Executable file
View File

@@ -0,0 +1,882 @@
#!/usr/bin/env perl
# Copyright (C) 20172018 Alex Schroeder <alex@gnu.org>
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
use strict;
use 5.10.0;
use base qw(Net::Server::Fork); # any personality will do
use MIME::Base64;
use Text::Wrap;
use List::Util qw(first);
our($RunCGI, $DataDir, %IndexHash, @IndexList, $IndexFile, $TagFile, $q,
%Page, $OpenPageName, $MaxPost, $ShowEdits, %Locks, $CommentsPattern,
$CommentsPrefix, $EditAllowed, $NoEditFile, $SiteName, $ScriptName);
my $external_image_path = '/home/alex/alexschroeder.ch/pics/';
# Sadly, we need this information before doing anything else
my %args = (proto => 'ssl');
for (grep(/--wiki_(key|cert)_file=/, @ARGV)) {
$args{SSL_cert_file} = $1 if /--wiki_cert_file=(.*)/;
$args{SSL_key_file} = $1 if /--wiki_key_file=(.*)/;
}
if ($args{SSL_cert_file} and not $args{SSL_key_file}
or not $args{SSL_cert_file} and $args{SSL_key_file}) {
die "I must have both --wiki_key_file and --wiki_cert_file\n";
} elsif ($args{SSL_cert_file} and $args{SSL_key_file}) {
OddMuse->run(%args);
} else {
OddMuse->run;
}
sub options {
my $self = shift;
my $prop = $self->{'server'};
my $template = shift;
# setup options in the parent classes
$self->SUPER::options($template);
# add a single value option
$prop->{wiki} ||= undef;
$template->{wiki} = \$prop->{wiki};
$prop->{wiki_dir} ||= undef;
$template->{wiki_dir} = \$prop->{wiki_dir};
$prop->{wiki_pages} ||= [];
$template->{wiki_pages} = $prop->{wiki_pages};
$prop->{menu} ||= [];
$template->{menu} = $prop->{menu};
$prop->{menu_file} ||= [];
$template->{menu_file} = $prop->{menu_file};
# $prop->{wiki_pem_file} ||= undef;
# $template->{wiki_pem_file} = $prop->{wiki_pem_file};
}
sub post_configure_hook {
my $self = shift;
$self->write_help if $ARGV[0] eq '--help';
$DataDir = $self->{server}->{wiki_dir} || $ENV{WikiDataDir} || '/tmp/oddmuse';
$self->log(3, "PID $$");
$self->log(3, "Host " . ("@{$self->{server}->{host}}" || "*"));
$self->log(3, "Port @{$self->{server}->{port}}");
$self->log(3, "Wiki data dir is $DataDir\n");
$RunCGI = 0;
my $wiki = $self->{server}->{wiki} || "./wiki.pl";
$self->log(1, "Running $wiki\n");
unless (my $return = do $wiki) {
$self->log(1, "couldn't parse wiki library $wiki: $@") if $@;
$self->log(1, "couldn't do wiki library $wiki: $!") unless defined $return;
$self->log(1, "couldn't run wiki library $wiki") unless $return;
}
# make sure search is sorted newest first because NewTagFiltered resorts
*OldGopherFiltered = \&Filtered;
*Filtered = \&NewGopherFiltered;
}
my $usage = << 'EOT';
This server serves a wiki as a gopher site.
It implements Net::Server and thus all the options available to
Net::Server are also available here. Additional options are available:
wiki - this is the path to the Oddmuse script
wiki_dir - this is the path to the Oddmuse data directory
wiki_pages - this is a page to show on the entry menu
menu - this is the description of a gopher menu to prepend
menu_file - this is the filename of the gopher menu to prepend
wiki_cert_file - the filename containing a certificate in PEM format
wiki_key_file - the filename containing a private key in PEM format
For many of the options, more information can be had in the Net::Server
documentation. This is important if you want to daemonize the server. You'll
need to use --pid_file so that you can stop it using a script, --setsid to
daemonize it, --log_file to write keep logs, and you'll net to set the user or
group using --user or --group such that the server has write access to the data
directory.
For testing purposes, you can start with the following:
--port=7070
The port to listen to, defaults to a random port.
--log_level=4
The log level to use, defaults to 2.
--wiki_dir=/var/oddmuse
The wiki directory, defaults to the value of the "WikiDataDir" environment
variable or "/tmp/oddmuse".
--wiki_lib=/home/alex/src/oddmuse/wiki.pl
The Oddmuse main script, defaults to "./wiki.pl".
--wiki_pages=SiteMap
This adds a page to the main index. Can be used multiple times.
--help
Prints this message.
Example invocation:
/home/alex/src/oddmuse/stuff/gopher-server.pl \
--port=7070 \
--wiki=/home/alex/src/oddmuse/wiki.pl \
--pid_file=/tmp/oddmuse/gopher.pid \
--wiki_dir=/tmp/oddmuse \
--wiki_pages=Homepage \
--wiki_pages=Gopher
Run the script and test it:
echo | nc localhost 7070
lynx gopher://localhost:7070
If you want to use SSL, you need to provide PEM files containing certificate and
private key. To create self-signed files, for example:
openssl req -new -x509 -days 365 -nodes -out \
gopher-server-cert.pem -keyout gopher-server-key.pem
Make sure the common name you provide matches your domain name!
Note that parameters should not contain spaces. Thus:
/home/alex/src/oddmuse/stuff/gopher-server.pl \
--port=7070 \
--log_level=3 \
--wiki=/home/alex/src/oddmuse/wiki.pl \
--wiki_dir=/home/alex/alexschroeder \
--menu=Moku_Pona_Updates \
--menu_file=~/.moku-pona/updates.txt \
--menu=Moku_Pona_Sites \
--menu_file=~/.moku-pona/sites.txt
EOT
run();
sub NewGopherFiltered {
my @pages = OldGopherFiltered(@_);
@pages = sort newest_first @pages;
return @pages;
}
sub normal_to_free {
my $title = shift;
$title =~ s/_/ /g;
return $title;
}
sub print_text {
my $self = shift;
my $text = shift;
print($text); # bytes
}
sub print_menu {
my $self = shift;
my $display = shift;
my $selector = shift;
my $host = shift
|| $self->{server}->{host}->[0]
|| $self->{server}->{sockaddr};
my $port = shift
|| $self->{server}->{port}->[0]
|| $self->{server}->{sockport};
my $encoded = shift;
$selector = join('/', map { UrlEncode($_) } split(/\//, $selector)) unless $encoded;
$self->print_text(join("\t", $display, $selector, $host, $port)
. "\r\n");
}
sub print_info {
my $self = shift;
my $info = shift;
$self->print_menu("i$info", "");
}
sub print_error {
my $self = shift;
my $error = shift;
$self->print_menu("3$error", "");
}
sub serve_main_menu {
my $self = shift;
my $more = shift;
$self->log(3, "Serving main menu");
$self->print_info("Welcome to the Gopher version of this wiki.");
$self->print_info("");
$self->print_info("Phlog:");
my @pages = sort { $b cmp $a } grep(/^\d\d\d\d-\d\d-\d\d/, @IndexList);
for my $id (@pages[0..9]) {
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
$self->print_menu("1" . "More...", "do/more");
$self->print_info("");
for my $id (@{$self->{server}->{wiki_pages}}) {
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
for my $id (@{$self->{server}->{menu}}) {
$self->print_menu("1" . normal_to_free($id), "map/$id");
}
$self->print_menu("1" . "Recent Changes", "do/rc");
$self->print_menu("0" . "Gopher RSS", "do/rss");
$self->print_menu("7" . "Find matching page titles", "do/match");
$self->print_menu("7" . "Full text search", "do/search");
$self->print_menu("1" . "Index of all pages", "do/index");
if ($TagFile) {
$self->print_menu("1" . "Index of all tags", "do/tags");
}
if ($EditAllowed and not IsFile($NoEditFile)) {
$self->print_menu("w" . "New page", "do/new");
}
}
sub serve_phlog_archive {
my $self = shift;
$self->log(3, "Serving phlog archive");
my @pages = sort { $b cmp $a } grep(/^\d\d\d\d-\d\d-\d\d/, @IndexList);
for my $id (@pages) {
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
}
sub serve_index {
my $self = shift;
$self->log(3, "Serving index of all pages");
for my $id (sort newest_first @IndexList) {
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
}
sub serve_match {
my $self = shift;
my $match = shift;
$self->log(3, "Serving pages matching " . UrlEncode($match));
$self->print_info("Use a regular expression to match page titles.");
$self->print_info("Spaces in page titles are underlines, '_'.");
for my $id (sort newest_first grep(/$match/i, @IndexList)) {
$self->print_menu( "1" . normal_to_free($id), "$id/menu");
}
}
sub serve_search {
my $self = shift;
my $str = shift;
$self->log(3, "Serving search result for " . UrlEncode($str));
$self->print_info("Use regular expressions separated by spaces.");
SearchTitleAndBody($str, sub {
my $id = shift;
$self->print_menu("1" . normal_to_free($id), "$id/menu");
});
}
sub serve_tags {
my $self = shift;
$self->log(3, "Serving tag cloud");
# open the DB file
my %h = TagReadHash();
my %count = ();
foreach my $tag (grep !/^_/, keys %h) {
$count{$tag} = @{$h{$tag}};
}
foreach my $id (sort { $count{$b} <=> $count{$a} } keys %count) {
$self->print_menu("1" . normal_to_free($id), "$id/tag");
}
}
sub serve_rc {
my $self = shift;
my $showedit = $ShowEdits = shift;
$self->log(3, "Serving recent changes"
. ($showedit ? " including minor changes" : ""));
$self->print_info("Recent Changes");
if ($showedit) {
$self->print_menu("1" . "Skip minor edits", "do/rc");
} else {
$self->print_menu("1" . "Show minor edits", "do/rc/showedits");
}
ProcessRcLines(
sub {
my $date = shift;
$self->print_info("");
$self->print_info("$date");
$self->print_info("");
},
sub {
my($id, $ts, $author_host, $username, $summary, $minor, $revision,
$languages, $cluster, $last) = @_;
$self->print_menu("1" . normal_to_free($id), "$id/menu");
for my $line (split(/\n/, wrap(' ', ' ', $summary))) {
$self->print_info($line);
}
});
}
sub serve_rss {
my $self = shift;
$self->log(3, "Serving Gopher RSS");
my $host = shift
|| $self->{server}->{host}->[0]
|| $self->{server}->{sockaddr};
my $port = shift
|| $self->{server}->{port}->[0]
|| $self->{server}->{sockport};
my $gopher = "gopher://$host:$port/"; # use gophers for TLS?
local $ScriptName = $gopher;
my $rss = GetRcRss();
$rss =~ s!$ScriptName\?action=rss!${gopher}1do/rss!g;
$rss =~ s!$ScriptName\?action=history;id=([^[:space:]<]*)!${gopher}1$1/history!g;
$rss =~ s!$ScriptName/([^[:space:]<]*)!${gopher}0$1!g;
$rss =~ s!<wiki:diff>.*</wiki:diff>\n!!g;
print $rss;
}
sub serve_map {
my $self = shift;
my $id = shift;
$self->log(3, "Serving map " . UrlEncode($id));
my @menu = @{$self->{server}->{menu}};
my $i = first { $id eq $menu[$_] } 0..$#menu;
my $file = $self->{server}->{menu_file}->[$i];
if (-f $file and open(my $fh, '<:encoding(UTF-8)', $file)) {
local $/ = undef;
my $text = <$fh>;
$self->log(4, "Map has " . length($text) . " characters");
$self->print_text($text);
} else {
$self->log(1, "Error reading $file");
}
}
sub serve_page_comment_link {
my $self = shift;
my $id = shift;
my $revision = shift;
if (not $revision and $CommentsPattern) {
if ($id =~ /$CommentsPattern/) {
my $original = $1;
# sometimes we are on a comment page and cannot derive the original
$self->print_menu("1" . "Back to the original page",
"$original/menu") if $original;
$self->print_menu("w" . "Add a comment", "$id/append/text");
} else {
my $comments = $CommentsPrefix . $id;
$self->print_menu("1" . "Comments on this page", "$comments/menu");
}
}
}
sub serve_page_history_link {
my $self = shift;
my $id = shift;
my $revision = shift;
if (not $revision) {
$self->print_menu("1" . "Page History", "$id/history");
}
}
sub serve_file_page_menu {
my $self = shift;
my $id = shift;
my $type = shift;
my $revision = shift;
my $code = substr($type, 0, 6) eq 'image/' ? 'I' : '9';
$self->log(3, "Serving file page menu for " . UrlEncode($id));
$self->print_menu($code . normal_to_free($id)
. ($revision ? "/$revision" : ""), $id);
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
}
sub serve_text_page_menu {
my $self = shift;
my $id = shift;
my $page = shift;
my $revision = shift;
$self->log(3, "Serving text page menu for " . UrlEncode($id)
. ($revision ? "/$revision" : ""));
$self->print_info("The text of this page:");
$self->print_menu("0" . normal_to_free($id),
$id . ($revision ? "/$revision" : ""));
$self->print_menu("h" . normal_to_free($id),
$id . ($revision ? "/$revision" : "") . "/html");
$self->print_menu("w" . "Replace " . normal_to_free($id),
$id . "/write/text");
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
my $first = 1;
while ($page->{text} =~ /\[\[([^\]|]*)(?:\|([^\]]*))?\]\]|\[(https?:\/\/\S+)\s+([^\]]*)\]|\[gopher:\/\/([^:\/]*)(?::(\d+))?(?:\/(\d)(\S+))?\s+([^\]]+)\]/g) {
my ($title, $text, $url, $hostname, $port, $type, $selector)
= ($1, $2||$4||$9, $3, $5, $6||70, $7||1, $8);
if ($first) {
$self->print_info("");
$self->print_info("Links leaving " . normal_to_free($id) . ":");
$first = 0;
}
if ($hostname) {
$self->print_text(join("\t", $type . $text, $selector, $hostname, $port) . "\r\n");
} elsif ($url) {
$self->print_menu("h$text", "URL:" . $url, undef, undef, 1);
} elsif ($title and substr($title, 0, 4) eq 'tag:') {
$self->print_menu("1" . ($text||substr($title, 4)),
substr($title, 4) . "/tag");
} elsif ($title =~ s!^image[/a-z]* external:!pics/!) {
$self->print_menu("I" . $text||$title, $title);
} elsif ($title) {
$title =~ s!^image[/a-z]*:!!i;
$self->print_menu("1" . ($text||$title), $title . "/menu");
}
}
$first = 1;
while ($page->{text} =~ /\[https?:\/\/gopher\.floodgap\.com\/gopher\/gw\?a=gopher%3a%2f%2f(.*?)(?:%3a(\d+))?%2f(.)(\S+)\s+([^\]]+)\]/gi) {
my ($hostname, $port, $type, $selector, $text) = ($1, $2||"70", $3, $4, $5);
if ($first) {
$self->print_info("");
$self->print_info("Gopher links (via Floodgap):");
$first = 0;
}
$selector =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eig; # url unescape
$self->print_text(join("\t", $type . $text, $selector, $hostname, $port)
. "\r\n");
}
if ($page->{text} =~ m/<journal search tag:(\S+)>\s*/) {
my $tag = $1;
$self->print_info("");
$self->serve_tag_list($tag);
}
}
sub serve_page_history {
my $self = shift;
my $id = shift;
$self->log(3, "Serving history of " . UrlEncode($id));
OpenPage($id);
$self->print_menu("1" . normal_to_free($id) . " (current)", "$id/menu");
$self->print_info(CalcTime($Page{ts})
. " by " . GetAuthor($Page{username})
. ($Page{summary} ? ": $Page{summary}" : "")
. ($Page{minor} ? " (minor)" : ""));
foreach my $revision (GetKeepRevisions($OpenPageName)) {
my $keep = GetKeptRevision($revision);
$self->print_menu("1" . normal_to_free($id) . " ($keep->{revision})",
"$id/$keep->{revision}/menu");
$self->print_info(CalcTime($keep->{ts})
. " by " . GetAuthor($keep->{username})
. ($keep->{summary} ? ": $keep->{summary}" : "")
. ($keep->{minor} ? " (minor)" : ""));
}
}
sub get_page {
my $id = shift;
my $revision = shift;
my $page;
if ($revision) {
$OpenPageName = $id;
$page = GetKeptRevision($revision);
} else {
OpenPage($id);
$page = \%Page;
}
return $page;
}
sub serve_page_menu {
my $self = shift;
my $id = shift;
my $revision = shift;
my $page = get_page($id, $revision);
if (my ($type) = TextIsFile($page->{text})) {
$self->serve_file_page_menu($id, $type, $revision);
} else {
$self->serve_text_page_menu($id, $page, $revision);
}
}
sub serve_file_page {
my $self = shift;
my $id = shift;
my $page = shift;
$self->log(3, "Serving " . UrlEncode($id) . " as file");
my ($encoded) = $page->{text} =~ /^[^\n]*\n(.*)/s;
$self->log(4, UrlEncode($id) . " has " . length($encoded)
. " bytes of MIME encoded data");
my $data = decode_base64($encoded);
$self->log(4, UrlEncode($id) . " has " . length($data)
. " bytes of binary data");
binmode(STDOUT, ":raw");
print($data);
}
sub serve_text_page {
my $self = shift;
my $id = shift;
my $page = shift;
my $text = $page->{text};
$self->log(3, "Serving " . UrlEncode($id) . " as " . length($text)
. " bytes of text");
$text =~ s/^\./../mg;
$self->print_text($text);
}
sub serve_page {
my $self = shift;
my $id = shift;
my $revision = shift;
my $page = get_page($id, $revision);
if (my ($type) = TextIsFile($page->{text})) {
$self->serve_file_page($id, $page);
} else {
$self->serve_text_page($id, $page);
}
}
sub serve_page_html {
my $self = shift;
my $id = shift;
my $revision = shift;
my $page = get_page($id, $revision);
$self->log(3, "Serving " . UrlEncode($id) . " as HTML");
my $title = normal_to_free($id);
print GetHtmlHeader(Ts('%s:', $SiteName) . ' ' . UnWiki($title), $id);
print GetHeaderDiv($id, $title);
print $q->start_div({-class=>'wrapper'});
if ($revision) {
# no locking of the file, no updating of the cache
PrintWikiToHTML($page->{text});
} else {
PrintPageHtml();
}
PrintFooter($id, $revision);
}
sub serve_redirect {
my $self = shift;
my $url = shift;
print qq{<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta http-equiv="refresh" content="0; url=$url">
<title>Redirection</title>
</head>
<body>
If you are not redirected automatically, follow this <a href='$url'>link</a>.
</body>
</html>
};
}
sub serve_image {
my $self = shift;
my $pic = shift;
my $file = $external_image_path . $pic;
# no tricks
if ($file !~ /\.\./ and $file !~ /\/\//
and -f $file and open(my $fh, "<", $file)) {
local $/ = undef;
my $data = <$fh>;
$self->log(4, $pic . " has " . length($data)
. " bytes of binary data");
binmode(STDOUT, ":raw");
print($data);
} else {
$self->log(1, "Error reading $file: $!");
}
}
sub newest_first {
my ($A, $B) = ($a, $b);
if ($A =~ /^\d\d\d\d-\d\d-\d\d/ and $B =~ /^\d\d\d\d-\d\d-\d\d/) {
return $B cmp $A;
}
$A cmp $B;
}
sub serve_tag_list {
my $self = shift;
my $tag = shift;
$self->print_info("Search result for tag $tag:");
for my $id (sort newest_first TagFind($tag)) {
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
}
sub serve_tag {
my $self = shift;
my $tag = shift;
$self->log(3, "Serving tag " . UrlEncode($tag));
if ($IndexHash{$tag}) {
$self->print_info("This page is about the tag $tag.");
$self->print_menu("1" . normal_to_free($tag), "$tag/menu");
$self->print_info("");
}
$self->serve_tag_list($tag);
}
sub serve_error {
my $self = shift;
my $id = shift;
my $error = shift;
$self->log(3, "Error ('" . UrlEncode($id) . "'): $error");
$self->print_error("Error ('" . UrlEncode($id) . "'): $error");
}
sub write_help {
my $self = shift;
my @lines = split(/\n/, <<"EOF");
This is how your document should start:
```
username: Alex Schroeder
summary: typo fixed
```
This is the text of your document.
Just write whatever.
Note the space after the colon for metadata fields.
More metadata fields are allowed:
`minor` is 1 if this is a minor edit. The default is 0.
EOF
for my $line (@lines) {
$self->print_info($line);
}
}
sub write_page_ok {
my $self = shift;
my $id = shift;
$self->print_info("Page was saved.");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
sub write_page_error {
my $self = shift;
my $error = shift;
$self->log(4, "Not saved: $error");
$self->print_error("Page was not saved: $error");
map { ReleaseLockDir($_); } keys %Locks;
}
sub write_data {
my $self = shift;
my $id = shift;
my $data = shift;
my $param = shift||'text';
SetParam($param, $data);
my $error;
eval {
local *ReBrowsePage = sub {};
local *ReportError = sub { $error = shift };
DoPost($id);
};
if ($error) {
$self->write_page_error($error);
} else {
$self->write_page_ok($id);
}
}
sub write_file_page {
my $self = shift;
my $id = shift;
my $data = shift;
my $type = shift || 'application/octet-stream';
$self->write_page_error("page title is missing") unless $id;
$self->log(3, "Posting " . length($data) . " bytes of $type to page "
. UrlEncode($id));
# no metadata
$self->write_data($id, "#FILE $type\n" . encode_base64($data));
}
sub write_text {
my $self = shift;
my $id = shift;
my $data = shift;
my $param = shift;
utf8::decode($data);
my ($lead, $meta, $text) = split(/^```\s*(?:meta)?\n/m, $data, 3);
if (not $lead and $meta) {
while ($meta =~ /^([a-z-]+): (.*)/mg) {
if ($1 eq 'minor' and $2) {
SetParam('recent_edit', 'on'); # legacy UseMod parameter name
} else {
SetParam($1, $2);
if ($1 eq "title") {
$id = $2;
}
}
}
$self->log(3, ($param eq 'text' ? "Posting" : "Appending")
. " " . length($text) . " characters (with metadata) to page $id");
$self->write_data($id, $text, $param);
} else {
# no meta data
$self->log(3, ($param eq 'text' ? "Posting" : "Appending")
. " " . length($data) . " characters to page $id") if $id;
$self->write_data($id, $data, $param);
}
}
sub write_text_page {
my $self = shift;
$self->write_text(@_, 'text');
}
sub append_text_page {
my $self = shift;
$self->write_text(@_, 'aftertext');
}
sub read_file {
my $self = shift;
my $length = shift;
$length = $MaxPost if $length > $MaxPost;
local $/ = \$length;
my $buf .= <STDIN>;
$self->log(4, "Received " . length($buf) . " bytes (max is $MaxPost)");
return $buf;
}
sub read_text {
my $self = shift;
my $buf;
while (1) {
my $line = <STDIN>;
if (length($line) == 0) {
sleep(1); # wait for input
next;
}
last if $line =~ /^.\r?\n/m;
$buf .= $line;
if (length($buf) > $MaxPost) {
$buf = substr($buf, 0, $MaxPost);
last;
}
}
$self->log(4, "Received " . length($buf) . " bytes (max is $MaxPost)");
utf8::decode($buf);
$self->log(4, "Received " . length($buf) . " characters");
return $buf;
}
sub process_request {
my $self = shift;
# clear cookie and all that
$q = undef;
Init();
# refresh list of pages
if (IsFile($IndexFile) and ReadIndex()) {
# we're good
} else {
RefreshIndex();
}
eval {
local $SIG{'ALRM'} = sub {
$self->log(1, "Timeout!");
die "Timed Out!\n";
};
alarm(10); # timeout
my $selector = <STDIN>; # no loop
$selector = UrlDecode($selector); # assuming URL-encoded UTF-8
$selector =~ s/\s+$//g; # no trailing whitespace
if (not $selector or $selector eq "/") {
$self->serve_main_menu();
} elsif ($selector eq "do/more") {
$self->serve_phlog_archive();
} elsif ($selector eq "do/index") {
$self->serve_index();
} elsif (substr($selector, 0, 9) eq "do/match\t") {
$self->serve_match(substr($selector, 9));
} elsif (substr($selector, 0, 10) eq "do/search\t") {
$self->serve_search(substr($selector, 10));
} elsif ($selector eq "do/tags") {
$self->serve_tags();
} elsif ($selector eq "do/rc") {
$self->serve_rc(0);
} elsif ($selector eq "do/rss") {
$self->serve_rss(0);
} elsif ($selector eq "do/rc/showedits") {
$self->serve_rc(1);
} elsif ($selector eq "do/new") {
my $data = $self->read_text();
$self->write_text_page(undef, $data);
} elsif ($selector =~ m!^([^/]*)/(\d+)/menu$!) {
$self->serve_page_menu($1, $2);
} elsif ($selector =~ m!^map/(.*)!) {
$self->serve_map($1);
} elsif (substr($selector, -5) eq '/menu') {
$self->serve_page_menu(substr($selector, 0, -5));
} elsif ($selector =~ m!^([^/]*)/tag$!) {
$self->serve_tag($1);
} elsif ($selector =~ m!^([^/]*)(?:/(\d+))?/html!) {
$self->serve_page_html($1, $2);
} elsif ($selector =~ m!^([^/]*)/history$!) {
$self->serve_page_history($1);
} elsif ($selector =~ m!^([^/]*)/write/text$!) {
my $data = $self->read_text();
$self->write_text_page($1, $data);
} elsif ($selector =~ m!^([^/]*)/append/text$!) {
my $data = $self->read_text();
$self->append_text_page($1, $data);
} elsif ($selector =~ m!^([^/]*)(?:/([a-z]+/[-a-z]+))?/write/file(?:\t(\d+))?$!) {
my $data = $self->read_file($3);
$self->write_file_page($1, $data, $2);
} elsif ($selector =~ m!^([^/]*)(?:/(\d+))?(?:/text)?$!) {
$self->serve_page($1, $2);
} elsif ($selector =~ m!^URL:(.*)!i) {
$self->serve_redirect(UrlDecode($1));
} elsif ($selector =~ m!^pics/(.*)!i) {
$self->serve_image(UrlDecode($1));
} else {
$self->serve_error($selector, ValidId($selector)||'Cause unknown');
}
$self->log(4, "Done");
}
}

View File

@@ -41,7 +41,7 @@ while ($arg =~ /^-l/) {
die $help unless -f $file;
my %backup = %Translate;
header_info_extract($file); # keep the header information of the translation files
do $file or die "Cannot do $file";
do "./$file" or die "Cannot do $file";
foreach my $key (keys %Translate) {
$backup{$key} = $Translate{$key};
}

40
t/checkbox.t Normal file
View File

@@ -0,0 +1,40 @@
# Copyright (C) 2018 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 => 5;
add_module('checkbox.pl');
my $text = q{
There's some stuff I want to work on:
[[ :something to do]]
[[x:something done]]
[[save:update the list]]
Let's do this!
};
$page = update_page('TODO', $text, 'saving it');
xpath_test(
$page,
'//p[text()="There\'s some stuff I want to work on:"]',
'//form[@class="checkboxes"]/p/label/input[@type="checkbox"][@name="something_to_do"]/following-sibling::text()[string()="something to do"]',
'//form[@class="checkboxes"]/p/label/input[@type="checkbox"][@name="something_done"][@checked="checked"]/following-sibling::text()[string()="something done"]',
'//form[@class="checkboxes"]/p/input[@type="submit"][@name="update the list"]',
'//p[text()="Let\'s do this!"]',);

309
t/gopher-server.t Normal file
View File

@@ -0,0 +1,309 @@
# Copyright (C) 20172018 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
use strict;
use 5.10.0;
use Test::More;
use IO::Socket::IP;
use utf8; # tests contain UTF-8 characters and it matters
require './t/test.pl';
add_module('tags.pl');
# enable uploads
our($ConfigFile);
AppendStringToFile($ConfigFile, "\$UploadAllowed = 1;\n");
my $port = random_port();
my $pid = fork();
END {
# kill server
if ($pid) {
kill 'KILL', $pid or warn "Could not kill server $pid";
}
}
our ($DataDir);
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path,
"stuff/gopher-server.pl",
"--port=$port",
"--log_level=0", # set to 4 for verbose logging
"--wiki=./wiki.pl",
"--wiki_dir=$DataDir",
"--wiki_pages=Alex",
"--wiki_pages=Berta",
"--wiki_pages=Chris")
or die "Cannot exec: $!";
}
update_page('Alex', "My best friend is [[Berta]].\n\nTags: [[tag:Friends]]\n");
update_page('Berta', "This is me.\n\nTags: [[tag:Friends]]\n");
update_page('Chris', "I'm Chris.\n\nTags: [[tag:Friends]]\n");
update_page('Friends', "Some friends.\n");
update_page('2017-12-25', 'It was a Monday.\n\nTags: [[tag:Day]]');
update_page('2017-12-26', 'It was a Tuesday.\n\nTags: [[tag:Day]]');
update_page('2017-12-27', 'It was a Wednesday.\n\nTags: [[tag:Day]]');
update_page('Friends', "News about friends.\n", 'rewrite', 1); # minor change
update_page('Friends', "News about friends:\n\n<journal search tag:friends>\n",
'add journal tag', 1); # minor change
# file created using convert NULL: test.png && base64 test.png
update_page('Picture',
"#FILE image/png\niVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bv"
. "kkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg==");
sub query_gopher {
my $query = shift;
my $text = shift;
# create client
my $socket = IO::Socket::IP->new(
PeerHost => "localhost",
PeerPort => $port,
Type => SOCK_STREAM, )
or die "Cannot construct client socket: $@";
$socket->print("$query\r\n");
$socket->print($text);
undef $/; # slurp
return <$socket>;
}
# main menu
my $page = query_gopher("");
for my $item(qw(Alex Berta Chris 2017-12-25 2017-12-26 2017-12-27)) {
like($page, qr/^1$item\t$item\/menu\t/m, "main menu contains $item");
}
# page menu
$page = query_gopher("Alex/menu");
like($page, qr/^0Alex\tAlex\t/m,
"Alex menu links to plain text");
like($page, qr/^hAlex\tAlex\/html\t/m,
"Alex menu links to HTML");
like($page, qr/^1Page History\tAlex\/history\t/m,
"Alex menu links to page history");
like($page, qr/^1Berta\tBerta\/menu\t/m,
"Alex menu links to Berta menu");
like($page, qr/^1Friends\tFriends\/tag\t/m,
"Alex menu links to Friends tag");
# plain text
$page = query_gopher("Alex");
like($page, qr/^My best friend is \[\[Berta\]\]/, "Alex plain text");
# HTML
$page = query_gopher("Alex/html");
like($page, qr/<p>My best friend is <a.*?>Berta<\/a>/, "Alex HTML");
# tags
$page = query_gopher("Friends/tag");
like($page, qr/iThis page is about the tag Friends/, "tag menu intro");
for my $item(qw(Friends Alex Berta Chris)) {
like($page, qr/^1$item\t$item\/menu\t/m, "tag menu contains $item");
}
# tags
$page = query_gopher("Day/tag");
like($page, qr/2017-12-27.*2017-12-26.*2017-12-25/s,
"tag menu sorted newest first");
# match
$page = query_gopher("do/match\t2017");
for my $item(qw(2017-12-25 2017-12-26 2017-12-27)) {
like($page, qr/^1$item\t$item\/menu\t/m, "match menu contains $item");
}
like($page, qr/2017-12-27.*2017-12-26.*2017-12-25/s,
"match menu sorted newest first");
# search
$page = query_gopher("do/search\ttag:day");
for my $item(qw(2017-12-25 2017-12-26 2017-12-27)) {
like($page, qr/^1$item\t$item\/menu\t/m, "serch menu contains $item");
}
like($page, qr/2017-12-27.*2017-12-26.*2017-12-25/s,
"search menu sorted newest first");
# rc
$page = query_gopher("do/rc");
my $re = join(".*", "Picture", "2017-12-27", "2017-12-26", "2017-12-25",
"Friends", "Chris", "Berta", "Alex");
like($page, qr/$re/s, "rc in the right order");
$page = query_gopher("do/rc/showedits");
$re = join(".*", "Friends", "2017-12-27", "2017-12-26", "2017-12-25");
like($page, qr/$re/s, "rc in the right order");
# history
$page = query_gopher("Friends/history");
like($page, qr/^1Friends \(1\)\tFriends\/1\/menu\t/m,
"Friends (1)");
like($page, qr/^1Friends \(2\)\tFriends\/2\/menu\t/m,
"Friends (2)");
like($page, qr/^1Friends \(current\)\tFriends\/menu\t/m,
"Friends (current)");
like($page, qr/Friends\/menu.*Friends\/2\/menu.*Friends\/1\/menu/s,
"history in the right order");
# revision menu
$page = query_gopher("Friends/1/menu");
like($page, qr/^0Friends\tFriends\/1\t/m,
"Friends/1 menu links to plain text");
like($page, qr/^hFriends\tFriends\/1\/html\t/m,
"Friends/1 menu links to HTML");
unlike($page, qr/Search result for tag/,
"Friends/1 has no journal and thus no tag search");
# revision plain text
$page = query_gopher("Friends/1");
like($page, qr/^Some friends/m, "Friends/1 plain text");
# revision html
$page = query_gopher("Friends/1/html");
like($page, qr/<p>Some friends/m, "Friends/1 html");
# upload text
my $haiku = <<EOT;
Quiet disk ratling
Keyboard clicking, then it stops.
Rain falls and I think
.
EOT
$page = query_gopher("Haiku/write/text", "$haiku");
like($page, qr/^iPage was saved./m, "Write Haiku");
like($page, qr/^1Haiku\tHaiku\/menu/m, "Link back to Haiku");
my $haiku_re = quotemeta(substr($haiku, 0, -2)); # strip period and \n
$page = query_gopher("Haiku");
like($page, qr/^$haiku_re/, "Haiku saved");
$haiku = <<"EOT";
```
username: Alex
minor: 1
summary: typos
```
Quiet disk rattling
Keyboard clicking, then it stops.
Rain falls and I think.
.
EOT
$page = query_gopher("Haiku/write/text", "$haiku");
like($page, qr/^iPage was saved./m, "Write haiku");
$haiku_re = quotemeta(<<"EOT");
Quiet disk rattling
Keyboard clicking, then it stops.
Rain falls and I think.
EOT
$page = query_gopher("Haiku");
like($page, qr/^$haiku_re/, "Haiku updated");
$page = query_gopher("Haiku/history");
like($page, qr/^1Haiku \(current\)\tHaiku\/menu\t/m, "Haiku (current)");
like($page, qr/^i\d\d:\d\d UTC by Alex: typos \(minor\)/m,
"Metadata recorded");
like($page, qr/^1Haiku \(1\)\tHaiku\/1\/menu\t/m, "Haiku (1)");
# new page
$page = query_gopher("do/new", <<"EOT");
```
username: Alex
summary: copy
title: Haiku_Copy
```
Quiet disk rattling
Keyboard clicking, then it stops.
Rain falls and I think.
.
EOT
like($page, qr/^iPage was saved./m, "Write copy of haiku");
$page = query_gopher("Haiku_Copy");
like($page, qr/^$haiku_re/, "New copy of haiku created");
# append
$page = query_gopher("Haiku_Copy/append/text", "This is a comment by me!\n.\n");
like($page, qr/^iPage was saved./m, "Append to copy of haiku");
$page = query_gopher("Haiku_Copy");
like($page, qr/^$haiku_re/, "Copy of haiku still there");
like($page, qr/\n\n----\n\nThis is a comment by me!\n\n-- Anonymous/,
"Comment is also there");
# Image download
my $image = query_gopher("Picture");
like($image, qr/\211PNG\r\n/, "Image download");
# Image upload
$page = query_gopher("PictureCopy/write/file\t" . length($image), "$image");
like($page, qr/Files of type application\/octet-stream are not allowed/m,
"MIME type check");
$page = query_gopher("PictureCopy/image/png/write/file\t" . length($image), "$image");
like($page, qr/^iPage was saved./m, "Image upload");
unlike($page, qr/^3Page was not saved/, "Messages are correct");
my $copy = query_gopher("PictureCopy");
like($copy, qr/\211PNG\r\n/, "Image copy download");
is($copy, $image, "Image and copy are identical");
# image:link
$page = query_gopher("Test/write/text", "[[image:Picture]]\n.\n");
like($page, qr/^iPage was saved./m, "Saved test page containing image link");
$page = query_gopher("Test/menu");
like($page, qr/^1Picture\tPicture\/menu/m, "Link to image page looks good");
$page = query_gopher("Picture/menu");
like($page, qr/^IPicture\tPicture/, "Link to image file looks good");
# Test upload of large page (but note $MaxPost: 1024 * 210 > (10 * 8 + 1) * 2600)
my $garbage = (("0123456789" x 8) . "\n") x 2600 . "Last Line\n";
$page = query_gopher("Large/write/text", "$garbage.\n");
like($page, qr/^iPage was saved./m, "Write page with "
. length($garbage) . " bytes");
$page = query_gopher("Large");
like(substr($page, -20), qr/Last Line/, "All of large page was saved");
# Test of Umlauts in the selector
test_page(update_page('Zürich♥', '[[Üetliberg♥]]'), 'Zürich♥', 'Üetliberg♥');
$page = query_gopher("Z%c3%bcrich%e2%99%a5");
utf8::decode($page);
like($page, qr/Üetliberg♥/, "UTF-8 encoded page names");
$page = query_gopher("Z%c3%bcrich%e2%99%a5/menu");
utf8::decode($page);
like($page, qr/^0Zürich♥\tZ%c3%bcrich%e2%99%a5\t/m, "UTF-8 encoded text link");
like($page, qr/^1Üetliberg♥\t%c3%9cetliberg%e2%99%a5\/menu\t/m,
"UTF-8 encoded links");
# gopher links
update_page('Gopher', '[http://gopher.floodgap.com/gopher/gw?a=gopher%3A%2F%2Fsdf.org%3A70%2F0%2Fusers%2Fsolderpunk%2Fphlog%2Fintroducing-vf1.txt VF-1], [gopher://sdf.org:70/1/phlogs/ Phlogs]');
$page = query_gopher("Gopher/menu");
like($page, qr/^1Phlogs\t\/phlogs\/\tsdf\.org\t70/m, "Direct Gopher link");
like($page, qr/^0VF-1\t\/users\/solderpunk\/phlog\/introducing-vf1.txt\tsdf\.org\t70/m, "Floodgap proxy link");
done_testing();

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20092015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20092018 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,18 +15,66 @@
require './t/test.pl';
package OddMuse;
use Test::More tests => 10;
use Test::More tests => 20;
my $hundred_days_ago = $Now - 100 * 24 * 60 * 60;
my $ten_days_ago = $Now - 10 * 24 * 60 * 60;
my $two_days_ago = $Now - 2 * 24 * 60 * 60;
my $ip = '127.0.0.1';
# $ts, $id, $minor, $summary, $host, @rest
# First, make sure that moving all the entries from rc.log leaves no newline in
# the file.
my $log = join("\n",
join($FS, $hundred_days_ago, 'Two_Hundred_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
'');
WriteStringToFile($RcFile, $log);
# old log entry to be moved
my $log = join($FS, '1235079422', 'Ganz_und_Gar', '',
'Ladenbeschreibung und Preisliste', '62.12.165.34',
'Alex', '1', '', '');
WriteStringToFile($RcFile, $log . "\n");
test_page(get_page('action=maintain pwd=foo'),
'Moving 1 log entries');
test_page(ReadFileOrDie($RcOldFile),
"^1235079422$FS");
is(ReadFileOrDie($RcFile), '', 'empty rc.log');
'Moving 1 log entries',
'Removing IP numbers from 0 log entries');
$log = ReadFileOrDie($RcOldFile);
test_page($log, "Hundred_Days_Ago.*Anonymous");
test_page_negative($log, $ip);
$log = ReadFileOrDie($RcFile);
is($log, '', 'rc.log is empty');
# Now let's make sure that an old entry get anonymized and moved (like the
# previous test), and that those that are not moved care anonymized if they are
# old enough.
$log = join("\n",
join($FS, $hundred_days_ago, 'One_Hundred_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
join($FS, $ten_days_ago, 'Ten_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
join($FS, $two_days_ago, 'Two_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
'');
WriteStringToFile($RcFile, $log);
test_page(get_page('action=maintain pwd=foo'),
'Moving 1 log entries',
'Removing IP numbers from 1 log entries');
$log = ReadFileOrDie($RcOldFile);
test_page($log,
"Two_Hundred_Days_Ago.*Anonymous",
"One_Hundred_Days_Ago.*Anonymous");
test_page_negative($log, $ip);
$log = ReadFileOrDie($RcFile);
test_page($log,
"Ten_Days_Ago.*Anonymous",
"Two_Days_Ago.*$ip");
test_page_negative($log, "Hundred_Days_Ago");
# Let's make sure that updating pages write the right rc lines.
update_page('test', 'this is a test');
my $log = ReadFileOrDie($RcFile);
test_page($log,
@@ -34,7 +82,8 @@ test_page($log,
"${FS}this is a test${FS}");
test_page_negative($log, "^\n");
# old page to be deleted
# Make sure that pages to be deleted are in fact deleted.
OpenPage('test');
$Page{ts} = 1;
$Page{revision} = 1;

View File

@@ -16,7 +16,7 @@
require './t/test.pl';
package OddMuse;
use Test::More tests => 52;
use Test::More tests => 53;
add_module('markdown-rule.pl');
add_module('bbcode.pl');
@@ -38,7 +38,7 @@ run_tests(split(/\n/,<<'EOT'));
- one
<ul><li>one</li></ul>
- one\n-- Alex
<ul><li>one</li><li>- Alex</li></ul>
<ul><li>one -- Alex</li></ul>
- one\n\n- Alex
<ul><li>one</li><li>Alex</li></ul>
* one\n * two
@@ -121,6 +121,8 @@ bar <h2>foo</h2><p>bar</p>
<table><tr><th><em style="font-style: normal; text-decoration: underline">foo</em></th></tr></table>
foo ~~bar~~
foo <del>bar</del>
pay 1.-/month
pay 1.-/month
EOT
xpath_run_tests(split('\n',<<'EOT'));

4
t/rc.t
View File

@@ -1,4 +1,4 @@
# Copyright (C) 200620015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062015 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -51,7 +51,7 @@ test_page(get_page('action=rc raw=1'), 'title: Wiki');
WriteStringToFile($RcFile, "1${FS}test${FS}${FS}test${FS}127.0.0.1${FS}${FS}1${FS}${FS}\n");
test_page_negative(get_page('action=rc raw=1'), 'title: test');
test_page(get_page('action=rc raw=1 from=1'), 'title: Wiki', 'title: test',
'description: test', 'generator: 127.0.0.1',
'description: test', 'generator: Anonymous',
'link: http://localhost/wiki.pl/test',
'last-modified: 1970-01-01T00:00Z', 'revision: 1');

23
t/search-inclusion.t Normal file
View File

@@ -0,0 +1,23 @@
# Copyright (C) 2018 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 => 3;
test_page(update_page("Omega", "last page"), "last page");
test_page(update_page("Alpha", "first page\n<search page>"),
"Omega");
test_page(get_page('Omega'), 'last page');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20062015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062018 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
@@ -156,7 +156,7 @@ xpath_test(update_page('IncludeSearch',
'//p[text()="first line "]', # note the NL -> SPC
'//div[@class="search"]/p/span[@class="result"]/a[@class="local"][@href="http://localhost/wiki.pl/NegativeSearchTest"][text()="NegativeSearchTest"]',
'//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
'//p[text()=" last line"]'); # note the NL -> SPC
xpath_test(get_page('search=Schröder'),
'//input[@name="search"][@value="Schröder"]');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20062015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062017 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 => 47;
use Test::More tests => 48;
add_module('usemod.pl');
@@ -73,6 +73,8 @@ This is <strong>strong text containing <em>emph</em> text</strong>.
<table class="user"><tr class="odd first"><td align="center">one <em>two</em></td></tr></table>
|| one two ||
<table class="user"><tr class="odd first"><td align="center">one two </td></tr></table>
||'''''foo''''' || '''''bar''''' ||\n||baz || quux ||
<table class="user"><tr class="odd first"><td align="left"><strong><em>foo</em></strong></td><td align="center"><strong><em>bar</em></strong></td></tr><tr class="even"><td align="left">baz </td><td align="center">quux </td></tr></table>
introduction\n\n||one||two||three||\n||||one two||three||
introduction<table class="user"><tr class="odd first"><td>one</td><td>two</td><td>three</td></tr><tr class="even"><td colspan="2">one two</td><td>three</td></tr></table>
||one||two||three||\n||||one two||three||\n\nfooter

110
wiki.pl
View File

@@ -1,5 +1,5 @@
#! /usr/bin/perl
# Copyright (C) 2001-2015
# Copyright (C) 2001-2018
# Alex Schroeder <alex@gnu.org>
# Copyright (C) 2014-2015
# Alex Jakimenko <alex.jakimenko@gmail.com>
@@ -35,6 +35,7 @@ use warnings;
no warnings 'numeric';
no warnings 'uninitialized';
use utf8; # in case anybody ever adds UTF8 characters to the source
use B;
use CGI qw/-utf8/;
use CGI::Carp qw(fatalsToBrowser);
use File::Glob ':glob';
@@ -124,6 +125,7 @@ our $DeletedPage = 'DeletedPage'; # Pages starting with this can be deleted
our $RCName = 'RecentChanges'; # Name of changes page
our @RcDays = qw(1 3 7 30 90); # Days for links on RecentChanges
our $RcDefault = 30; # Default number of RecentChanges days
our $KeepHostDays = 4; # Days to keep IP numbers for
our $KeepDays = 0; # Days to keep old revisions (0 means keep forever)
our $KeepMajor = 1; # 1 = keep at least one major rev when expiring pages
our $SummaryHours = 4; # Hours to offer the old subject when editing a page
@@ -687,10 +689,10 @@ sub CloseHtmlEnvironments { # close all -- remember to use AddHtmlEnvironment('p
}
sub CloseHtmlEnvironment { # close environments up to and including $html_tag
my $html = (@_ and InElement(@_)) ? CloseHtmlEnvironmentUntil(@_) : '';
my $html = (@_ and InElement(@_)) ? CloseHtmlEnvironmentUntil(@_) : undef;
if (@HtmlStack and (not(@_) or defined $html)) {
shift(@HtmlAttrStack);
return $html . '</' . shift(@HtmlStack) . '>';
$html .= '</' . shift(@HtmlStack) . '>';
}
return $html || ''; # avoid returning undefined
}
@@ -1324,12 +1326,12 @@ sub DoBrowseRequest {
eval { local $SIG{__DIE__}; MyActions(); };
} elsif ($action) {
ReportError(Ts('Invalid action parameter %s', $action), '501 NOT IMPLEMENTED');
} elsif (GetParam('match', '') ne '') {
SetParam('action', 'index'); # make sure this gets a NOINDEX
DoIndex();
} elsif (GetParam('search', '') ne '') { # allow search for "0"
SetParam('action', 'search'); # make sure this gets a NOINDEX
DoSearch();
} elsif (GetParam('match', '') ne '') {
SetParam('action', 'index'); # make sure this gets a NOINDEX
DoIndex();
} elsif (GetParam('title', '') and not GetParam('Cancel', '')) {
DoPost(GetParam('title', ''));
} else {
@@ -1757,7 +1759,7 @@ sub RcHtml {
$languages, $cluster, $last) = @_;
my $all_revision = $last ? undef : $revision; # no revision for the last one
$host = QuoteHtml($host);
my $author = GetAuthorLink($host, $username);
my $author = GetAuthorLink($username, $host);
my $sum = $summary ? $q->span({class=>'dash'}, ' &#8211; ')
. $q->strong(QuoteHtml($summary)) : '';
my $edit = $minor ? $q->em({class=>'type'}, T('(minor)')) : '';
@@ -1840,7 +1842,7 @@ sub RcTextRevision {
: ($UsePathInfo ? '/' : '?') . UrlEncode($id));
print "\n", RcTextItem('title', NormalToFree($id)),
RcTextItem('description', $summary),
RcTextItem('generator', GetAuthor($host, $username)),
RcTextItem('generator', GetAuthor($username)),
RcTextItem('language', join(', ', @{$languages})), RcTextItem('link', $link),
RcTextItem('last-modified', TimeToW3($ts)),
RcTextItem('revision', $revision),
@@ -1925,7 +1927,6 @@ sub RssItem {
}
my $date = TimeToRFC822($ts);
$username = QuoteHtml($username);
$username ||= $host;
my $rss = "<item>\n";
$rss .= "<title>$name</title>\n";
my $link = ScriptUrl(GetParam('all', $cluster)
@@ -2034,8 +2035,7 @@ sub GetHistoryLine {
$html .= ' ' . GetOldPageLink('browse', $id, $revision,
Ts('Revision %s', $revision));
}
my $host = $data{host} || $data{ip};
$html .= T(' . . . .') . ' ' . GetAuthorLink($host, $data{username});
$html .= T(' . . . .') . ' ' . GetAuthorLink($data{username});
$html .= $q->span({class=>'dash'}, ' &#8211; ')
. $q->strong(QuoteHtml($data{summary})) if $data{summary};
$html .= ' ' . $q->em({class=>'type'}, T('(minor)')) . ' ' if $data{minor};
@@ -2197,26 +2197,37 @@ sub ScriptLinkDiff {
return ScriptLink($action, $text, 'diff');
}
sub ColorCode {
my ($str) = @_;
my $num = unpack("L",B::hash($str)); # 32-bit integer
my $code = sprintf("%o", $num); # octal is 0-7
my @indexes = split(//, substr($code, 0, 4)); # four numbers
my @colors = qw/red orange yellow green blue indigo violet white/;
return $q->span({-class => 'ip-code', -title => T('Anonymous')},
join('', map { $q->span({-class => $colors[$_]}, $_) }
@indexes));
}
sub GetAuthor {
my ($host, $username) = @_;
return $username . ' ' . Ts('from %s', $host) if $username and $host;
my ($username) = @_;
return $username if $username;
return T($host); # could be 'Anonymous'
return T('Anonymous');
}
sub GetAuthorLink {
my ($host, $username) = @_;
my ($username, $host) = @_;
$username = FreeToNormal($username);
my $name = NormalToFree($username);
if (ValidId($username) ne '') { # ValidId() returns error string
$username = ''; # Just pretend it isn't there.
}
if ($username and $RecentLink) {
return ScriptLink(UrlEncode($username), $name, 'author', undef, $host);
return ScriptLink(UrlEncode($username), $name, 'author');
} elsif ($username) {
return $q->span({-class=>'author'}, $name);
}
return T($host); # could be 'Anonymous'
return T('Anonymous') if $host eq 'Anonymous';
return ColorCode($host);
}
sub GetHistoryLink {
@@ -2437,7 +2448,7 @@ sub GetFooterTimestamp {
$page //= \%Page;
if ($id and $rev ne 'history' and $rev ne 'edit' and $page->{revision}) {
my @elements = (($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($page->{ts}),
Ts('by %s', GetAuthorLink($page->{host}, $page->{username})));
Ts('by %s', GetAuthorLink($page->{username})));
push(@elements, ScriptLinkDiff(2, $id, T('(diff)'), $rev)) if $UseDiff and $page->{revision} > 1;
return $q->div({-class=>'time'}, @elements);
}
@@ -2522,7 +2533,7 @@ sub GetFormStart {
sub GetSearchForm {
my $html = GetFormStart(undef, 'get', 'search') . $q->start_p;
$html .= $q->label({-for=>'search'}, T('Search:')) . ' '
. $q->textfield(-name=>'search', -id=>'search', -size=>20, -accesskey=>T('f')) . ' ';
. $q->textfield(-name=>'search', -id=>'search', -size=>15, -accesskey=>T('f')) . ' ';
if (GetParam('search') ne '' and UserIsAdmin()) { # see DoBrowseRequest
$html .= $q->label({-for=>'replace'}, T('Replace:')) . ' '
. $q->textfield(-name=>'replace', -id=>'replace', -size=>20) . ' '
@@ -2532,11 +2543,11 @@ sub GetSearchForm {
}
if (GetParam('matchingpages', $MatchingPages)) {
$html .= $q->label({-for=>'matchingpage'}, T('Filter:')) . ' '
. $q->textfield(-name=>'match', -id=>'matchingpage', -size=>20) . ' ';
. $q->textfield(-name=>'match', -id=>'matchingpage', -size=>15) . ' ';
}
if (%Languages) {
$html .= $q->label({-for=>'searchlang'}, T('Language:')) . ' '
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>10, -default=>GetParam('lang', '')) . ' ';
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>5, -default=>GetParam('lang', '')) . ' ';
}
$html .= $q->submit('dosearch', T('Go!')) . $q->end_p . $q->end_form;
return $html;
@@ -3473,8 +3484,8 @@ sub SearchTitleAndBody {
foreach my $id (Filtered($regex, AllPagesList())) {
my $name = NormalToFree($id);
my ($text) = PageIsUploadedFile($id); # set to mime-type if this is an uploaded file
local ($OpenPageName, %Page); # this is local!
if (not $text) { # not uploaded file, therefore allow searching of page body
local ($OpenPageName, %Page); # this is local!
OpenPage($id); # this opens a page twice if it is not uploaded, but that's ok
if ($lang) {
my @languages = split(/,/, $Page{languages});
@@ -3490,8 +3501,11 @@ sub SearchTitleAndBody {
return @found;
}
sub Filtered { # this is overwriten in extensions such as tags.pl
return @_[1 .. $#_]; # ignores $regex and returns all pages
sub Filtered { # this is overwriten in extensions such as tags.pl
my ($string, @pages) = @_;
my $match = GetParam('match', '');
@pages = grep /$match/i, @pages if $match;
return @pages;
}
sub SearchString {
@@ -3526,20 +3540,19 @@ sub PrintSearchResult {
$entry{size} = int((length($text) / 1024) + 1) . 'K';
$entry{'last-modified'} = TimeToText($Page{ts});
$entry{username} = $Page{username};
$entry{host} = $Page{host};
PrintSearchResultEntry(\%entry);
}
sub PrintSearchResultEntry {
my %entry = %{(shift)}; # get value from reference
if (GetParam('raw', 0)) {
$entry{generator} = GetAuthor($entry{host}, $entry{username});
foreach my $key (qw(title description size last-modified generator username host)) {
$entry{generator} = GetAuthor($entry{username});
foreach my $key (qw(title description size last-modified generator username)) {
print RcTextItem($key, $entry{$key});
}
print RcTextItem('link', "$ScriptName?$entry{title}"), "\n";
} else {
my $author = GetAuthorLink($entry{host}, $entry{username});
my $author = GetAuthorLink($entry{username});
$author ||= $entry{generator};
my $id = $entry{title};
my ($class, $resolved, $title, $exists) = ResolveId($id);
@@ -3593,8 +3606,8 @@ sub ReplaceAndSave {
RequestLockOrError(); # fatal
my @result = Replace($from, $to, 1, sub {
my ($id, $new) = @_;
Save($id, $new, $from . ' → ' . $to, 1, ($Page{host} ne $q->remote_addr()));
});
Save($id, $new, $from . ' → ' . $to, 1);
});
ReleaseLock();
return @result;
}
@@ -3713,9 +3726,7 @@ sub DoPost {
}
my $newAuthor = 0;
if ($oldrev) { # the first author (no old revision) is not considered to be "new"
# prefer usernames for potential new author detection
$newAuthor = 1 if not $Page{username} or $Page{username} ne GetParam('username', '');
$newAuthor = 1 if not $q->remote_addr() or not $Page{host} or $q->remote_addr() ne $Page{host};
}
my $oldtime = $Page{ts};
my $myoldtime = GetParam('oldtime', ''); # maybe empty!
@@ -3789,7 +3800,6 @@ sub AddComment {
sub Save { # call within lock, with opened page
my ($id, $new, $summary, $minor, $upload) = @_;
my $user = GetParam('username', '');
my $host = $q->remote_addr();
my $revision = $Page{revision} + 1;
my $old = $Page{text};
my $olddiff = $Page{'diff-major'} == '1' ? $Page{'diff-minor'} : $Page{'diff-major'};
@@ -3805,8 +3815,8 @@ sub Save { # call within lock, with opened page
ExpireKeepFiles();
$Page{lastmajor} = $revision unless $minor;
$Page{lastmajorsummary} = $summary unless $minor;
@Page{qw(ts revision summary username host minor text)} =
($Now, $revision, $summary, $user, $host, $minor, $new);
@Page{qw(ts revision summary username minor text)} =
($Now, $revision, $summary, $user, $minor, $new);
if ($UseDiff and $UseCache > 1 and $revision > 1 and not $upload and not TextIsFile($old)) {
UpdateDiffs($old, $new, $olddiff); # sets diff-major and diff-minor
}
@@ -3817,6 +3827,7 @@ sub Save { # call within lock, with opened page
if ($revision == 1 and $LockOnCreation{$id}) {
WriteStringToFile(GetLockedPageFile($id), 'LockOnCreation');
}
my $host = $q->remote_addr();
WriteRcLog($id, $summary, $minor, $revision, $user, $host, $languages, GetCluster($new));
AddToIndex($id) if ($revision == 1);
}
@@ -3890,7 +3901,7 @@ sub DoMaintain {
RequestLockOrError();
print $q->p(T('Main lock obtained.'));
print $q->p(Ts('Moving part of the %s log file.', $RCName));
# Determine the number of days to go back
# Determine the number of days to go back, default is largest of @RcDays
my $days = 0;
foreach (@RcDays) {
$days = $_ if $_ > $days;
@@ -3901,23 +3912,36 @@ sub DoMaintain {
if (not $status) {
print $q->p($q->strong(Ts('Could not open %s log file', $RCName) . ':') . ' ' . $RcFile),
$q->p(T('Error was') . ':'), $q->pre($!), $q->p(T('Note: This error is normal if no changes have been made.'));
} else {
WriteStringToFile($RcFile . '.old', $data);
}
# Move the old stuff from rc to temp
my @rc = split(/\n/, $data);
my @tmp = ();
for my $line (@rc) {
my $line;
my $changed = 0;
while ($line = shift(@rc)) {
my ($ts, $id, $minor, $summary, $host, @rest) = split(/$FS/, $line);
last if $ts >= $starttime;
push(@tmp, join($FS, $ts, $id, $minor, $summary, 'Anonymous', @rest));
$changed = 1;
}
unshift(@rc, $line) if $line; # this one ended the loop
print $q->p(Ts('Moving %s log entries.', scalar(@tmp)));
if (@tmp) {
# Write new files, and backups
AppendStringToFile($RcOldFile, join("\n", @tmp) . "\n");
WriteStringToFile($RcFile . '.old', $data);
splice(@rc, 0, scalar(@tmp)); # strip
WriteStringToFile($RcFile, @rc ? join("\n", @rc) . "\n" : '');
AppendStringToFile($RcOldFile, join("\n", @tmp) . "\n") if @tmp;
# remove IP numbers from all but the last few days
$starttime = $Now - $KeepHostDays * 86400; # 24*60*60
@tmp = ();
while ($line = shift(@rc)) {
my ($ts, $id, $minor, $summary, $host, @rest) = split(/$FS/, $line);
last if $ts >= $starttime;
push(@tmp, join($FS, $ts, $id, $minor, $summary, 'Anonymous', @rest));
$changed = 1;
}
unshift(@rc, $line) if $line; # this one ended the loop
unshift(@rc, @tmp) if @tmp;
print $q->p(Ts('Removing IP numbers from %s log entries.', scalar(@tmp)));
WriteStringToFile($RcFile, @rc ? join("\n", @rc) . "\n" : '') if $changed;
if (opendir(DIR, $RssDir)) { # cleanup if they should expire anyway
foreach (readdir(DIR)) {
Unlink("$RssDir/$_") if $Now - Modified($_) > $RssCacheHours * 3600;