Compare commits

...

14 Commits
2.4.0 ... 2.4.1

Author SHA1 Message Date
Alex Schroeder
f21f257c1b Fix parenthesis 2023-03-24 21:59:52 +01:00
Alex Schroeder
48916943a1 More spans for the search bar 2023-03-24 21:16:47 +01:00
Alex Schroeder
3b185e5521 Add some spans to the gotobar for better styling 2023-03-24 15:38:50 +01:00
Alex Schroeder
612af8f7fb Make feed link more flexible
The result is that feeds generated by journal-rss.pl contain a link to
the Recent Changes page instead of linking twice to the feed.
2023-02-27 14:12:15 +01:00
Alex Schroeder
dc9131e600 Fix translation-link.t 2023-02-27 14:12:04 +01:00
Alex Schroeder
99af4d984d Handle [an example](#foo "Title") 2023-02-17 17:16:23 +01:00
Alex Schroeder
88f4fe3b89 Whitespace 2023-02-17 17:16:02 +01:00
Sandra Snan
851f2f77e8 Handle image/right
Everyone loves hacky regexes♥
2023-02-17 17:14:24 +01:00
Alex Schroeder
975e15c9f8 Don't turn all whitespace into a space
We want to honor NO-BREAK SPACE and the like!
2022-08-26 13:42:35 +02:00
Alex Schroeder
d235d6ac47 GetId returns the normal form of $id.
This means, "2022-07-15 The Joy of Exploration", which arrives as
"2022-07-15%20The%20Joy%20of%20Exploration", gets turned into
"2022-07-15_The_Joy_of_Exploration". The problem is that when posting,
$id = FreeToNormal(shift), so pages are always written to the page
with underscores. If you then request the raw history of a page,
however, no such call was happening and so no keep files were found by
DoHistory.
2022-07-18 17:51:22 +02:00
Alex Schroeder
f0d0942bfb namespaces: remove underscore from page title 2022-04-15 10:15:00 +02:00
Alex Schroeder
cd9246ebed Add Cooklang extension 2021-11-06 20:32:02 +01:00
Alex Schroeder
f7b23d854f ban-contributors: test Net::IP use 2021-09-29 19:52:12 +02:00
Alex Schroeder
104a1395e7 ban-contributors: use Net::IP to parse CIDR
whois 191.101.31.160 doesn't return a range, only something like
inetnum: 191.101.0.0/16
2021-09-29 18:17:58 +02:00
10 changed files with 135 additions and 28 deletions

View File

@@ -171,6 +171,7 @@ sub NewBanContributorsWriteRcLog {
package BanContributors;
use Net::Whois::Parser qw/parse_whois/;
use Net::IP;
sub get_range {
my $ip = shift;
@@ -181,7 +182,8 @@ sub get_range {
my @result;
$_ = $response->{$_};
for (ref eq 'ARRAY' ? @$_ : $_) {
push(@result, $1, $2) if /($re) *- *($re)/;
$ip = Net::IP->new($_);
push(@result, $ip->ip, $ip->last_ip) if $ip;
}
return @result if @result;
}

66
modules/cook-lang.pl Normal file
View File

@@ -0,0 +1,66 @@
# Copyright (C) 2021 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/>.
use strict;
use v5.10;
AddModuleDescription('cook-lang.pl', 'Cooklang Extension');
our ($q, $bol, @MyRules);
push(@MyRules, \&CookLangRule);
sub CookLangRule {
if (/\G#([^\n#\@\{\}]+)\{(?:([^\n%\}]+)(?:%([^\n\}]+))?)?\}/cg) {
# #canning funnel{}
my $html = "";
$html .= $q->strong({-title=>"number"}, $2) if $2;
$html .= " " if $2 and $3;
$html .= $q->strong({-title=>"unit"}, $3) if $3;
$html .= " " if $1 and ($2 or $3);
$html .= $q->strong({-title=>"cookware"}, $1);
return $html;
} elsif (/\G#(\w+)/cg) {
# #pot
return $q->strong({-title=>"cookware"}, $1);
} elsif (/\G\@([^\n#\@\{\}]+)\{(?:([^\n%\}]+)(?:%([^\n\}]+))?)?\}/cg) {
# @ground black pepper{}
my $html = "";
$html .= $q->strong({-title=>"number"}, $2) if $2;
$html .= " " if $2 and $3;
$html .= $q->strong({-title=>"unit"}, $3) if $3;
$html .= " " if $1 and ($2 or $3);
$html .= $q->strong({-title=>"ingredient"}, $1);
return $html;
} elsif (/\G\@(\w+)/cg) {
# @salt
return $q->strong({-title=>"ingredient"}, $1);
} elsif (/\G\~\{([^\n%\}]+)(?:%([^\n\}]+))?\}/cg) {
# ~{25%minutes}
my $html = $q->strong({-title=>"number"}, $1);
$html .= " " if $1 and $2;
$html .= $q->strong({-title=>"unit"}, $2) if $2;
return $html;
} elsif (/\G\/\/\s*(.*)/cg) {
# // Don't burn the roux!
return $q->em({-title=>"comment"}, $1);
} elsif ($bol and /\G&gt;&gt;\s*(.*)/cg) {
# // Don't burn the roux!
return CloseHtmlEnvironments()
. $q->blockquote({-title=>"meta"}, $1)
. AddHtmlEnvironment('p');
}
# no match
return;
}

View File

@@ -31,6 +31,7 @@ sub DoJournalRss {
local $CollectingJournal = 1;
# Fake the result of GetRcLines()
local *GetRcLines = \&JournalRssGetRcLines;
local *RcSelfWebsite = \&JournalRssSelfWebsite;
local *RcSelfAction = \&JournalRssSelfAction;
local *RcPreviousAction = \&JournalRssPreviousAction;
local *RcLastAction = \&JournalRssLastAction;
@@ -55,6 +56,15 @@ sub JournalRssParameters {
return $more;
}
sub JournalRssSelfWebsite {
my $more = '';
my $search = GetParam('rcfilteronly', '');
$more .= ";search=" . UrlEncode($search) if $search;
my $match = GetParam('match', '');
$more .= ";match=" . UrlEncode($match) if $match;
return $more;
}
sub JournalRssSelfAction {
return "action=journal" . JournalRssParameters(qw(offset));
}

View File

@@ -505,9 +505,9 @@ sub MailUnsubscribe {
=head1 Migrate
The mailmigrate action will migrate your subscription list from the
old format to the new format. This is necessary because these days
because the keys and values of the DB_File are URL encoded.
The mailmigrate action will migrate your subscription list from the old format
to the new format. This is necessary because these days the keys and values of
the DB_File are URL encoded.
=cut

View File

@@ -203,6 +203,15 @@ sub MarkdownRule {
$params{-title} = $title if $title;
return $q->a(\%params, $text);
}
# link: [an example](#foo "Title")
elsif (m/\G\[((?:[^]\n]+\n?)+)\]\((#\S)+(\s+"(.+?)")?\)/cg) {
my ($text, $url, $title) = ($1, $2, $4);
my %params;
$params{-href} = $url;
$params{-class} = "named-anchor";
$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()

View File

@@ -126,7 +126,7 @@ sub NamespacesInitVariables {
and $ns ne $NamespacesSelf) {
$NamespaceCurrent = $ns;
# Change some stuff from the original InitVariables call:
$SiteName .= ' ' . $NamespaceCurrent;
$SiteName .= ' ' . NormalToFree($NamespaceCurrent);
$InterWikiMoniker = $NamespaceCurrent;
$DataDir .= '/' . $NamespaceCurrent;
$PageDir = "$DataDir/page";

View File

@@ -675,10 +675,10 @@ sub gemini_text {
$block =~ s/\[\[tag:([^]|]+)\]\]/push(@links, $self->gemini_link("tag\/$1", $1)); $1/ge;
$block =~ s/\[\[tag:([^]|]+)\|([^\]|]+)\]\]/push(@links, $self->gemini_link("tag\/$1", $2)); $2/ge;
$block =~ s/<journal search tag:(\S+)>\n*/push(@links, $self->gemini_link("tag\/$1", "Explore the $1 tag")); ""/ge;
$block =~ s/\[\[image:([^]|]+)\]\]/push(@links, $self->gemini_link($1, "$1 (image)")); "$1"/ge;
$block =~ s/\[\[image:([^]|]+)\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, "$2 (image)")); "$2"/ge;
$block =~ s/\[\[image:([^]|]+)\|([^\]|]*)\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, "$2 (image)"), $self->gemini_link($3, "$2 (follow-up)")); "$2"/ge;
$block =~ s/\[\[image:([^]|]+)\|([^\]|]*)\|([^\]|]*)\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, "$2 (image)"), $self->gemini_link($3, "$4 (follow-up)")); "$2"/ge;
$block =~ s/\[\[image(?:\/right)?:([^]|]+)\]\]/push(@links, $self->gemini_link($1, "$1 (image)")); "$1"/ge;
$block =~ s/\[\[image(?:\/right)?:([^]|]+)\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, "$2 (image)")); "$2"/ge;
$block =~ s/\[\[image(?:\/right)?:([^]|]+)\|([^\]|]*)\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, "$2 (image)"), $self->gemini_link($3, "$2 (follow-up)")); "$2"/ge;
$block =~ s/\[\[image(?:\/right)?:([^]|]+)\|([^\]|]*)\|([^\]|]*)\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, "$2 (image)"), $self->gemini_link($3, "$4 (follow-up)")); "$2"/ge;
$block =~ s/\[\[$FreeLinkPattern\|([^\]|]+)\]\]/push(@links, $self->gemini_link($1, $2)); $2/ge;
$block =~ s/\[\[$FreeLinkPattern\]\]/push(@links, $self->gemini_link($1)); $1/ge;
$block =~ s/\[color=([^]]+)\]/color($1)/ge;

View File

@@ -15,6 +15,7 @@
require './t/test.pl';
package OddMuse;
use Test::More;
use Net::IP;
add_module('ban-contributors.pl');
@@ -43,6 +44,13 @@ is(BanContributors::get_regexp_ip('45.87.2.128', '45.87.2.255'),
'^45\.87\.2\.(12[8-9]|1[3-9][0-9]|2[0-4][0-9]|25[0-5])',
'45.87.2.128 - 45.87.2.255');
# 191.101.0.0/16
# verify that Net::IP works as intended
my $ip = Net::IP->new('191.101.0.0/16');
ok($ip, 'Net::IP parsed CIDR');
is($ip->ip, '191.101.0.0', 'First IP in range');
is($ip->last_ip, '191.101.255.255', 'Last IP in range');
$localhost = '127.0.0.1';
$ENV{'REMOTE_ADDR'} = $localhost;

View File

@@ -129,8 +129,8 @@ test_page(update_page('Testing', 'This is spam.'), 'This page does not exist');
test_page(update_page('Spam', 'Trying again.'), 'This page does not exist');
test_page(get_page('action=translate id=Spam target=Harmless translation=en'),
'Edit Denied',
'Regular expression "spam" matched on this page');
'Regular expression "spam" matched "Spam" on this page');
test_page(get_page('Spam'), 'This page does not exist');
test_page(get_page('action=translate id=Harmless target=Spam translation=en'),
'Edit Denied',
'Regular expression "spam" matched on this page');
'Regular expression "spam" matched "Spam" on this page');

46
wiki.pl
View File

@@ -514,7 +514,7 @@ sub ApplyRules {
Clean(CloseHtmlEnvironments() . AddHtmlEnvironment('p')); # another one like this further up
} elsif (m/\G&amp;([A-Za-z]+|#[0-9]+|#x[A-Za-f0-9]+);/cg) { # entity references
Clean("&$1;");
} elsif (m/\G\s+/cg) {
} elsif (m/\G[ \t\r\n]+/cg) { # don't use \s because we want to honor NO-BREAK SPACE etc
Clean(' ');
} elsif (m/\G([A-Za-z\x{0080}-\x{fffd}]+([ \t]+[a-z\x{0080}-\x{fffd}]+)*[ \t]+)/cg
or m/\G([A-Za-z\x{0080}-\x{fffd}]+)/cg or m/\G(\S)/cg) {
@@ -1313,7 +1313,7 @@ sub GetId {
SetParam($p, 1); # script/p/q -> p=1
}
}
return $id;
return FreeToNormal($id);
}
sub DoBrowseRequest {
@@ -1712,6 +1712,11 @@ sub RcOtherParameters {
return $more;
}
sub RcSelfWebsite {
my $action = 'rc';
return "action=$action" . RcOtherParameters(qw(from upto days));
}
sub RcSelfAction {
my $action = GetParam('action', 'rc');
return "action=$action" . RcOtherParameters(qw(from upto days));
@@ -1898,7 +1903,7 @@ sub GetRcRss {
};
my $title = QuoteHtml($SiteName) . ': ' . GetParam('title', QuoteHtml(NormalToFree($HomePage)));
$rss .= "<title>$title</title>\n";
$rss .= "<link>$ScriptName?" . RcSelfAction() . "</link>\n";
$rss .= "<link>$ScriptName?" . RcSelfWebsite() . "</link>\n";
$rss .= qq{<atom:link href="$ScriptName?} . RcSelfAction() . qq{" rel="self" type="application/rss+xml" />\n};
$rss .= qq{<atom:link href="$ScriptName?} . RcPreviousAction() . qq{" rel="previous" type="application/rss+xml" />\n};
$rss .= qq{<atom:link href="$ScriptName?} . RcLastAction() . qq{" rel="last" type="application/rss+xml" />\n};
@@ -1918,7 +1923,7 @@ sub GetRcRss {
$rss .= "<image>\n";
$rss .= "<url>$RssImageUrl</url>\n";
$rss .= "<title>$title</title>\n"; # the same as the channel
$rss .= "<link>$ScriptName?" . RcSelfAction() . "</link>\n"; # the same as the channel
$rss .= "<link>$ScriptName?" . RcSelfWebsite() . "</link>\n"; # the same as the channel
$rss .= "</image>\n";
}
my $limit = GetParam("rsslimit", 15); # Only take the first 15 entries
@@ -2552,23 +2557,30 @@ 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=>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) . ' '
. $q->label({-for=>'delete', -title=>'If you want to replace matches with the empty string'}, T('Delete')) . ' '
. $q->input({-type=>'checkbox', -name=>'delete'})
. $q->submit('preview', T('Preview'));
my $html = GetFormStart(undef, 'get', 'search');
my $replacing = (GetParam('search') ne '' and UserIsAdmin());
$html .= $q->start_p({-class => ($replacing ? 'replace' : 'search')});
$html .= $q->span({-class=>'search'},
$q->label({-for=>'search'}, T('Search:')) . ' '
. $q->textfield(-name=>'search', -id=>'search', -size=>15, -accesskey=>T('f'))) . ' ';
if ($replacing) { # see DoBrowseRequest
$html .= $q->span({-class=>'replace'},
$q->label({-for=>'replace'}, T('Replace:')) . ' '
. $q->textfield(-name=>'replace', -id=>'replace', -size=>20)) . ' '
. $q->span({-class=>'delete'},
$q->label({-for=>'delete', -title=>'If you want to replace matches with the empty string'}, T('Delete')) . ' '
. $q->input({-type=>'checkbox', -name=>'delete'})) . ' '
. $q->submit('preview', T('Preview')) . ' ';
}
if (GetParam('matchingpages', $MatchingPages)) {
$html .= $q->label({-for=>'matchingpage'}, T('Filter:')) . ' '
. $q->textfield(-name=>'match', -id=>'matchingpage', -size=>15) . ' ';
$html .= $q->span({-class=>'match'},
$q->label({-for=>'matchingpage'}, T('Filter:')) . ' '
. $q->textfield(-name=>'match', -id=>'matchingpage', -size=>15)) . ' ';
}
if (%Languages) {
$html .= $q->label({-for=>'searchlang'}, T('Language:')) . ' '
. $q->textfield(-name=>'lang', -id=>'searchlang', -size=>5, -default=>GetParam('lang', '')) . ' ';
$html .= $q->span({-class=>'lang'},
$q->label({-for=>'searchlang'}, T('Language:')) . ' '
. $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;