Compare commits

...

240 Commits

Author SHA1 Message Date
Alex Schroeder
0974b7bbd8 wordcount: fix test 2023-10-25 18:42:25 +02:00
Alex Schroeder
f73d420957 markdown-rule: be more lenient
Don't just limit to full URL: accept any non-whitespace characters as
a link.
2023-10-25 18:40:43 +02:00
Alex Schroeder
17ef2aaf88 CapnDan tells me this line is missing at the end 2023-08-12 21:22:49 +02:00
Alex Schroeder
b70c8e8def Add rolblack marker stripping back in 2023-08-10 11:23:18 +02:00
Alex Schroeder
f8752e69bc Update years 2023-08-10 11:20:46 +02:00
Alex Schroeder
9d48f875a2 Fix rollback code 2023-08-08 16:31:09 +02:00
Alex Schroeder
39e9cea7b0 Add Matched sub
This allows the use of whitespace to separate terms when using page
matching. This seems more natural than requiring underscores to match
the space between words in a page title. So now, a page with id
"one_two" is matched by terms such as "one_two", "two_one", "one two"
and "two one" (notice the word order).
2023-08-08 14:59:53 +02:00
Alex Schroeder
e7b718f610 [network-blocker] Ignore missing file 2023-07-15 21:06:04 +02:00
Alex Schroeder
261aeccb3f [network-blocker] New module 2023-07-15 21:00:58 +02:00
Alex Schroeder
a09c846700 Fix a rollback issue
Without this fix, Oddmuse would enter an infinite loop if the list of
items to show began with a rollback tag.
2023-06-30 13:02:03 +02:00
Alex Schroeder
8dbede3813 Tarballs doesn't link to a latest.tar.gz 2023-06-21 22:05:16 +02:00
Alex Schroeder
89d9f27b2a [rename-pages] Close form 2023-05-29 20:24:55 +02:00
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
Alex Schroeder
ceb4c3a9cc Release process: master → main 2021-09-25 16:41:21 +02:00
Alex Schroeder
536757e8e2 ban-contributors: fix get_range
For 8.21.11.127 we have to issues: the key in the result set is
netrange and not inetnum, and the result for netrange is a list of
ranges and not just a single range. The new code no longer presumes to
know the keys. It just goes through all of them, trying to find
something that looks like a range. When it finds an array reference,
it goes through each entry, looking for a range. The first key where
are least one range is found is returned, with all the ranges for that
key, in our case that would be 8.0.0.0 - 8.127.255.255 and 8.21.11.0 -
8.21.11.255.

The ban-contributors code then presents two forms, one for each match.
2021-09-25 16:17:37 +02:00
Alex Schroeder
00af1aa638 New CSS 2021-09-19 23:01:59 +02:00
Alex Schroeder
c2cf3e7b43 namespaces: reload @IndexList before saving
Without this commit, what used to happen is that if ban-contributors
banned a contributor in a namespace, the pageidx of the main space got
overwritten with the pageidx of the namespace: since the values of
@IndexList and %IndexHash remained unchanged.
2021-09-12 18:34:04 +02:00
Alex Schroeder
05c14d37b2 ban-contributors: another regexp improvement 2021-09-09 22:33:41 +02:00
Alex Schroeder
fc3614f291 ban-contributors: improved the IP regex generator
I did not want to use Number::Range::Regex because those regular
expressions are somewhat hard to read, so instead some test cases from
actual spammers were added and the code rewritten to be easier to
understand. It should now also be obvious when it breaks.
2021-09-04 23:39:07 +02:00
Alex Schroeder
e201c77696 namespaces: share BannedContent and BannedHosts
New approach: save the original value of $DataDir in
$NamespacesRootDataDir. When reading the value of $BannedHosts or
$BannedContent via GetPageContent in UserIsBanned or BannedContent,
and in DoBanHosts for ban-contributors.pl, use the root data dir; when
saving $BannedHosts or $BannedContent via DoPost, use the root data
dir.
2021-08-28 00:51:15 +02:00
Alex Schroeder
f280cb5df4 Revert "namespaces: share BannedContent and BannedHosts"
This reverts commit 29863d7109.
2021-08-28 00:09:34 +02:00
Alex Schroeder
29863d7109 namespaces: share BannedContent and BannedHosts
To facilitate spamfighting, the namespace is not set when the current
action refers to one of the page ids in the @NamespaceIgnored list.
The default value for these is $BannedContent and $BannedHosts, in
other words, the pages 'BannedContent' and 'BannedHosts'.
2021-08-26 23:52:58 +02:00
Alex Schroeder
b514ea7846 Fix TRANSLATIONS glob in the Makefile
The current code always resulted in an empty list of files for
TRANSLATIONS; the did not end up in the build directory; and they did
not get installed elsewhere.
2021-08-08 09:10:38 +02:00
Tobias Fendin
eeaf615d3b Added more swedish translations 2021-08-08 08:56:20 +02:00
Tobias Fendin
f003481c5e Translated webmention to swedish 2021-08-08 08:56:14 +02:00
Alex Schroeder
4d10ef389a Add script/unsusbscribe.pl
Mail subscriptions for blog posts that are many years old make no
sense to me. Here's a script to purge those old subscriptions.
2021-08-02 15:50:33 +02:00
Alex Schroeder
726ffdced1 banned-regexps: do not remove URLs from the text
If we want to match borked spam like <a href=http://example> then it's
counterproductive if we remove the URLs because our pattern will have
to be "href=" instead of "href=http". Also it's hard to remember that
URLs are removed.
2021-07-21 15:15:05 +02:00
Alex Schroeder
18c4071da8 t/test: handle spaces in PERLBREW_PATH
When running under the fish shell, PERLREW_PATH contains two
directories separated by a space.
2021-07-13 15:10:53 +02:00
Alex Schroeder
fd7fa0c3ab Add progress indicator to stats 2021-07-13 15:09:21 +02:00
Alex Schroeder
2ba5b72242 stats.pl is a new script to print some stats for a page dir 2021-07-13 14:57:38 +02:00
Alex Schroeder
fa5a2f7a1a rc2mail: print $root
When sending mails for multiple wikis, it's important to show for what
wiki the mails were sent, given that the user asked us to be verbose.
2021-07-13 10:38:35 +02:00
Alex Schroeder
ad042630b6 ban-contributors: fix regexp generator
The code would often prematurely end a regular expression with $.
2021-07-13 10:38:35 +02:00
Alex Schroeder
9cf35b9b52 Updated Swedish translation 2021-07-09 23:40:48 +02:00
Alex Schroeder
4f69103b8c wiki: simplify a regular expression
In OpenHtmlEnvironment we simplyfy the regular expression that is
supposed to detect whether this is a class assignment to a simple
check whether the attribute contains an equal sign.
2021-03-26 15:59:56 +01:00
Alex Schroeder
37c882780a emoji: add \b after :p
This prevents something like time:prime to trigger the emoji
replacement.
2021-03-26 15:58:15 +01:00
Alex Schroeder
6d5f97e1ba journal-rss: fix headers for raw output 2021-02-20 11:17:56 +01:00
Alex Schroeder
4b1063c699 journal-rss: add one more test 2021-02-20 10:18:24 +01:00
Alex Schroeder
b891674a6f journal-rss: support raw mode 2021-02-20 09:51:31 +01:00
Alex Schroeder
1a65df6e36 Update the French translation
Submitted by pierre. Thank you very much!
2021-01-04 23:03:22 +01:00
Alex Schroeder
6043be852c Fix handling of $RssExclude
It contains a list of regular expressions, not of page names.
2020-12-09 12:39:03 +01:00
Alex Schroeder
bb11bdf789 Updated the tests for HTML5
All tests successful.
Files=153, Tests=3483, 128 wallclock secs ( 0.85 usr  0.22 sys + 228.35 cusr 26.61 csys = 256.03 CPU)
Result: PASS

All right!! 😁
2020-10-29 17:02:19 +01:00
Alex Schroeder
540fd588c9 Incompatible HTML changes
Trying to get more HTML5 elements used.

PrintAllPages:

Use the article element instead of a div with class "page". The new
article element still has the "h-entry" class that the old div had.

The h1 element for these pages used to have the class "entry-title"
which is apparently deprecated. The new code now uses the "p-name"
attribute.

The page content is no longer surrounded in a div with the
"entry-content" class and the appropriate lang attribute. We rely on
PrintPageHtml to do the right thing, now.

PrintPageHtml:

Surround the page being printed with a div containing the "e-content"
class and an appropriate lang attribute.

PageHtml:

This also uses PrintPageHtml and therefore doesn't need to surround
the page content with a div containing the "page" class and the lang
attribute.

As PageHtml is used in RSS feed generation, that means that the feed
entries now don't have a div containing the "page" class but a div
containing the "e-content" class.

GetHeaderDiv:

Instead of using a div with the "header" class, use the header
element.

Instead of using a div with the "menu" class, use the nav element.

PrintPageContent:

No changes! We're not changing the div here because the content that
is being printed here does not belong into an article element. It is
not "a self-contained composition in a … page … intended to be
independently distributable or reusable" – it *is* the page
itself (without the h1 header).

PrintFooter:

Use an additional footer element.

DefaultFooter:

Remove the div with the "footer" class.

References:

* http://microformats.org/wiki/h-entry
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer
2020-10-27 21:34:02 +01:00
Alex Schroeder
278fad1f43 Fixed development target in Makefile
The problem is that by default the test-data/config file contains
$ScriptName = 'http://localhost/wiki.pl' but morbo serves the site at
http://127.0.0.1:8080. We therefore append a new $ScriptName
assignment if the correct one doesn't exist. The alternative is
tricky because of the /wiki.pl prefix; fixing that would require a lot
more code, I suspect.
2020-10-25 10:31:36 +01:00
Alex Schroeder
eadeb460f5 Fixed tests
DuckDuckGo search doesn't use the www subdomain anymore.

The raw recent changes returns the bogus hash (four octal digits)
instead of Anonymous before maintenance anonymises the entry.
2020-08-12 21:09:28 +02:00
Alex Schroeder
5da9ce64c0 Lazy loading of images
Use the new loading="lazy" attribute for images.
2020-08-12 20:53:58 +02:00
Alex Schroeder
40498b53f7 duckduckgo-search: no www subdomain
Use duckduckgo.com intead of www.duckduckgo.com.
2020-07-29 09:09:42 +02:00
Alex Schroeder
eaf97602ff Make sure the bogus hash is served for raw changes
When serving recent changes, we know the username and host of the
person making the edit. We use GetAuthorLink to show either the name
linked to the username, or "Anonymous", or a colour coded bogus hash
of their host (that's the four octal digits, hopefully colourized by
your CSS).

When serving raw changes, we used to serve just the username or
"Anonymous". In order to help use cases such as the Gemini wiki
running on gemini://alexschroeder.ch:1965 which consumes raw changes
to present a view that is compatible with Gemini Wiki, we'd like those
bogus hashes as well. This comit does that by splitting ColorCode into
Code and ColorCode such that we can use Code when serving raw changes.
2020-07-23 11:49:42 +02:00
Alex Schroeder
987c262425 wiki: add n limit to index action
Useful when retrieving the latest blog pages from a wiki using raw=1.
2020-07-16 18:11:25 +02:00
Alex Schroeder
c33ee0a9e6 markdown-rule: add one more test 2020-07-13 11:26:25 +02:00
Alex Schroeder
eb7665661f gemini-server: handle Gemini markup
Up to now it was assumed that the raw wiki text would not be written
as Gemtext, but increasingly that is not the case. This commit adds
handling of Gemtext links.
2020-07-12 13:02:02 +02:00
Alex Schroeder
72ae1bf56f gemini-server: fix month in Atom date 2020-07-03 13:41:24 +02:00
Alex Schroeder
8f30ed8109 gemini-server: don't require a space after URL 2020-07-02 17:34:43 +02:00
Alex Schroeder
19e71f1180 gemini-server: clean up feed generation
Reorganize the code a little bit, removing some useless statements.
Make sure it workes with the journal-rss.pl module. Add tests.
2020-07-01 10:34:45 +02:00
Alex Schroeder
9397a38394 gemini-server: add RSS and Atom feeds 2020-06-30 22:48:54 +02:00
Alex Schroeder
17bd2d08cd gemini-server: small updates
gemini_link now handles URLs and is used for all links in
serve_gemini_page.

Paragraph splits now happen at the beginning of list items and when
line breaks are requested. It's not great but what else are you going
to do?

Handle image links.

Handle HTML tags (by ignoring them).

Raw pages served as text/plain instead of text/markdown.
2020-06-22 09:04:39 +02:00
Alex Schroeder
47a5e81000 Run extension even if testing
That is, run the gemini_config file before surge protection!
2020-06-17 23:34:37 +02:00
Alex Schroeder
7bfe740fb2 gemini-server: add language support 2020-06-16 23:08:22 +02:00
Alex Schroeder
6a324b59b9 gemini-server: move run_extensions to the top 2020-06-16 00:08:08 +02:00
Alex Schroeder
23545006a5 gemini-server: add diff support 2020-06-15 20:13:39 +02:00
Alex Schroeder
65012eacbb gemini-server: add history page support
Makefile now also has a gemini target to start up a gemini wiki. It
also calls openssl to generate keys if necessary.

gemini-server.pl now also has log messages in the various functions
serving content, instead of having some of them in the main function.
The footer is printed in a separate function.
2020-06-15 17:19:56 +02:00
Alex Schroeder
91107143f3 gemini-server: switching from gemini+write to titan 2020-06-14 12:34:43 +02:00
Alex Schroeder
cafda90555 gemini-server: various improvements
Render colours (from bbCode).

Serve HTML, including link from footer.

Fix escaping of code blocks.

Remove /m flag from most block substitutions.
2020-06-13 13:51:31 +02:00
Alex Schroeder
32dfec102d gemini-server: add support for a config file 2020-06-13 01:44:26 +02:00
Alex Schroeder
c1cdca5f95 gemini-server: more tests
Also chaged two more permanent redirects to temporary redirects.
2020-06-11 23:45:16 +02:00
Alex Schroeder
61dc928e33 gemini-server: write tests, fix bugs
Fixed sorting. Added \r to some of the links. Allow loading
gemini-server.pl as a library. Don't force the display of ten links in
the main menu unless we actually have as many day pages. Change URL
for minor recent changes.
2020-06-11 13:53:56 +02:00
Alex Schroeder
d43fe3fab9 gemini-server: more improvements
Fixed [URL text] and [[in-reply-to:URL|text]] patterns: don't pass an
URL to gemini_link!

Some log output when writing files. Don't double-decode UTF-8 when
writing text pages.

Render pages ending in '.txt' as raw text. No folding of robots.txt!

Fix two confusing calls to UrlEncode at the end: when serving Gemini
pages and raw text pages, don't URL-encode the page names, use
FreeToNormal to get valid page ids!
2020-06-08 21:34:44 +02:00
Alex Schroeder
3acb572c0d gemini-server: small improvements
Fiddle with the block parsing... Perhaps the single /\n\* / match is
unnecessary? Let's wait for a case where this is wrong. Basically that
would be a paragraph followed immediately by a list item
* like this

I don't think people write it like this when writing for the wiki.

Support the special case <journal search tag:foo> for tag pages.

Fix handling of newlines for blocks that are just links and the like.
2020-06-08 14:09:18 +02:00
Alex Schroeder
0f6787d349 gemini-server: use temporary redirects, always 2020-06-07 22:04:46 +02:00
Alex Schroeder
af287a1279 gemini-server: various improvements
No longer wrap paragraphs. In fact, unwrap paragraphs and list items
because that's what the specification says: Each line is to be wrapped
separately.

Allow single line/paragraph comments. This requires the QuestionAsker
extension and supports the questions and answers.

Support in-reply-to links. Support Markdown links.

Sort pages and their comments correctly even if not day pages.

Fix URL-encoding of $id whenever a URL is printed (for redirection
using 30 and 31, for example), and fix normal form (underscores
instead of spaces for $id).
2020-06-07 21:21:08 +02:00
Alex Schroeder
6bbd43f8a3 gemini-server: various improvements
Do not URL-escape the slash.

Reorganize the main menu and add a 'New page' link.

Change the tag format from $id/tag to tag/$id.

Change the naming so that "text page" is now a "raw page" served via
raw/$id.

Only allow editing of raw pages.

Improve transformation of wiki raw text to Gemini format. Handle tags
with alternate text and images.

Add a footer to Gemini format pages.

When editing existing pages, always make it a minor edit. That matches
how I use the wiki as a blog. It might not be correct for a wiki used
as an encyclopedia.

Handle spaces in $id.
2020-06-07 12:01:27 +02:00
Alex Schroeder
364d7c695b gemini-server: serve Gemini, best effort 2020-06-06 14:22:57 +02:00
Alex Schroeder
871af41881 gemini-server: add searching, matching, sorting 2020-06-05 23:19:03 +02:00
Alex Schroeder
4648bfbd83 wiki: make url-decoding case insensitive 2020-06-05 23:00:31 +02:00
Alex Schroeder
129d02850b gemini-server: answer with a redirect after saving 2020-06-05 13:16:45 +02:00
Alex Schroeder
ee23ef509c gemini-server: fix issues to allow writes
Remaining known problem: HTML output when an error occurs (and status
line at the end saying that everything went fine).
2020-06-05 00:54:58 +02:00
Alex Schroeder
7e865696b0 gemini-server: add write support 2020-06-04 19:59:27 +02:00
Alex Schroeder
244d06ca3b gemini-server: fix recent changes and rss 2020-05-31 19:40:51 +02:00
Alex Schroeder
1a59075b51 gemini-server: new 2020-05-31 00:34:59 +02:00
Alex Schroeder
e0b3c18499 mail: allow unsubscription from all pages
Fix unsubscription from all pages when no list of pages has been
provided: do not return early.
2020-05-27 08:36:34 +02:00
Alex Schroeder
5434136a4d mail: allow unsubscription from all pages
Simplify code. Fix display of email list: URL decoding email addresses
and printing the email addresses in the list. Fix the feedback message
after unsubscribing somebody from all pages.
2020-05-27 08:32:39 +02:00
Alex Schroeder
aeeb182dad mail: allow unsubscription from all pages
A new action (subscribers) also just lists email addresses to make
this easier.
2020-05-27 07:54:07 +02:00
Alex Schroeder
4d6882ffc7 ip-to-regexp.pl: new command line script
Helps to block entire IP blocks.
2020-05-27 07:51:56 +02:00
Alex Schroeder
828482f439 webmention: various updates
scripts/webmention.pl now extracts the author of FROM

modules/webmention.pl now is less complicated about skipping
webmention endpoint advertising and it tries to use the author and
link advertised from the incoming webmention page

And the tests were amended.
2020-05-03 21:09:30 +02:00
Alex Schroeder
2b6f2dfa0c pygmentize: add a test 2020-05-03 14:33:58 +02:00
Alex Schroeder
413b43174c webmention: avoid warning about CGI::param
We're using it in list context, now, and that generates a warning.
Using multi_param avoids this.
2020-05-03 11:49:19 +02:00
Alex Schroeder
9709c87185 webmention: better UI 2020-05-03 10:54:50 +02:00
Alex Schroeder
0cca358de2 webmention: add UI 2020-05-03 10:02:44 +02:00
Alex Schroeder
acff0cb69f Add tests for grep-filtered 2020-03-19 20:45:14 +01:00
Alex Schroeder
8d5956cb7f grep-filtered: old/new module 2020-03-19 14:07:59 +01:00
Alex Schroeder
e9773ea694 Add wordcount module 2020-02-06 14:01:33 +01:00
Alex Schroeder
7094ec098b stuff/server: fix UTF-8 encoding problem
Sadly, I would have thought that the correct solution is the
following, at the end, once we have $server:

$server->cgi_init(
  sub {
    require CGI;
    CGI::initialize_globals();
    CGI->import(qw(-utf8));
  });

This is based on the documentation of HTTP::Server::Simple::CGI, and
skimming its source code, and the documentation of the use pragma, and
the pseudo code in its documentation. The workaround of setting
$CGI::PARAM_UTF8 directly is based on reading the CGI.pm source code.
This is definitely a hack.
2019-12-28 18:11:49 +01:00
Alex Schroeder
04fe4d6991 stuff/server: better instructions at the beginning 2019-12-28 13:59:55 +01:00
Alex Schroeder
570a6b1f07 stuff/server: use ./wiki.pl instead of wiki.pl 2019-12-28 13:45:19 +01:00
Alex Schroeder
4211d6aa03 definition-lists: new 2019-10-14 22:36:32 +02:00
Alex Schroeder
5941fcd7e6 recaptcha: fix tests
check_answer_v2 requires an answer or it throws the error "To check
answer, the user response token must be provided". That causes all the
tests to fail. Thus, if no answer is provided, we return 0 (false).
2019-10-06 18:04:44 +02:00
Alex Schroeder
f3df2eb289 Merge pull request #21 from fancypantalons/master
Switch to reCAPTCH v2 API.
2019-10-06 09:00:03 +02:00
Brett Kosinski
8b975cd5e5 Switch to reCAPTCH v2 API.
The v1 API has been deprecated, so this changeset switches to using the
v2 API as exposed by the Captch::reCAPTCHA module.

The main changes are to switch to the _v2 methods for get_html and
check_answer, plus necessary changes to the check_answer parameters to
make the code compatible with the new form field naming.
2019-10-05 19:37:27 -06:00
Alex Schroeder
850f292260 rc2mail: switch to Net::SMTP and Authen::SASL 2019-10-04 18:33:32 +02:00
Alex Schroeder
ced1252b9d new-utf8: new modules add new translations 2019-10-04 18:32:14 +02:00
Alex Schroeder
c0a6bfebf2 Add language to body element and various divs
The benefit is that this allows us to use the CSS rule that allows the
browser to hyphenate words:

body { hyphens: auto }

This required a number of changes.

wiki.pl

- new option, $CurrentLanguage
- GetHtmlHeader adds lang attribute to body
- PrintAllPages adds lang attribute to div
- PrintPageContent adds lang attribute to div
- PageHtml adds div with lang attribute
- GetLanguages sorts languages by occurences
- GetLanguage is new and returns the first language of GetLanguages

Modules that had to be changed because they refer to the divs that
have now been changed:

- crossbar.pl

Tests that had to be changed:

- atom.t
- crossbar.t
- hr.t
- languages.t
- portrait-support.t
- rss.t
2019-09-27 21:34:32 +02:00
Alex Schroeder
36815767f3 markdown-rule: allow to set text-align style 2019-09-19 10:38:47 +02:00
Alex Schroeder
2cba2d30b7 Makefile: add a morbo target
This makes it easy to quickly deploy the wiki standalone using
Mojolicious. It reloads automatically when any of the relevant files
are changed. This might be easier to work with than a local webserver
installation.
2019-07-16 23:02:10 +02:00
Alex Schroeder
fded17520c namespaces: add test for numerical namespaces 2019-07-16 22:58:43 +02:00
Alex Schroeder
46580a3958 gopher-server: fix linebreaks in links 2019-06-24 12:08:36 +02:00
Alex Schroeder
d584899447 markdown-rule: test removal of MarkdownExtraRule 2019-06-24 11:03:53 +02:00
Alex Schroeder
79c0a9fd02 gopher-server: fix test
The test didn't specify a host explicitly. On my systems, that meant
"localhost" sometimes ended up being "127.0.0.1" or "::1" but for
Alexine it ended up being "::ffff:127.0.0.1". Let's hope that
explicitly setting "127.0.0.1" fixes all of this.
2019-06-24 10:50:14 +02:00
Alex Schroeder
c3f21d60c2 markdown-rule: fix > block quoting 2019-06-24 10:29:02 +02:00
Alex Schroeder
43221ea8a9 markdown-rule: fix """ quotes 2019-06-24 10:19:33 +02:00
Alex Schroeder
25350e93cb markdown-rule: allow newlines in link text
Also added """ for blockquotes.
2019-06-21 23:51:50 +02:00
Alex Schroeder
415a8aa9a8 gopher-server: more links, more tests 2019-06-20 14:27:32 +02:00
Alex Schroeder
a3d740aa67 gopher-server: Gopher URLs can have no type 2019-06-20 12:35:04 +02:00
Alex Schroeder
1956335640 Support gophers URL scheme 2019-06-20 12:31:31 +02:00
Alex Schroeder
b89c8b99e8 Example configs for Mojolicious 2019-06-10 15:02:10 +02:00
Alex Schroeder
4e4d8ee784 Webmention: new feature
This deprecates pingback-server!
2019-05-24 21:43:25 +02:00
Alex Schroeder
7ecd7b784e t: updated copyright years 2019-05-08 08:01:54 +02:00
Alex Schroeder
0d9764b0de gopher-server: fix space normalisation in links 2019-05-04 18:33:36 +02:00
Alex Schroeder
5d7964977c tags: hide the taglist action 2019-04-13 19:33:36 +02:00
Alex Schroeder
d50cda9cea tags: add $TagCloudSize 2019-04-13 19:31:32 +02:00
Alex Schroeder
7257bfb6d5 tags.pl: use HTML::TagCloud to generate cloud 2019-04-13 19:21:55 +02:00
Alex Schroeder
f2470256ae server: fix shebang line with better env path 2019-04-13 19:21:55 +02:00
Alex Schroeder
3c7a56ba5a gopher-server: documentation in the README
And:

- fix typo in the comments of gopher-server.pl
- add log level 4 output to debug how the data directory is sets
2019-04-13 19:21:55 +02:00
Alex Schroeder
07d68b4400 journal-rss: fix tests 2019-03-20 12:12:08 +01:00
Alex Schroeder
51d2d3e0ca tarballs: support "latest" release 2019-03-19 14:50:43 +01:00
Alex Schroeder
0a361873de Fix RSS links in feed
Trying to fix all the issues the validator noticed.
2019-03-19 14:47:39 +01:00
Alex Schroeder
6af02a8a1e Fix quoting of RC and RSS link parameters
When generating RC and RSS link parameters, the values of the
parameters were not URL encoded. This is now fixed.
2019-03-19 14:46:29 +01:00
Alex Schroeder
dc283ea828 tarballs: use Sort::Versions 2019-03-02 14:44:36 +01:00
Alex Schroeder
8e3917b933 rename-pages: add a space in front of the button 2019-03-02 14:27:06 +01:00
Alex Schroeder
5a2209a0c2 rename-pages: fix 'for' attribute for label 2019-03-01 21:00:04 +01:00
Alex Schroeder
73b6949b95 meta: Fix text for 'rename' 2019-03-01 20:59:50 +01:00
Alex Schroeder
771b07e661 rename-pages: new 2019-03-01 20:34:11 +01:00
Alex Schroeder
629693e353 Buf fix in DoMaintain
Frank Betten noted about the old code "$Now - Modified($fname) < 0.5"
that the "left-hand side is in seconds, right-hand side in days".
This was fixed by replacing 0.5 with 12 * 60 * 60.
2019-02-11 21:29:49 +01:00
Alex Schroeder
a45e13f74c pingback-client: add license and intro paragraph 2019-02-01 23:28:03 +01:00
Alex Schroeder
a919f4b8fd Add pingback-client for some experimenting 2019-01-29 13:56:14 +01:00
Alex Schroeder
6d37baabd9 Add pingback-server tests
Also change the HTTP status messages from the pingback-server for
easier debugging. After all, the message text can be whatever we want.
In order to know which of code 400 errors we ran into, use different
messages for each.
2019-01-27 20:07:31 +01:00
Alex Schroeder
41f18d0b3e Make sure we can start multiple servers in tests 2019-01-27 20:06:53 +01:00
Alex Schroeder
8a5cce103e pingback-server: complete rewrite 2019-01-26 23:38:44 +01:00
Alex Schroeder
65cfd93de9 Fix @IndexOptions
Frank Betten reported that after installing the module for Permanent
Anchors the parameter permanentanchors has no effect and there's no
checkbox on the index page. The reason is that Permanent Anchors used
to add to @IndexOptions upon loading (InitModules), but commit
15263102 had moved the setting of @IndexOptions into InitVariables,
thus overwriting any changes made during InitModules. In order to have
an effect, @IndexOptions has to be modified via @MyInitVariables. All
of this is necessary because @IndexOptions uses translated strings and
these are themselves loaded via modules so setting @IndexOptions at
InitModules time can be too early. This was fixed with commit deec99c,
necessitating all these follow-up changes.
2019-01-24 22:22:45 +01:00
Alex Schroeder
a67cac80f5 More support for microformats
On Journal pages, add the classes h-feed for the entire feed, h-entry
for a single page, entry-title for the header, and entry-content for
the page content.
2019-01-20 21:01:35 +01:00
Alex Schroeder
c73f224f2d Allow - in image classes for hCard compatibility 2019-01-19 12:09:39 +01:00
Alex Schroeder
f241d7cd77 Format the summaries of changes in a diff
Make sure to always call QuoteHtml on the summary to avoid XSS
attacks.
2019-01-19 09:33:07 +01:00
Alex Schroeder
a5f5607667 Format the summaries of changes in a diff
Make sure entries consisting of nothing but whitespace are also
skipped.
2019-01-18 21:09:07 +01:00
Alex Schroeder
2d7451402f Format the summaries of changes in a diff
When there is more than one summary, use a list.
2019-01-18 20:15:07 +01:00
Alex Schroeder
7e13816d40 diff.t: removed duplicate tests
The last commit duplicated some tests. This commit simply removes them
again and fixes the number of tests to run.
2019-01-18 19:21:50 +01:00
Alex Schroeder
c2231b9e39 Show all the summaries of changes in a diff 2019-01-18 19:19:42 +01:00
Alex Schroeder
58ecb96975 Add UplinkedIntelligence
Set $HomePage
2018-12-10 13:40:19 +01:00
Alex Schroeder
d24b7d8bd3 mail: allow unsubscription from non-existing pages 2018-12-09 18:01:33 +01:00
Alex Schroeder
91e054cea4 wiki: fix RSS 3.0 and support full parameter 2018-11-30 23:49:30 +01:00
Alex Schroeder
be2abffde2 Revert "gopher-server: add support for RSS Gopher Module"
This reverts commit 93b706dd28.
2018-11-30 22:18:05 +01:00
Alex Schroeder
93b706dd28 gopher-server: add support for RSS Gopher Module 2018-11-30 16:53:52 +01:00
Alex Schroeder
8f43c818df journal-rss: fix bug
The old code assumed that a kept revision for the last major revision
would always exist. But if you just create a page using a minor edit, it
still gets shown, triggering a call to die. This fixes it.
2018-11-26 08:58:21 +01:00
Alex Schroeder
8194d6bb81 permanent-anchors: fixed page deletion
NewPermanentAnchorsDeletePage called DeletePermanentAnchors which
ended with ReleaseLockDir and that is wrong if a lock exists: it’ll
return the value in %Locks which is going to be 1. Thus, with this
module installed, pages would get deleted, but maintenance would still
print: "X not deleted: 1". We fix this by return the empty string from
NewPermanentAnchorsDeletePage.
2018-11-23 18:55:25 +01:00
Alex Schroeder
dc203917b9 german-utf8: updated translation 2018-11-21 10:15:51 +01:00
Alex Schroeder
e7818ef43e translations: updated with new keys 2018-11-21 10:12:24 +01:00
Alex Schroeder
37c4a19f1f Makefile: sort modules
This makes sure that translations have small diffs.
2018-11-21 10:11:48 +01:00
Alex Schroeder
864f294761 translation-stats: fix @INC 2018-11-21 09:57:05 +01:00
Alex Schroeder
1534d6dd8f markdown-convert: all users may use the menu 2018-11-21 09:40:03 +01:00
Alex Schroeder
9ae728bf13 markdown-convert: fix another hungry regexp 2018-11-17 00:03:28 +01:00
Alex Schroeder
768244860f markdown-convert: inline code, fixed 2018-11-17 00:01:18 +01:00
Alex Schroeder
95e566ae97 markdown-conert: inline code 2018-11-16 23:55:21 +01:00
Alex Schroeder
040545269a markdown-conert: fix subheadings
Don't require equal signs at the end; be flexible with spaces at the
beginning.
2018-11-16 23:52:07 +01:00
Alex Schroeder
c6b7d952bd markdown-conert: fix multiple code blocks 2018-11-16 23:48:52 +01:00
Alex Schroeder
06067dd1c1 markdown-convert: strip marker from HTML
On campaignwiki.org we use the code
https://oddmuse.org/wiki/Creole_or_Markdown which also strips the
marker, but we want to support people who don't install that code.
2018-11-16 13:39:36 +01:00
Alex Schroeder
6ee6b384cf markdown-convert: skip uploaded files 2018-11-16 13:17:36 +01:00
Alex Schroeder
7cdf2abe2d markdown-convert: use utf8 2018-11-16 13:11:28 +01:00
Alex Schroeder
942d77f599 markdown-convert: use ordered list for candidates 2018-11-16 13:10:16 +01:00
Alex Schroeder
71baa51a54 markdown-convert: add option to list unconverted pages 2018-11-16 13:05:54 +01:00
Alex Schroeder
8819183be3 markdown-convert: fix space issue 2018-11-16 12:43:17 +01:00
Alex Schroeder
0941a09c19 markdown-convert: fix AddModuleDescription 2018-11-16 12:30:12 +01:00
Alex Schroeder
eae48212be markdown-convert: Add #MARKDOWN marker 2018-11-16 11:45:19 +01:00
Alex Schroeder
4f9641c43f markdown-convert: new module 2018-11-15 22:18:02 +01:00
Alex Schroeder
32fa6057c7 gotobar: support $$id in the GotoBar page 2018-11-08 08:29:54 +01:00
Alex Schroeder
025907f65a gotobar: support $id in the GotoBar page 2018-11-08 08:12:53 +01:00
Alex Schroeder
a259edc0bb bbcode: add [br] 2018-10-21 10:01:48 +02:00
Alex Schroeder
94fd9c0c2a journal-rss: minor changes don't update pubDate
If this is a minor edit, let's keep everything as it is, but show the
date of the last major change, if possible. This is important for
blogs that get added to a Planet. A minor change doesn't mean that the
page needs to go to the front of the Planet.
2018-10-17 23:09:10 +02:00
Alex Schroeder
0affd5a1b7 rc2mail: suppress one more print if -q is given 2018-10-07 18:15:30 +02:00
Alex Schroeder
f27ab9a8a0 gopher-server: support gopher tags
See gopher://circumlunar.space:70/0/~solderpunk/phlog/tagged-gophers.txt
2018-10-07 15:39:51 +02:00
Alex Schroeder
086a992b5a olocalmap.pl: use GetId 2018-09-11 13:11:44 +02:00
Alex Schroeder
54cae9bc1d $SurgeProtection is used by gopher-server.pl 2018-09-10 12:15:02 +02:00
Alex Schroeder
9d9c196441 Add comment, removed unused $SurgeProtection 2018-09-10 11:29:56 +02:00
Alex Schroeder
9238e42cfd banned-regexps: updated copyright year 2018-09-04 09:54:04 +02:00
Alex Schroeder
836f3abecb Fixed bug in banned-regexps
BannedRegexps needs to check for BannedContent before it does
anything. BannedContent checks the URLs, and so BannedRegexps wants to
check everything except for the URLs. The order is important: the old
code used to remove the URLs before handing the text to BannedContent,
so it never found a thing. Changing the order fixed this.
2018-09-04 09:45:47 +02:00
Alex Schroeder
d669f783bb t/ban: test for homepage field 2018-09-04 09:37:49 +02:00
Alex Schroeder
c18279ca4c alex-2018.css: bigger fonts, wrapping pre 2018-08-24 11:29:23 +02:00
Alex Schroeder
bbd280abf9 ban-mixed-script: better fix for encoding issue 2018-08-14 15:01:01 +02:00
Alex Schroeder
c15e6041ef ban-mixed-scripts: silence meta.t warning 2018-08-14 11:57:26 +02:00
Alex Schroeder
e4c84d9b3a ban-mixed-scripts 2018-08-12 19:43:58 +02:00
Alex Schroeder
a49afca3f7 gopher-server: fix regular expressions for links
The regular expression now also matches mardown style external links.
Now using /x and named groups. This makes the entire thing much more
readable.
2018-08-10 20:45:11 +02:00
Alex Schroeder
883f07f926 Fix URL in Makefile 2018-08-06 12:07:18 +02:00
Alex Schroeder
b5d182582f Fix URLs in AddModuleDescription 2018-08-06 11:19:45 +02:00
Alex Schroeder
fe26471abb wiki.css: update for All Modules
Although I must say, the more I see of Div Foo, the less I like it. I
can't wrap a div around the <titles ...> rule and so now I use the
following on the All_Modules page:

<list>
<titles ...>

This introduces an empty div before the div I care about and the CSS
then does .foo_list + .journal etc.
2018-07-25 17:21:01 +02:00
Alex Schroeder
96a3cbbb22 tags: fix double quote bug
The old code removed double quotes before passing the search string on
to SearchString. Thus, "a b" would become a search for "a" and "b".
2018-07-24 12:56:08 +02:00
Alex Schroeder
d303e5e955 journal-rss: switch to full page feed always 2018-07-19 16:18:17 +02:00
Alex Schroeder
dd6198ffca journal-rss.t: simplify tests accordingly
Once the journal RSS just uses the latest edits, we don't have to care
about all the stuff involving major and minor versions.
2018-07-19 16:15:29 +02:00
Alex Schroeder
ae646290ed journal-rss: fix handling of new offset parameter 2018-07-19 16:02:24 +02:00
Alex Schroeder
1a74dc9605 journal-rss: don't ignore minor changes
If a page was created by just minor edits, then it is visible in a
journal. We shouldn't skip it from the journal RSS. Furthermore:
what's the point in showing the text from the last major version? Show
the latest version!
2018-07-19 15:52:17 +02:00
Alex Schroeder
0c19bd13cd tags.t: some more tests for filtering RC 2018-07-19 15:38:14 +02:00
Alex Schroeder
42bb4888a2 rss-journal: support feed pagination 2018-07-19 14:36:27 +02:00
Alex Schroeder
9a5e44aba3 Make feed pagination possible for journal-rss.pl
This works without changes to journal-rss.pl itself. All we do is make
sure that RcSelfAction, RcPreviousAction, and RcLastAction don't get
the action (rc vs. rss) from their arguments but determine it by
looking at the script parameters. Since journal-rss simply uses the
journal action, this will then get picked up correctly.
2018-07-19 13:04:31 +02:00
Alex Schroeder
78b5dd3737 rss.t: add more tests for RSS pagination 2018-07-19 11:43:07 +02:00
Alex Schroeder
b8717c4084 rss.t: add tests for RSS pagination 2018-07-19 11:39:06 +02:00
Alex Schroeder
42f4816ae5 gopher-server: surge protection disabled for tests
Make sure the gopher server reads the config file and skips surge
protection if $SurgeProtection is set to 0. The tests do this. If we
don't do this, tests start failing after a while.
2018-07-19 00:10:24 +02:00
Alex Schroeder
ff8d671787 wiki: first draft at implementing RFC 5005
We can easily provide the latest and the previous feed URL in addition
to the current (self) URL.
2018-07-18 23:53:18 +02:00
Alex Schroeder
e16cda46c1 gopher-server: rewrote surge protection
Instead of calling DoSurgeProtection as part of process_request and
faking username and overwriting ReportError, I've moved the essential
code to allow_deny_hook which seems to be the place dedicated to this
sort of code. Sadly, push(@{$self->{server}->{deny}}, $peeraddr) seems
to have no effect. When the next request comes around, the deny list
is empty again. I guess we would have to persist this somehow. For
now, we're simply using allow_deny_hook and that seems to be just as
well.
2018-07-16 21:34:11 +02:00
Alex Schroeder
431e792e52 gopher-server: add surge protection 2018-07-16 09:37:39 +02:00
Alex Schroeder
c9f3060e42 release: accept non-numeric versions like 2.3.11a 2018-07-13 00:52:35 +02:00
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 &amp; 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
156 changed files with 6822 additions and 1048 deletions

View File

@@ -3,8 +3,8 @@
# subdirectory.
VERSION_NO=$(shell git describe --tags)
TRANSLATIONS=$(wildcard modules/translations/[a-z]*-utf8.pl$)
MODULES=$(wildcard modules/*.pl)
TRANSLATIONS=$(wildcard modules/translations/*-utf8.pl)
MODULES=$(sort $(wildcard modules/*.pl))
BUILD=build/wiki.pl $(foreach file, $(notdir $(MODULES)) $(notdir $(TRANSLATIONS)), build/$(file))
# PREPARE/BUILD: this creates copies of wiki.pl and all the modules
@@ -25,7 +25,7 @@ release:
perl stuff/release ~/oddmuse.org/releases 2.3.3
build/wiki.pl: wiki.pl
perl -lne "s/(\\\$$q->a\(\{-href=>'https:\/\/www.oddmuse.org\/'\}, 'Oddmuse'\))/\\\$$q->a({-href=>'https:\/\/git.savannah.gnu.org\/cgit\/oddmuse.git\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
perl -lne "s/(\\\$$q->a\(\{-href=>'https:\/\/www.oddmuse.org\/'\}, 'Oddmuse'\))/\\\$$q->a({-href=>'https:\/\/alexschroeder.ch\/cgit\/oddmuse\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
build/%-utf8.pl: modules/translations/%-utf8.pl
perl -lne "s/(AddModuleDescription\('[^']+', '[^']+')\)/\$$1, 'translations\/', '$(VERSION_NO)')/; print" < $< > $@
@@ -60,3 +60,21 @@ jobs ?= 4
test:
prove t/setup.pl
prove --jobs=$(jobs) --state=slow,save t
# Spin up a quick test
development:
@if grep --quiet 'ScriptName = "http://127.0.0.1:8080";' test-data/config; then \
echo Not overwriting \$$ScriptName in test-data/config; \
else \
echo '$ScriptName = "http://127.0.0.1:8080";' >> test-data/config; \
fi
morbo --listen http://*:8080 \
--watch wiki.pl --watch test-data/config --watch test-data/modules/ \
stuff/mojolicious-app.pl
%.pem:
openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout key.pem
gemini: cert.pem key.pem
perl stuff/gemini-server.pl --wiki_cert_file=cert.pem --wiki_key_file=key.pem

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

View File

@@ -1,6 +1,6 @@
#! /usr/bin/perl
# Copyright (C) 20112015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20112018 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
@@ -23,7 +23,8 @@ use utf8;
# load Oddmuse core
$RunCGI = 0;
do "wiki.pl";
$DataDir = '/home/alex/campaignwiki';
do "/home/alex/farm/wiki.pl";
# globals depending on the name of the script
my ($self, $name, $wiki);
@@ -35,6 +36,9 @@ if ($0 eq '/home/alex/campaignwiki.org/add-link.pl') {
$self = "https://campaignwiki.org/add-adventure";
$name = "OSR Links to Adventures";
$wiki = 'Adventures';
} elsif ($0 eq '/home/alex/campaignwiki.org/add-sf-link.pl') {
$name = "OSRSF House Rules Wiki: Uplinked Intelligence";
$wiki = 'UplinkedIntelligence';
} else {
ReportError('Cannot determine wiki!', '500 INTERNAL SERVER ERROR');
}
@@ -164,7 +168,7 @@ sub is_duplicate {
" already links to the URL you submitted:",
GetUrl($link->[0], $link->[1]));
return 1;
}
}
}
return 0;
}
@@ -288,11 +292,13 @@ sub print_end_of_page {
sub main {
$ConfigFile = "$DataDir/config"; # read the global config file
$ModuleDir = "$DataDir/modules"; # global modules
$DataDir = "$DataDir/$wiki"; # but link to the local pages
Init(); # read config file (no modules!)
Init(); # read config file
$ScriptName = $site; # undo setting in the config file
$FullUrl = $site; #
InitPageVariables(); # call again: $ScriptName was wrong
$HomePage = 'HomePage'; # $HomePage must not be translated
binmode(STDOUT,':utf8');
$q->charset('utf8');
if ($q->path_info eq '/source') {

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

View File

@@ -2,9 +2,9 @@
html{ text-align: center; }
body, rss {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
font-family: "DejaVu Serif", Palatino, serif;
font-style: normal;
font-size: 14pt;
font-size: 16pt;
padding: 1em 3em;
max-width: 72ex;
display: inline-block;
@@ -13,10 +13,31 @@ body, rss {
background-color: #fff;
}
@media only screen and (max-device-width: 480px) {
@media only screen and (max-device-width: 600px) {
body {
padding: 1ex;
}
textarea {
font-size: inherit;
}
}
/* code */
textarea, pre, code, tt {
font-family: "DejaVu Mono", "Andale Mono", Monaco, "Courier New", Courier, monospace;
}
pre, code, tt {
font-size: 12pt; /* 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+ */
}
@import url(file:///home/alex/alexschroeder.ch/css/alex-2017.css) print;
@@ -191,22 +212,6 @@ input[type="text"] {
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. */
@@ -223,6 +228,9 @@ div.header, div.footer, div.near, div.definition, p.comment, a.tag {
div.footer form.search {
display: none;
}
/* Recent Changes */
div.rc {
overflow: hidden;
}
@@ -233,12 +241,57 @@ div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
.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;
}
/* Diff */
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;

425
css/latex.css Normal file
View File

@@ -0,0 +1,425 @@
/*!
* LaTeX.css (https://latex.now.sh/)
*
* Source: https://github.com/vincentdoerig/latex-css
* Licensed under MIT (https://github.com/vincentdoerig/latex-css/blob/master/LICENSE)
*/
@font-face {
font-family: 'Latin Modern';
font-style: normal;
font-weight: normal;
font-display: swap;
src: url('/style/fonts/LM-regular.woff2') format('woff2'),
url('/style/fonts/LM-regular.woff') format('woff'),
url('/style/fonts/LM-regular.ttf') format('truetype');
}
@font-face {
font-family: 'Latin Modern';
font-style: italic;
font-weight: normal;
font-display: swap;
src: url('/style/fonts/LM-italic.woff2') format('woff2'),
url('/style/fonts/LM-italic.woff') format('woff'),
url('/style/fonts/LM-italic.ttf') format('truetype');
}
@font-face {
font-family: 'Latin Modern';
font-style: normal;
font-weight: bold;
font-display: swap;
src: url('/style/fonts/LM-bold.woff2') format('woff2'),
url('/style/fonts/LM-bold.woff') format('woff'),
url('/style/fonts/LM-bold.ttf') format('truetype');
}
@font-face {
font-family: 'Latin Modern';
font-style: italic;
font-weight: bold;
font-display: swap;
src: url('/style/fonts/LM-bold-italic.woff2') format('woff2'),
url('/style/fonts/LM-bold-italic.woff') format('woff'),
url('/style/fonts/LM-bold-italic.ttf') format('truetype');
}
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
ul[class],
ol[class],
li,
figure,
figcaption,
dl,
dd {
margin: 0;
}
/* Make default font-size 1rem and add smooth scrolling to anchors */
html {
font-size: 1.4rem;
scroll-behavior: smooth;
}
body {
font-family: 'Latin Modern', Georgia, Cambria, 'DejaVu Serif', 'Times New Roman', Times, serif;
line-height: 1.4;
max-width: 80ch;
min-height: 100vh;
overflow-x: hidden;
margin: 0 auto;
padding: 2rem 1.25rem;
counter-reset: theorem;
counter-reset: definition;
color: hsl(0, 5%, 10%);
background-color: hsl(210, 20%, 98%);
text-rendering: optimizeLegibility;
}
/* Justify and hyphenate all paragraphs */
p {
text-align: justify;
hyphens: auto;
-webkit-hyphens: auto;
-moz-hyphens: auto;
margin-top: 1rem;
}
/* A elements that don't have a class get default styles */
a:not([class]) {
text-decoration-skip-ink: auto;
}
/* Make links red */
a {
text-decoration: none;
color: #a00;
}
a:visited {
text-decoration: none;
color: #800;
}
a:focus {
outline-offset: 2px;
outline: 2px solid hsl(220, 90%, 52%);
}
/* Ueberschriften mit Links nur dezent einfärben */
h1 a, h1 a:visited,
h2 a, h2 a:visited,
h3 a, h3 a:visited,
h4 a, h4 a:visited,
h5 a, h5 a:visited,
h6 a, h6 a:visited {
color: #555;
}
/* goto bar */
div.menu form.search {
font-size:75%;
margin-top:2em;
margin-bottom:3em;
}
div.menu span.gotobar a.local,
div.menu span.gotobar a.local:visited {
text-decoration: none;
color: #1e133c87;
margin-right:1.1em;
font-weight: bold;
}
/* Make images easier to work with */
img {
max-width: 100%;
display: block;
}
/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
font: inherit;
}
/* Prevent textarea from overflowing */
textarea {
width: 100%;
}
/* Natural flow and rhythm in articles by default */
article > * + * {
margin-top: 1em;
}
/* Styles for inline code or code snippets */
code,
pre,
kbd {
font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
font-size: 85%;
}
pre {
padding: 1rem 1.4rem;
max-width: 100%;
overflow: auto;
border-radius: 4px;
background: hsl(210, 28%, 93%);
}
pre code {
font-size: 95%;
position: relative;
}
kbd {
background: hsl(210, 5%, 100%);
border: 1px solid hsl(210, 5%, 70%);
border-radius: 2px;
padding: 2px 4px;
font-size: 75%;
}
/* Make table 100% width, add borders between rows */
table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
max-width: 100%;
}
th,
td {
text-align: left;
padding: 0.5rem;
}
td {
border-bottom: 1px solid hsl(0, 0%, 85%);
}
thead th {
border-bottom: 2px solid hsl(0, 0%, 70%);
}
tfoot th {
border-top: 2px solid hsl(0, 0%, 70%);
}
/* Center align the title */
h1:first-child {
text-align: center;
}
/* Nested ordered list for ToC */
nav ol {
counter-reset: item;
padding-left: 2rem;
}
nav li {
display: block;
}
nav li:before {
content: counters(item, '.') ' ';
counter-increment: item;
padding-right: 0.85rem;
}
/* Center definitions (most useful for display equations) */
dl dd {
text-align: center;
}
/* Theorem */
.theorem {
counter-increment: theorem;
display: block;
margin: 12px 0;
font-style: italic;
}
.theorem::before {
content: 'Satz ' counter(theorem) '. ';
font-weight: bold;
font-style: normal;
}
/* Lemma */
.lemma {
counter-increment: theorem;
display: block;
margin: 12px 0;
font-style: italic;
}
.lemma::before {
content: 'Lemma ' counter(theorem) '. ';
font-weight: bold;
font-style: normal;
}
/* Proof */
.proof {
display: block;
margin: 12px 0;
font-style: normal;
position: relative;
}
.proof::before {
content: 'Beweis.' attr(title);
font-style: italic;
}
.proof:after {
content: '◾️';
position: absolute;
right: -12px;
bottom: -2px;
}
/* Definition */
.definition {
counter-increment: definition;
display: block;
margin: 12px 0;
font-style: normal;
}
.definition::before {
content: 'Definition ' counter(definition) '. ';
font-weight: bold;
font-style: normal;
}
/* Center align author name, use small caps and add vertical spacing */
.author {
margin: 0.85rem 0;
font-variant-caps: small-caps;
text-align: center;
}
/* Make footnote text smaller and left align it (looks bad with long URLs) */
.footnotes p {
text-align: left;
line-height: 1.5;
font-size: 85%;
margin-bottom: 0.4rem;
}
.footnotes {
border-top: 1px solid hsl(0, 0%, 39%);
}
/* Center title and paragraph */
.abstract,
.abstract p {
text-align: center;
}
.abstract {
margin: 2.25rem 0;
}
/* Format the LaTeX symbol correctly (a higher up, e lower) */
.latex span:nth-child(1) {
text-transform: uppercase;
font-size: 0.75em;
vertical-align: 0.28em;
margin-left: -0.48em;
margin-right: -0.15em;
line-height: 1ex;
}
.latex span:nth-child(2) {
text-transform: uppercase;
vertical-align: -0.5ex;
margin-left: -0.1667em;
margin-right: -0.125em;
line-height: 1ex;
}
/* Heading typography */
h1 {
font-size: 2.5rem;
line-height: 3.25rem;
margin-bottom: 1.625rem;
}
h2 {
font-size: 1.7rem;
line-height: 2rem;
margin-top: 3rem;
}
h3 {
font-size: 1.4rem;
margin-top: 2.5rem;
}
h4 {
font-size: 1.2rem;
margin-top: 2rem;
}
h5 {
font-size: 1rem;
margin-top: 1.8rem;
}
h6 {
font-size: 1rem;
font-style: italic;
font-weight: normal;
margin-top: 2.5rem;
}
h3,
h4,
h5,
h6 {
line-height: 1.625rem;
}
h1 + h2 {
margin-top: 1.625rem;
}
h2 + h3,
h3 + h4,
h4 + h5 {
margin-top: 0.8rem;
}
h5 + h6 {
margin-top: -0.8rem;
}
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 0.8rem;
}
div.diff div.old {
background-color: #FFFFAF;
}
div.diff div.new {
background-color: #CFFFCF;
}
div.content blockquote {
font-style: italic;
}

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;
@@ -219,3 +263,19 @@ code {
background: #eee;
white-space: pre-wrap;
}
/* for https://oddmuse.org/wiki/All_Modules */
.foo_list + .journal h1 {
font: inherit;
border: none;
display: list-item;
margin-top: 0;
margin-left: 1em;
}
.foo_list + .journal a {
font: inherit;
border: none;
text-decoration: none;
color: #a00;
}

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

@@ -1,4 +1,4 @@
# Copyright (C) 2013-2016 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2013-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
@@ -96,16 +96,20 @@ sub DoBanHosts {
if (IsItBanned($_, \@regexps)) {
print $q->p(Ts("%s is banned", $name));
} else {
my ($start, $end) = BanContributors::get_range($_);
$range = "[$start - $end]";
$name .= " " . $range;
print GetFormStart(undef, 'get', 'ban'),
GetHiddenValue('action', 'ban'),
GetHiddenValue('id', $id),
GetHiddenValue('range', $range),
GetHiddenValue('regexp', BanContributors::get_regexp_ip($start, $end)),
GetHiddenValue('recent_edit', 'on'),
$q->p($name, $q->submit(T('Ban!'))), $q->end_form();
my @pairs = BanContributors::get_range($_);
while (@pairs) {
my $start = shift(@pairs);
my $end = shift(@pairs);
$range = "[$start - $end]";
$name .= " " . $range;
print GetFormStart(undef, 'get', 'ban'),
GetHiddenValue('action', 'ban'),
GetHiddenValue('id', $id),
GetHiddenValue('range', $range),
GetHiddenValue('regexp', BanContributors::get_regexp_ip($start, $end)),
GetHiddenValue('recent_edit', 'on'),
$q->p($name, $q->submit(T('Ban!'))), $q->end_form();
}
}
}
}
@@ -167,47 +171,73 @@ sub NewBanContributorsWriteRcLog {
package BanContributors;
use Net::Whois::Parser qw/parse_whois/;
use Net::IP;
sub get_range {
my $ip = shift;
my $response = parse_whois(domain => $ip);
my ($start, $end);
my $re = '(?:[0-9]{1,3}\.){3}[0-9]{1,3}';
my ($start, $end) = $response->{inetnum} =~ /($re) *- *($re)/;
return $start, $end;
# Just try all the keys and see whether there is a range match.
for (keys %$response) {
my @result;
$_ = $response->{$_};
for (ref eq 'ARRAY' ? @$_ : $_) {
$ip = Net::IP->new($_);
push(@result, $ip->ip, $ip->last_ip) if $ip;
}
return @result if @result;
}
# Fallback
return $ip, $ip;
}
sub get_groups {
my ($from, $to) = @_;
my @groups;
if ($from < 10) {
my $to = $to >= 10 ? 9 : $to;
if ($from == $to) {
return [$from, $to];
}
# ones up to the nearest ten
if ($from < $to and ($from % 10 or $from < 10)) {
# from 5-7: as is
# from 5-17: 5 + 9 - 5 = 9 thus 5-9, set $from to 10
my $to2 = int($to/10) > int($from/10) ? $from + 9 - $from % 10 : $to;
push(@groups, [$from, $to2]);
$from = $to2 + 1;
}
# tens up to the nearest hundred
if ($from < $to and $from % 100) {
# 10-17: as is
# 10-82: 10 to 79, set $from to 80 (8*10-1)
# 10-182: 10 to 99, set $from to 100 (10+99=10=99)
# 110-182: 110 to 179, set $from to 180 (170)
# 110-222: 110 to 199, set $from to 200 (110+99-10 = 199)
my $to2 = int($to/100) > int($from/100) ? $from + 99 - $from % 100
: int($to/10) > int($from/10) ? int($to / 10) * 10 - 1
: $to;
push(@groups, [$from, $to2]);
$from = $to2 + 1;
}
# up to the next hundred
if (int($to/100) > int($from/100)) {
# from 100 to 223: set $from to 200 (2*100-1)
my $to2 = int($to/100) * 100 - 1;
push(@groups, [$from, $to2]);
$from = $to2 + 1;
}
# up to the next ten
if (int($to/10) > int($from/10)) {
# 10 to 17: skip
# 100 to 143: set $from to 140 (14*10-1)
my $to2 = int($to / 10) * 10 - 1;
push(@groups, [$from, $to2]);
$from = $to2 + 1;
}
# up to the next one
if ($from <= $to) {
push(@groups, [$from, $to]);
$from = $to + 1;
}
while ($from < $to) {
my $to = int($from/100) < int($to/100) ? $from + 99 - $from % 100 : $to;
if ($from % 10) {
push(@groups, [$from, $from + 9 - $from % 10]);
$from += 10 - $from % 10;
}
if (int($from/10) < int($to/10)) {
if ($to % 10 == 9) {
push(@groups, [$from, $to]);
$from = 1 + $to;
} else {
push(@groups, [$from, $to - 1 - $to % 10]);
$from = $to - $to % 10;
}
} else {
push(@groups, [$from - $from % 10, $to]);
last;
}
if ($to % 10 != 9) {
push(@groups, [$from, $to]);
$from = 1 + $to; # jump from 99 to 100
}
}
# warn join("; ", map { "@$_" } @groups);
return \@groups;
}
@@ -235,24 +265,42 @@ sub get_regexp_ip {
my $regexp = "^";
for my $i (0 .. 3) {
if ($start[$i] eq $end[$i]) {
# if the byte is the same, use it as is
$regexp .= $start[$i];
} elsif ($start[$i] eq '0' and $end[$i] eq '255') {
$regexp .= '\.' if $i < 3;
} elsif ($start[$i] == 0 and $end[$i] == 255) {
# the starting byte is 0 and the end byte is 255, then anything goes:
# we're done, e.g. 185.244.214.0 - 185.244.214.255 results in 185\.244\.214\.
last;
} elsif ($start[$i + 1] > 0) {
$regexp .= '(' . $start[$i] . '\.('
. get_regexp_range($start[$i + 1], '255') . ')|'
. get_regexp_range($start[$i] + 1, $end[$i + 1]) . ')';
$regexp .= '\.';
} elsif ($i == 3 and $start[$i] != $end[$i]) {
# example 45.87.2.128 - 45.87.2.255: the last bytes differ
$regexp .= '(' . get_regexp_range($start[$i], $end[$i]) . ')';
last;
} elsif ($start[$i + 1] == 0 and $end[$i + 1] == 255) {
# if we're here, we already know that the start byte and the end byte are
# not the same; if the next bytes are from 0 to 255, we know that
# everything else doesn't matter, e.g. 42.118.48.0 - 42.118.63.255
$regexp .= '(' . get_regexp_range($start[$i], $end[$i]) . ')';
$regexp .= '\.' if $i < 3;
last;
} elsif ($end[$i] - $start[$i] == 1 and $start[$i + 1] > 0 and $end[$i + 1] < 255) {
# if we're here, we already know that the start byte and the end byte are
# not the same; if the starting byte of the next (!) byte is bigger than
# zero, then we need groups: in the case 77.56.180.0 - 77.57.70.255 for
# example,
$regexp .= '(' . $start[$i] . '\.(' . get_regexp_range($start[$i + 1], 255) . ')|'
. $end[$i] . '\.(' . get_regexp_range(0, $end[$i + 1]) . ')';
$regexp .= '\.' if $i < 3;
last;
} else {
$regexp .= '(' . get_regexp_range($start[$i], $end[$i]) . ')$';
warn "Unhandled regexp: $from - $to ($i)";
$regexp .= 'XXX';
$regexp .= '\.' if $i < 3;
last;
}
$regexp .= '\.' if $i < 3;
}
return $regexp;
}
# this is required in case we concatenate other modules to this one
package OddMuse;

View File

@@ -0,0 +1,72 @@
# 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/>.
=encoding utf8
=head1 Mixed Scripts
This module disallows ordinary users from posting words that consist of multiple
scripts. Stuff like this: "It's diffіcult to find knowledgeable people on this
topic, but youu sound like you know wgat you're takіng аboսt!" Did you notice
the confusable characters? The sentence contains the following:
ARMENIAN SMALL LETTER SEH
CYRILLIC SMALL LETTER A
CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
SMALL ROMAN NUMERAL FIFTY
=cut
use strict;
use v5.10;
use Unicode::UCD qw(charprop);
AddModuleDescription('ban-mixed-scripts.pl', 'Ban Mixed Scripts Extension');
*OldBanMixedScriptsBannedContent = \&BannedContent;
*BannedContent = \&NewBanMixedScriptsBannedContent;
sub NewBanMixedScriptsBannedContent {
my $rule = OldBanMixedScriptsBannedContent(@_);
$rule ||= BanMixedScript(@_);
return $rule;
}
sub BanMixedScript {
my $str = shift;
my @words = $str =~ m/\w+/g;
my %seen;
my %prop;
for my $word (@words) {
next if $seen{$word};
$seen{$word} = 1;
my $script;
for my $char (split(//, $word)) {
my $s = $prop{$char};
if (not $s) {
$s = charprop(ord($char), "Script_Extensions");
if ($s eq 'Hiragana') {
$s = 'Han'; # this mixing is ok
}
$prop{$char} = $s;
}
next if $s eq "Common";
if (not $script) {
$script = $s;
} elsif ($script ne $s) {
return "Mixed scripts in $word ($script and $s, if not more)";
}
}
}
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2012 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2012-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
@@ -62,14 +62,13 @@ push(@MyInitVariables, sub {
sub RegexpNewBannedContent {
my $str = shift;
# remove URLs as they are controlled by $BannedContent
$str =~ s/$FullUrlPattern//g;
# check whether Banned Content complains
my $rule = RegexpOldBannedContent($str, @_);
if (not $rule) {
foreach (split(/\n/, GetPageContent($BannedRegexps))) {
next unless m/^\s*([^#]+?)\s*(#\s*(\d\d\d\d-\d\d-\d\d\s*)?(.*))?$/;
my ($regexp, $comment, $re) = ($1, $4, undef);
eval { $re = qr/$regexp/i; };
eval { $re = qr/($regexp)/i; };
if (defined($re) && $str =~ $re) {
my $group1 = $1;
my $explanation = ($group1

View File

@@ -32,7 +32,9 @@ sub bbCodeRule {
my $bbcode = $1;
my $tag = lc($2);
my $option = $3; # sanitize?
if ($tag eq 'b') {
if ($tag eq 'br') {
return $q->br(); }
elsif ($tag eq 'b') {
return AddHtmlEnvironment('b'); }
elsif ($tag eq 'i') {
return AddHtmlEnvironment('i'); }

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2004, 2005, 2006 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 20042023 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2006 Ingo Belka
#
# This program is free software; you can redistribute it and/or modify
@@ -112,9 +112,7 @@ sub DoCollect {
my $search = GetParam('search', '');
ReportError(T('The match parameter is missing.')) unless $match or $search;
print GetHeader('', Ts('Page Collection for %s', $match||$search), '');
my @pages = (grep(/$match/, $search
? SearchTitleAndBody($search)
: AllPagesList()));
my @pages = Matched($match, $search ? SearchTitleAndBody($search) : AllPagesList());
if (!$CollectingJournal) {
$CollectingJournal = 1;
# Now save information required for saving the cache of the current page.

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

@@ -246,7 +246,8 @@ sub CreoleRule {
$q->img({-src=> UnquoteHtml($1),
-alt=> UnquoteHtml($3),
-title=> UnquoteHtml($3),
-class=> 'url outside'})));
-class=> 'url outside',
-loading=>'lazy'})));
}
# image link: [[link|{{pic}}]] and [[link|{{pic|text}}]]
elsif (m/\G(\[\[$FreeLinkPattern$CreoleLinkPipePattern
@@ -257,7 +258,8 @@ sub CreoleRule {
$q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
-alt=> UnquoteHtml($text),
-title=> UnquoteHtml($text),
-class=> 'upload'}), 'image')), $text);
-class=> 'upload',
-loading=>'lazy'}), 'image')), $text);
}
# image link: [[link|{{url}}]] and [[link|{{url|text}}]]
elsif (m/\G(\[\[$FreeLinkPattern$CreoleLinkPipePattern
@@ -268,7 +270,8 @@ sub CreoleRule {
$q->img({-src=> UnquoteHtml($3),
-alt=> UnquoteHtml($text),
-title=> UnquoteHtml($text),
-class=> 'url outside'}), 'image')), $text);
-class=> 'url outside',
-loading=>'lazy'}), 'image')), $text);
}
# image link: [[url|{{pic}}]] and [[url|{{pic|text}}]]
elsif (m/\G(\[\[$FullUrlPattern$CreoleLinkPipePattern
@@ -279,7 +282,8 @@ sub CreoleRule {
$q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
-alt=> UnquoteHtml($text),
-title=> UnquoteHtml($text),
-class=> 'upload'}))), $text);
-class=> 'upload',
-loading=>'lazy'}))), $text);
}
# image link: [[url|{{url}}]] and [[url|{{url|text}}]]
elsif (m/\G\[\[$FullUrlPattern$CreoleLinkPipePattern
@@ -289,7 +293,8 @@ sub CreoleRule {
$q->img({-src=> UnquoteHtml($2),
-alt=> UnquoteHtml($4),
-title=> UnquoteHtml($4),
-class=> 'url outside'})));
-class=> 'url outside',
-loading=>'lazy'})));
}
# link: [[url]] and [[url|text]]
elsif (m/\G\[\[$FullUrlPattern$CreoleLinkTextPattern\]\]/cgs) {

View File

@@ -217,7 +217,7 @@ sub PrintPageContentCrossbar {
# If the crossbar div is placed immediately after the content div, place it
# immediately before the content div.
if (not ($html =~ s~(<div class="content browse">)$crossbar_pattern~$2$1~)) {
if (not ($html =~ s~(<div class="content browse" lang="[a-z]*">)$crossbar_pattern~$2$1~)) {
# Otherwise, if the crossbar div is placed immediately before the end of the
# content div, place it immediately after the end of the content div.
$html =~

View File

@@ -0,0 +1,37 @@
#! /usr/bin/perl
# Copyright (C) 2019 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('definition-lists.pl', 'Definition Lists Extension');
our ($q, $bol, @MyRules, @HtmlStack, $Fragment);
push(@MyRules, \&DefinitionListsRule);
# term
# : definition
sub DefinitionListsRule {
if ($bol and /\G(?:\s*\n)*(\S.*)\n[ \t]*:[ \t]*/cg) {
return OpenHtmlEnvironment('dl', 1) . "<dt>$1</dt>" . AddHtmlEnvironment('dd');
} elsif (InElement('dd') and /\G(?:\s*\n)+(\S.*)\n[ \t]*:[ \t]*/cg) {
return OpenHtmlEnvironment('dl', 1) . "<dt>$1</dt>" . AddHtmlEnvironment('dd');
} elsif (InElement('dd') and /\G(\s*\n)+[ \t]*:[ \t]*/cg) {
return OpenHtmlEnvironment('dl', 1) . AddHtmlEnvironment('dd');
}
return;
}

View File

@@ -43,7 +43,7 @@ sub DitaaRule {
my $data = MIME::Base64::encode_base64($image);
my $url = "data:image/png;base64,$data";
return CloseHtmlEnvironments()
. "<div$style>" . $q->img({-src=>$url, -alt=>$map}) . "</div>";
. "<div$style>" . $q->img({-src=>$url, -alt=>$map, -loading=>'lazy'}) . "</div>";
}
return undef;
}

View File

@@ -49,5 +49,5 @@ sub DuckDuckGoSearchInit {
sub DoDuckDuckGoSearch {
my $search = UrlEncode(GetParam('search', undef));
print $q->redirect({-uri=>"https://www.duckduckgo.com/?q=$search+site%3A$DuckDuckGoSearchDomain"});
print $q->redirect({-uri=>"https://duckduckgo.com/?q=$search+site%3A$DuckDuckGoSearchDomain"});
}

View File

@@ -48,7 +48,7 @@ sub EmojiRule {
} elsif (/\G&gt;:-?\(/cg) {
# 😠 1F620 ANGRY FACE
return '&#x1F620;';
} elsif (/\G:-?[Ppb]/cg) {
} elsif (/\G:-?[Ppb]\b/cg) {
# 😝 1F61D FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES
return '&#x1F61D;';
} elsif (/\G&lt;3/cg) {

View File

@@ -35,6 +35,8 @@ push(@MyInitVariables, \&GotobarInit);
sub GotobarInit {
$GotobarName = FreeToNormal($GotobarName); # spaces to underscores
$AdminPages{$GotobarName} = 1;
my $id = GetId();
my $encoded = UrlEncode($id);
if ($IndexHash{$GotobarName}) {
OpenPage($GotobarName);
return if PageMarkedForDeletion();
@@ -42,8 +44,11 @@ sub GotobarInit {
# links for unsuspecting users.
@UserGotoBarPages = ();
$UserGotoBar = '';
my $text = $Page{text};
$text =~ s/\$\$id\b/$encoded/g;
$text =~ s/\$id\b/$id/g;
my $count = 0;
while ($Page{text} =~ m/($LinkPattern|\[\[$FreeLinkPattern\]\]|\[\[$FreeLinkPattern\|([^\]]+)\]\]|\[$InterLinkPattern\s+([^\]]+?)\]|\[$FullUrlPattern[|[:space:]]([^\]]+?)\])/g) {
while ($text =~ m/($LinkPattern|\[\[$FreeLinkPattern\]\]|\[\[$FreeLinkPattern\|([^\]]+)\]\]|\[$InterLinkPattern\s+([^\]]+?)\]|\[$FullUrlPattern[|[:space:]]([^\]]+?)\])/g) {
my $page = $2||$3||$4||$6||$8;
my $text = $5||$7||$9;
$UserGotoBar .= ' ' if $UserGotoBar;
@@ -57,10 +62,10 @@ sub GotobarInit {
# is the list of recent changes.
$count++;
if ($count == 1) {
unless ($GotobarSetHome) {$HomePage = FreeToNormal($page)};
} elsif ($count == 2) {
unless ($GotobarSetRC) {$RCName = FreeToNormal($page);}
}
unless ($GotobarSetHome) {$HomePage = FreeToNormal($page)};
} elsif ($count == 2) {
unless ($GotobarSetRC) {$RCName = FreeToNormal($page);}
}
}
}
}

48
modules/grep-filtered.pl Normal file
View File

@@ -0,0 +1,48 @@
# Copyright (C) 2020 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2020 Daniel MacKay <daniel@bonmot.ca>
#
# 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('grep-filtered.pl', 'Grep Filtered');
our ($PageDir);
our ($UseGrep);
$UseGrep = 1;
*OldGrepFiltered = \&Filtered;
*Filtered = \&NewGrepFiltered;
sub NewGrepFiltered {
my ($string, @pages) = @_;
my @pages = OldGrepFiltered(@_);
my $regexp = SearchRegexp($string);
return @pages unless GetParam('grep', $UseGrep) and $regexp;
my @result = grep(/$regexp/i, @pages); # search parameter for page titles
my %found = map {$_ => 1} @result;
$regexp =~ s/\\n(\)*)$/\$$1/g; # sometimes \n can be replaced with $
$regexp =~ s/([?+{|()])/\\$1/g; # basic regular expressions from man grep
# if we know of any remaining grep incompatibilities we should
# return @pages here!
$regexp = quotemeta($regexp);
open(F, '-|:encoding(UTF-8)', "find $PageDir -type f -print0 | xargs -0 -n10 -P4 grep --ignore-case -l '$regexp'") ;
while (<F>) {
my ($pageName) = m/.*\/(.*)\.pg$/ ;
push(@result, $pageName) if not $found{$pageName};
} close(F);
return sort @result;
}

View File

@@ -29,7 +29,7 @@ push(@MyRules, \&ImageSupportRule);
sub ImageSupportRule {
my $result = undef;
if (m!\G\[\[image((/[a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\](\{([^}]+)\})?!cg) {
if (m!\G\[\[image((/[-a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\](\{([^}]+)\})?!cg) {
my $oldpos = pos;
my $class = 'image' . $1;
my $external = $3;
@@ -66,7 +66,7 @@ sub ImageSupportRule {
$src = $ImageUrlPath . '/' . ImageUrlEncode($name);
}
if ($found) {
$result = $q->img({-src=>$src, -alt=>$alt, -title=>$alt, -class=>'upload'});
$result = $q->img({-src=>$src, -alt=>$alt, -title=>$alt, -class=>'upload', -loading=>'lazy'});
$result = $q->a({-href=>$link, -class=>$linkclass}, $result);
if ($comments) {
for (split '\n', $comments) {

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2004, 2007 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 20042023 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
@@ -47,8 +47,5 @@ sub PrintableIndexPages {
push(@pages, AllPagesList()) if GetParam('pages', 1);
push(@pages, keys %PermanentAnchors) if GetParam('permanentanchors', 1);
push(@pages, keys %NearSource) if GetParam('near', 0);
my $match = GetParam('match', '');
@pages = grep /$match/i, @pages if $match;
@pages = sort @pages;
return @pages;
return sort Matched(GetParam('match'), @pages);
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20042014 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20042023 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
@@ -18,7 +18,8 @@ use v5.10;
AddModuleDescription('journal-rss.pl', 'Journal RSS Extension');
our ($OpenPageName, $CollectingJournal, %Page, %Action, @MyInitVariables, $DeletedPage, %NearLinksException);
our ($OpenPageName, $CollectingJournal, %Page, %Action, @MyInitVariables, $DeletedPage, %NearLinksException,
$RecentLink, $SiteName, $SiteDescription, $ScriptName, $RssRights);
$Action{journal} = \&DoJournalRss;
# Currently RSS works like RecentChanges, which is not what bloggers
@@ -30,7 +31,52 @@ sub DoJournalRss {
local $CollectingJournal = 1;
# Fake the result of GetRcLines()
local *GetRcLines = \&JournalRssGetRcLines;
print GetHttpHeader('application/xml') . GetRcRss();
local *RcSelfWebsite = \&JournalRssSelfWebsite;
local *RcSelfAction = \&JournalRssSelfAction;
local *RcPreviousAction = \&JournalRssPreviousAction;
local *RcLastAction = \&JournalRssLastAction;
SetParam('full', 1);
if (GetParam('raw', 0)) {
print GetHttpHeader('text/plain');
print RcTextItem('title', $SiteName),
RcTextItem('description', $SiteDescription), RcTextItem('link', $ScriptName),
RcTextItem('generator', 'Oddmuse'), RcTextItem('rights', $RssRights);
ProcessRcLines(sub {}, \&RcTextRevision);
} else {
print GetHttpHeader('application/xml') . GetRcRss();
}
}
sub JournalRssParameters {
my $more = '';
foreach (@_, qw(rsslimit match search reverse monthly)) {
my $val = GetParam($_, '');
$more .= ";$_=" . UrlEncode($val) if $val;
}
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));
}
sub JournalRssPreviousAction {
my $num = GetParam('rsslimit', 10);
my $offset = GetParam('offset', 0) + $num;
return "action=journal;offset=$offset" . JournalRssParameters();
}
sub JournalRssLastAction {
return "action=journal" . JournalRssParameters();
}
sub JournalRssGetRcLines {
@@ -39,7 +85,8 @@ sub JournalRssGetRcLines {
my $search = GetParam('search', '');
my $reverse = GetParam('reverse', 0);
my $monthly = GetParam('monthly', 0);
my @pages = sort JournalSort (grep(/$match/, $search ? SearchTitleAndBody($search) : AllPagesList()));
my $offset = GetParam('offset', 0);
my @pages = sort JournalSort (Matched($match, $search ? SearchTitleAndBody($search) : AllPagesList()));
if ($monthly and not $match) {
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime();
$match = '^' . sprintf("%04d-%02d", $year+1900, $mon+1) . '-\d\d';
@@ -48,28 +95,27 @@ sub JournalRssGetRcLines {
@pages = reverse @pages;
}
# FIXME: Missing 'future' and 'past' keywords.
# FIXME: Do we need 'offset'? I don't think so.
my @result = ();
my $n = 0;
foreach my $id (@pages) {
# Now save information required for saving the cache of the current page.
local %Page;
local $OpenPageName = '';
OpenPage($id);
# If this is a minor edit, ignore it. Load the last major revision
# instead, if you can.
if ($Page{minor}) {
# Perhaps the old kept revision is gone due to $KeepMajor=0 or
# admin.pl or because a page was created as a minor change and
# never edited. Reading kept revisions in this case results in
# an error.
eval {
%Page = GetKeptRevision($Page{lastmajor});
};
next if $@;
# If this is a minor edit, let's keep everything as it is, but show the date
# of the last major change, if possible. This is important for blogs that
# get added to a Planet. A minor change doesn't mean that the page needs to
# go to the front of the Planet.
if ($Page{minor} and $Page{lastmajor}) {
my %major = GetKeptRevision($Page{lastmajor});
$Page{ts} = $major{ts} if $major{ts};
}
next if $Page{text} =~ /^\s*$/; # only whitespace is also to be deleted
next if $DeletedPage && substr($Page{text}, 0, length($DeletedPage))
eq $DeletedPage; # no regexp
eq $DeletedPage; # no regexp
# OK, this is a candidate page
$n++;
next if $n <= $offset;
# Generate artifical rows in the list to pass to GetRcRss. We need
# to open every single page, because the meta-data ordinarily
# available in the rc.log file is not available to us. This is why
@@ -79,7 +125,7 @@ sub JournalRssGetRcLines {
push (@result, [$Page{ts}, $id, $Page{minor}, $Page{summary}, $Page{host},
$Page{username}, $Page{revision}, \@languages,
GetCluster($Page{text})]);
last if $num ne 'all' and $#result + 1 >= $num;
last if @result >= $num;
}
return @result;
}

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

@@ -1,4 +1,4 @@
# Copyright (C) 20092015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20092022 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2015 Aleks-Daniel Jakimenko <alex.jakimenko@gmail.com>
#
# This program is free software; you can redistribute it and/or modify it under
@@ -230,6 +230,10 @@ sub MailMenu {
ScriptLink('action=subscriptionlist',
T('All mail subscriptions'),
'subscriptionlist')) if UserIsAdmin();
push(@$menuref,
ScriptLink('action=subscribers',
T('All mail subscribers'),
'subscribers')) if UserIsAdmin();
}
=head1 Your subscriptions
@@ -283,9 +287,9 @@ sub MailSubscription {
=head1 Administrator Access
The subscriptionlist action will show you the subscription database,
if you're an administrator. It's a plain text file of the data, which
you can use for debugging and scripting purposes.
The C<subscriptionlist> action will show you the subscription database, if
you're an administrator. With the C<raw> parameter set it's a plain text file of
the data, which you can use for debugging and scripting purposes.
=cut
@@ -327,6 +331,41 @@ sub MailLink {
. join(';', map { "pages=$_" } @pages), $str);
}
=pod
The C<subscribers> action lists each unique email address for easier mass
unsubscribing of email addresses after a wave of wiki spam.
=cut
$Action{subscribers} = \&DoMailSubscribers;
sub DoMailSubscribers {
UserIsAdminOrError();
my $raw = GetParam('raw', 0);
if ($raw) {
print GetHttpHeader('text/plain');
} else {
print GetHeader('', T('Subscriptions')),
$q->start_div({-class=>'content subscribtionlist'}),
$q->p(T('Mail addresses are linked to unsubscription links.')),
'<ul>';
}
my %authors;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
for my $author (sort grep /\@/, map { UrlDecode($_) } keys %h) {
if ($raw) {
print "$author\n";
} else {
print $q->li(ScriptLink("action=unsubscribe;who=$author", $author));
}
}
print '</ul></div>' unless $raw;
PrintFooter() unless $raw;
untie %h;
}
=head1 Subscription
The subscribe action will subscribe you to pages. The mail parameter
@@ -402,13 +441,13 @@ sub MailSubscribe {
=head1 Unsubscription
The unsubscribe action will unsubscribe you from pages. The mail
parameter contains the mail address to use and defaults to the value
store in your cookie. Multiple pages parameters contain the pages to
unsubscribe.
The unsubscribe action will unsubscribe you from pages. The mail parameter
contains the mail address to use and defaults to the value store in your cookie.
Multiple pages parameters contain the pages to unsubscribe. Without naming
pages, you will be unsubscribed from all pages.
The who parameter overrides the mail parameter and is used for
administrator unsubscription from the subscriptionlist action.
The who parameter overrides the mail parameter and is used for administrator
unsubscription from the subscriptionlist action.
=cut
@@ -416,21 +455,20 @@ $Action{unsubscribe} = \&DoMailUnsubscribe;
sub DoMailUnsubscribe {
my $mail = GetParam('who', GetParam('mail', ''));
return DoMailSubscriptions(@_) unless $mail;
local $CGI::LIST_CONTEXT_WARN = 0;
my @pages = $q->param('pages');
return DoMailSubscriptions(@_) unless $mail;
my @real = ();
foreach my $id (@pages) {
push @real, $id if $IndexHash{$id};
}
MailUnsubscribe($mail, @real);
MailUnsubscribe($mail, @pages);
# MailUnsubscribe will set a parameter and must run before printing
# the header.
print GetHeader('', T('Subscriptions')),
$q->start_div({-class=>'content unsubscribe'});
print $q->p(Ts('Unsubscribed %s from the following pages:', $mail));
print $q->ul($q->li([map { GetPageLink($_) } @real]));
print $q->p(T('The remaining pages do not exist.')) if $#real < $#pages;
$q->start_div({-class=>'content unsubscribe'});
if (@pages) {
print $q->p(Ts('Unsubscribed %s from the following pages:', $mail));
print $q->ul($q->li([map { GetPageLink($_) } @pages]));
} else {
print $q->p(Ts('Unsubscribed %s from all pages.', $mail));
}
print $q->p(ScriptLink('action=subscriptions', T('Your mail subscriptions'),
'subscriptions') . '.');
print $q->end_div();
@@ -439,10 +477,11 @@ sub DoMailUnsubscribe {
sub MailUnsubscribe {
my ($mail, @pages) = @_;
return unless $mail and @pages;
return unless $mail;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
@pages = keys %subscriptions unless @pages;
foreach my $id (@pages) {
delete $subscriptions{$id};
# take care of reverse lookup
@@ -466,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

@@ -0,0 +1,108 @@
#! /usr/bin/perl
# 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/>.
use strict;
use v5.10;
use utf8;
AddModuleDescription('markdown-converter.pl', 'Markdown Convert');
our (%Action, @MyAdminCode, $q, $OpenPageName, %Page, @MyRules);
push(@MyAdminCode, \&MarkdownConvertMenu);
sub MarkdownConvertMenu {
my ($id, $menuref, $restref) = @_;
my $name = $id;
$name =~ s/_/ /g;
if ($id) {
push(@$menuref, ScriptLink('action=convert;id=' . $id, Ts('Help convert %s to Markdown', $name), 'convert'));
}
push(@$menuref, ScriptLink('action=conversion-candidates', Ts('List all non-Markdown pages'), 'convert'));
}
$Action{convert} = \&MarkdownConvert;
# some text that doesn't start and end with a space, or just one non-space
sub MarkdownConvertString {
my $c = shift;
return qr"([^\\$c \n][^\\$c\n]*[^\\$c \n]|[^\\$c \n])";
}
sub MarkdownConvert {
my $id = GetParam('id', '');
ValidIdOrDie($id);
print GetHeader('', Ts('Converting %s', $id), '');
$_ = GetPageContent($id);
s/^\{\{\{((?:.*\n)+?)\}\}\}$/```$1```/gm;
s/\{\{\{(.+?)\}\}\}/`$1`/gm;
my $s = MarkdownConvertString('*');
s/\*$s\*/**$1**/g;
# avoid URL schemas like http://example.org
$s = MarkdownConvertString('/');
s#(?<!:/)/$s/#*$1*#g;
s#(?<!:)//$s//#*$1*#g;
s/^# /1. /gm;
s/##(.*?)##/`$1`/g;
s/^(=+) *(.*?) ?=*$/'#' x length($1) . ' ' . $2/gme;
s!\[(https?://\S+) (.*?)\]![$2]($1)!g;
return DoEdit($id, "#MARKDOWN\n" . $_, 1); # preview
}
$Action{'conversion-candidates'} = \&MarkdownConversionCandidates;
sub MarkdownConversionCandidates {
# from Search
print GetHeader('', Ts('Candidates for Conversion to Markdown'));
print $q->start_div({-class=>'content'});
print $q->start_ol();
# from SearchTitleAndBody
my $regex = qr'^(?!#MARKDOWN)';
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
OpenPage($id); # this opens a page twice if it is not uploaded, but that's ok
if ($Page{text} =~ /$regex/) {
my $action = 'action=convert;id=' . UrlEncode($id);
my $name = NormalToFree($id);
print $q->li(GetPageLink($id, $name) . ' '
. ScriptLink($action, Ts('Help convert %s to Markdown', $name)));
}
}
}
print $q->end_ol();
print $q->end_div();
PrintFooter();
}
push(@MyRules, \&MarkdownConvertRule);
sub MarkdownConvertRule {
if (pos == 0 and /\G#MARKDOWN\n/gc) {
return '';
}
return;
}

View File

@@ -1,5 +1,5 @@
#! /usr/bin/perl
# Copyright (C) 20142017 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20142022 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
@@ -18,7 +18,7 @@ use v5.10;
AddModuleDescription('markdown-rule.pl', 'Markdown Rule Extension');
our ($q, $bol, %RuleOrder, @MyRules, $UrlProtocols, $FullUrlPattern, @HtmlStack);
our ($q, $bol, %RuleOrder, @MyRules, $UrlProtocols, $FullUrlPattern, @HtmlStack, $Fragment);
push(@MyRules, \&MarkdownRule);
# Since we want this package to be a simple add-on, we try and avoid
@@ -31,6 +31,7 @@ $RuleOrder{\&MarkdownRule} = 200;
# https://help.github.com/articles/github-flavored-markdown
sub MarkdownRule {
my $alignment;
# \escape
if (m/\G\\([-#>*`=])/cg) {
return $1;
@@ -50,9 +51,28 @@ sub MarkdownRule {
}
# > blockquote
# with continuation
elsif ($bol and m/\G&gt;/cg) {
return CloseHtmlEnvironments()
. AddHtmlEnvironment('blockquote');
elsif ($bol and m/\G((?:&gt;.*\n?)+)/cg) {
Clean(CloseHtmlEnvironments());
Dirty($1);
my $text = $1;
my ($oldpos, $old_) = ((pos), $_);
print '<blockquote>';
$text =~ s/^&gt; ?//gm;
ApplyRules($text, 1, 1, undef, 'p'); # local links, anchors, no revision, start with p
print '</blockquote>';
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
}
# """ = blockquote, too
elsif ($bol and m/\G("""[ \t]*\n(.*?)\n"""[ \t]*(?:\n|$))/cgs) {
Clean(CloseHtmlEnvironments());
Dirty($1);
my ($oldpos, $old_) = ((pos), $_);
print '<blockquote>';
ApplyRules($2, 1, 1, undef, 'p'); # local links, anchors, no revision, start with p
print '</blockquote>';
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
}
# ``` = code
elsif ($bol and m/\G```[ \t]*\n(.*?)\n```[ \t]*(\n|$)/cgs) {
@@ -123,34 +143,47 @@ sub MarkdownRule {
# beginning of a table
elsif ($bol and !InElement('table') and m/\G\|/cg) {
# warn pos . " beginning of a table";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return OpenHtmlEnvironment('table',1)
. AddHtmlEnvironment('tr')
. AddHtmlEnvironment('th');
. AddHtmlEnvironment('th', $alignment);
}
# end of a row and beginning of a new row
elsif (InElement('table') and m/\G\|?\n\|/cg) {
# warn pos . " end of a row and beginning of a new row";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('tr')
. AddHtmlEnvironment('tr')
. AddHtmlEnvironment('td');
. AddHtmlEnvironment('td', $alignment);
}
# otherwise the table ends
elsif (InElement('table') and m/\G\|?(\n|$)/cg) {
# warn pos . " otherwise the table ends";
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('table')
. AddHtmlEnvironment('p');
}
# continuation of the first row
elsif (InElement('th') and m/\G\|/cg) {
# warn pos . " continuation of the first row";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('th')
. AddHtmlEnvironment('th');
. AddHtmlEnvironment('th', $alignment);
}
# continuation of other rows
elsif (InElement('td') and m/\G\|/cg) {
# warn pos . " continuation of other rows";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('td')
. AddHtmlEnvironment('td');
. AddHtmlEnvironment('td', $alignment);
}
# whitespace indentation = code
elsif ($bol and m/\G(\s*\n)*( .+)\n?/cg) {
@@ -160,13 +193,21 @@ sub MarkdownRule {
}
return OpenHtmlEnvironment('pre',1) . $str; # always level 1
}
# [an example](http://example.com/ "Title")
elsif (m/\G\[(.+?)\]\($FullUrlPattern(\s+"(.+?)")?\)/cg) {
# link: [an example](http://example.com/ "Title")
elsif (m/\G\[((?:[^]\n]+\n?)+)\]\((\S+)(\s+"(.+?)")?\)/cg) {
my ($text, $url, $title) = ($1, $2, $4);
$url =~ /^($UrlProtocols)/;
my %params;
$params{-href} = $url;
$params{-class} = "url $1";
$params{-class} = "url";
$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);
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2012 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20042022 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
@@ -42,10 +42,18 @@ AddModuleDescription('namespaces.pl', 'Namespaces Extension');
use File::Glob ':glob';
our ($q, %Action, %Page, @IndexList, $Now, %InterSite, $SiteName, $ScriptName, $UsePathInfo, $DataDir, $HomePage, @MyInitVariables, @MyAdminCode, $FullUrl, $LinkPattern, $InterSitePattern, $FreeLinks, $FreeLinkPattern, $InterLinkPattern, $FreeInterLinkPattern, $UrlProtocols, $WikiLinks, $FS, $RcFile, $RcOldFile, $RcDefault, $PageDir, $KeepDir, $LockDir, $TempDir, $IndexFile, $VisitorFile, $NoEditFile, $WikiDescription, $LastUpdate, $StaticDir, $StaticUrl, $InterWikiMoniker, $RefererDir, $PermanentAnchorsFile);
our ($q, %Action, %Page, @IndexList, $Now, %InterSite, $SiteName, $ScriptName,
$UsePathInfo, $DataDir, $HomePage, @MyInitVariables, @MyAdminCode, $FullUrl,
$LinkPattern, $InterSitePattern, $FreeLinks, $FreeLinkPattern,
$InterLinkPattern, $FreeInterLinkPattern, $UrlProtocols, $WikiLinks, $FS,
$BannedContent, $BannedHosts, $RcFile, $RcOldFile, $RcDefault, $PageDir,
$KeepDir, $LockDir, $TempDir, $IndexFile, $VisitorFile, $NoEditFile,
$WikiDescription, $LastUpdate, $StaticDir, $StaticUrl, $InterWikiMoniker,
$RefererDir, $PermanentAnchorsFile, @IndexList, %IndexHash);
our ($NamespacesMain, $NamespacesSelf, $NamespaceCurrent,
$NamespaceRoot, $NamespaceSlashing, @NamespaceParameters,
%Namespaces);
$NamespaceRoot, $NamespaceSlashing, @NamespaceParameters,
%Namespaces, $NamespacesRootDataDir);
$NamespacesMain = 'Main'; # to get back to the main namespace
$NamespacesSelf = 'Self'; # for your own namespace
@@ -110,6 +118,7 @@ sub NamespacesInitVariables {
}
}
$NamespaceRoot = $ScriptName; # $ScriptName may be changed below
$NamespacesRootDataDir = $DataDir; # $DataDir may be chanegd below
$NamespaceCurrent = '';
my $ns = GetNamespace();
if ($ns
@@ -117,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";
@@ -162,6 +171,56 @@ sub NamespaceRequiredByParameter {
}
}
=head Spam fighting
We want to share C<BannedContent> and C<BannedHosts> between all the wiki
namespaces. Therefore, we need to handle a number of cases:
C<UserIsBanned> uses C<GetPageContent($BannedHosts)> and C<BannedContent> uses
C<GetPageContent($BannedContent)>, therefore C<GetPageContent> is going to get
modified.
C<DoBanHosts> in F<ban-contributors.pl> uses C<DoPost($BannedContent)> and
C<DoPost($BannedHosts)>, therefore C<DoPost> is going to get modified.
=cut
*OldNamespaceGetPageContent = \&GetPageContent;
*GetPageContent = \&NewNamespaceGetPageContent;
sub NewNamespaceGetPageContent {
my ($id) = @_;
if ($NamespaceCurrent and ($id eq $BannedContent or $id eq $BannedHosts)) {
local $PageDir = "$NamespacesRootDataDir/page";
# we cannot use ReadFileOrDie because our $IndexHash{$id} does not reflect the existence of the root file
my ($status, $data) = ReadFile(GetPageFile($id));
return ParseData($data)->{text} if $status;
return '';
}
return OldNamespaceGetPageContent(@_);
}
*OldNamespaceDoPost = \&DoPost;
*DoPost = \&NewNamespaceDoPost;
sub NewNamespaceDoPost {
my ($id) = @_;
if ($NamespaceCurrent and ($id eq $BannedContent or $id eq $BannedHosts)) {
local $DataDir = $NamespacesRootDataDir;
local $PageDir = "$DataDir/page";
local $KeepDir = "$DataDir/keep";
local $LockDir = "$TempDir/lock";
local $NoEditFile = "$DataDir/noedit";
local $RcFile = "$DataDir/rc.log";
local $RcOldFile = "$DataDir/oldrc.log";
local $IndexFile = "$DataDir/pageidx";
@IndexList = %IndexHash = ();
AllPagesList(); # reload from new pageidx
return OldNamespaceDoPost(@_);
}
return OldNamespaceDoPost(@_);
}
=head2 RecentChanges
RecentChanges in the main namespace will list changes to all the

View File

@@ -312,9 +312,7 @@ resolved to the same target (the local page), which is unexpected.
# IndexOptions must be set in MyInitVariables for translations to
# work.
push(@MyInitVariables, sub {
push(@IndexOptions, ['near', T('Include near pages'), 0,
\&ListNearPages]);
});
push(@IndexOptions, ['near', T('Include near pages'), 0, \&ListNearPages])});
sub ListNearPages {
my %pages = %NearSource;

198
modules/network-blocker.pl Normal file
View File

@@ -0,0 +1,198 @@
# -*- mode: perl -*-
# Copyright (C) 2023 Alex Schroeder <alex@gnu.org>
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero 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 Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
=encoding utf8
=head1 NAME
Oddmuse Network Blocker
=head1 DESCRIPTION
This module hooks into regular Oddmuse Surge Protection. It adds the following
features:
Repeated offenders are blocked for increasingly longer times.
For every offender, we record the CIDR their IP number belongs to. Everytime an
IP number is blocked, all the CIDRs of the other blocked IPs are checked: if
there are three or more blocked IP numbers sharing the same CIDRs, the CIDR
itself is blocked.
CIDR blocking works the same way: Repeated offenders are blocked for
increasingly longer times.
=head2 Behind a reverse proxy
Make sure your config file copies the IP number to the correct environment
variable:
$ENV{REMOTE_ADDR} = $ENV{HTTP_X_FORWARDED_FOR};
=head1 SEE ALSO
<Oddmuse Surge Protection|https://oddmuse.org/wiki/Surge_Protection>
=cut
use strict;
use v5.10;
use Net::IP qw(:PROC);
use Net::DNS qw(rr);
AddModuleDescription('network-blocker.pl', 'Network Blocker Extension');
our ($Now, $DataDir, $SurgeProtectionViews, $SurgeProtectionTime);
{
no warnings 'redefine';
*OldNetworkBlockerDelayRequired = \&DelayRequired;
*DelayRequired = \&NewNetworkBlockerDelayRequired;
}
# Block for at least this many seconds.
my $NetworkBlockerMinimumPeriod = 30;
# Every violation doubles the current period until this maximum is reached (four weeks).
my $NetworkBlockerMaximumPeriod = 60 * 60 * 24 * 7 * 4;
# All the blocked networks. Maps CIDR to an array [expiry timestamp, expiry
# period].
my %NetworkBlockerList;
# Candidates are remembered for this many seconds.
my $NetworkBlockerCachePeriod = 600;
# All the candidate networks for a block. Maps IP to an array [ts, cidr, ...].
# Candidates are removed after $NetworkBlockerCachePeriod.
my %NetworkBlockerCandidates;
sub NetworkBlockerRead {
my ($status, $data) = ReadFile("$DataDir/network-blocks");
return unless $status;
my @lines = split(/\n/, $data);
while ($_ = shift(@lines)) {
my @items = split(/,/);
$NetworkBlockerList{shift(@items)} = \@items;
}
# an empty line separates the two sections
while ($_ = shift(@lines)) {
my @items = split(/,/);
$NetworkBlockerCandidates{shift(@items)} = \@items;
}
return 1;
}
sub NetworkBlockerWrite {
RequestLockDir('network-blocks') or return '';
WriteStringToFile(
"$DataDir/network-blocks",
join("\n\n",
join("\n", map {
join(",", $_, @{$NetworkBlockerList{$_}})
} keys %NetworkBlockerList),
join("\n", map {
join(",", $_, @{$NetworkBlockerCandidates{$_}})
} keys %NetworkBlockerCandidates)));
ReleaseLockDir('network-blocks');
}
sub NewNetworkBlockerDelayRequired {
my $ip = shift;
# If $ip is a name and not an IP number, parsing fails. In this case, run the
# regular code.
my $ob = new Net::IP($ip);
return OldNetworkBlockerDelayRequired($ip) unless $ob;
# Read the file. If the file does not exist, no problem.
NetworkBlockerRead();
# See if the current IP number is one of the blocked CIDR ranges.
for my $cidr (keys %NetworkBlockerList) {
# Perhaps this CIDR block can be expired.
if ($NetworkBlockerList{$cidr}->[0] < $Now) {
delete $NetworkBlockerList{$cidr};
next;
}
# Forget the CIDR if it cannot be turned into a range.
my $range = new Net::IP($cidr);
if (not $range) {
warn "CIDR $cidr is blocked but has no range: " . Net::IP::Error();
delete $NetworkBlockerList{$cidr};
next;
}
# If the CIDR overlaps with the remote IP number, it's a block.
warn "Checking whether $ip is in $cidr\n";
my $overlap = $range->overlaps($ob);
# $IP_PARTIAL_OVERLAP (ranges overlap) $IP_NO_OVERLAP (no overlap)
# $IP_A_IN_B_OVERLAP (range2 contains range1) $IP_B_IN_A_OVERLAP (range1
# contains range2) $IP_IDENTICAL (ranges are identical) undef (problem)
if (defined $overlap and $overlap != $IP_NO_OVERLAP) {
# Double the block period unless it has reached $NetworkBlockerMaximumPeriod.
if ($NetworkBlockerList{$cidr}->[1] < $NetworkBlockerMaximumPeriod / 2) {
$NetworkBlockerList{$cidr}->[1] *= 2;
} else {
$NetworkBlockerList{$cidr}->[1] = $NetworkBlockerMaximumPeriod;
}
$NetworkBlockerList{$cidr}->[0] = $Now + $NetworkBlockerList{$cidr}->[1];
# And we're done!
NetworkBlockerWrite();
ReportError(Ts('Too many connections by %s', $cidr)
. ': ' . Tss('Please do not fetch more than %1 pages in %2 seconds.',
$SurgeProtectionViews, $SurgeProtectionTime),
'503 SERVICE UNAVAILABLE');
}
}
# If the CIDR isn't blocked, let's see if Surge Protection wants to block it.
my $result = OldNetworkBlockerDelayRequired($ip);
warn "$ip was blocked\n" if $result;
# If the IP is to be blocked, determine its CIDRs and put them on a list. Sadly,
# routeviews does not support IPv6 at the moment!
if ($result and not ip_is_ipv6($ip) and not $NetworkBlockerCandidates{$ip}) {
my $reverse = $ob->reverse_ip();
$reverse =~ s/in-addr\.arpa\.$/asn.routeviews.org/;
my @candidates;
for my $rr (rr($reverse, "TXT")) {
next unless $rr->type eq "TXT";
my @data = $rr->txtdata;
push(@candidates, join("/", @data[1..2]));
}
warn "$ip is in @candidates\n";
$NetworkBlockerCandidates{$ip} = [$Now, @candidates];
# Expire any of the other candidates
for my $other_ip (keys %NetworkBlockerCandidates) {
if ($NetworkBlockerCandidates{$other_ip}->[0] < $Now - $NetworkBlockerCachePeriod) {
delete $NetworkBlockerCandidates{$other_ip};
}
}
# Determine if any of the CIDRs is to be blocked.
my $save;
for my $cidr (@candidates) {
# Count how often the candidate CIDRs show up for other IP numbers.
my $count = 0;
for my $other_ip (keys %NetworkBlockerCandidates) {
my @data = $NetworkBlockerCandidates{$other_ip};
for my $other_cidr (@data[1 .. $#data]) {
$count++ if $cidr eq $other_cidr;
}
}
if ($count >= 3) {
$NetworkBlockerList{$cidr} = [$Now + $NetworkBlockerMinimumPeriod, $NetworkBlockerMinimumPeriod];
$save = 1;
}
}
NetworkBlockerWrite() if $save;
}
return $result;
}

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2005 Flavio Poletti <flavio@polettix.it>
# Copyright (C) 2005 Flavio Poletti <flavio@polettix.it>
# Copyright (C) 2014-2015 Alex Jakimenko <alex.jakimenko@gmail.com>
# Copyright (C) 2014-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
@@ -48,7 +50,7 @@ AddModuleDescription('olocalmap.pl');
push(@MyInitVariables, \&InitLocalMap);
sub InitLocalMap {
my $id = GetCurrentPageName();
my $id = GetId();
my $action = lc(GetParam('action', ''));
AllPagesList(); # Build %IndexHash

View File

@@ -178,6 +178,7 @@ sub NewPermanentAnchorsDeletePage {
my $status = OldPermanentAnchorsDeletePage(@_);
return $status if $status; # this would be the error message
DeletePermanentAnchors(@_); # the only parameter is $id
return ''; # no errors
}
*OldPermanentAnchorsSave = \&Save;
@@ -261,12 +262,15 @@ Some user interface changes are required as well.
=item *
Allow the page index to list permanent anchors or not by setting
C<@IndexOptions>.
C<@IndexOptions>. Note that we need to delay setting this option until we're
sure that translations have loaded correctly, which is why we're setting
C<@IndexOptions> as part of running C<@MyInitVariables>.
=cut
push(@IndexOptions, ['permanentanchors', T('Include permanent anchors'),
1, sub { keys %PermanentAnchors }]);
push(@MyInitVariables, sub {
push(@IndexOptions, ['permanentanchors', T('Include permanent anchors'),
1, sub { keys %PermanentAnchors }])});
=item *

View File

@@ -1,4 +1,5 @@
# Copyright (C) 2004 Brock Wilcox <awwaiid@thelackthereof.org>
# Copyright (C) 2019 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,152 +17,124 @@
use strict;
use v5.10;
use LWP::UserAgent; # This one will one day be eliminated! Hopefully!
# Need these to do pingback
use RPC::XML;
use LWP::UserAgent;
use RPC::XML::Parser;
use RPC::XML;
AddModuleDescription('pingback-server.pl', 'Pingback Server Extension');
our ($CommentsPrefix);
# Specification: http://www.hixie.ch/specs/pingback/pingback
# XML-RPC errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
*OldPingbackServerGetHtmlHeader = \&GetHtmlHeader;
*GetHtmlHeader = \&NewPingbackServerGetHtmlHeader;
our ($CommentsPrefix, $q, $HtmlHeaders, %Action, $QuestionaskerSecretKey,
@MyInitVariables, %IndexHash);
# Add the <link ...> to the header
sub NewPingbackServerGetHtmlHeader {
my ($title, $id) = @_;
my $header = OldPingbackServerGetHtmlHeader($title,$id);
my $pingbackLink =
'<link rel="pingback" '
. 'href="http://thelackthereof.org/wiki.pl?action=pingback;id='
. $id . '">';
$header =~ s/<head>/<head>$pingbackLink/;
return $header;
push(@MyInitVariables, \&PingbackServerAddLink);
sub PingbackServerAddLink {
SetParam('action', 'pingback') if $q->path_info =~ m|/pingback\b|;
my $id = GetId();
return unless $id;
return if $id =~ /^$CommentsPrefix/;
my $link = '<link rel="alternate" type="application/wiki" href="'
. ScriptUrl('pingback/' . UrlEncode($id)) . '" />';
$HtmlHeaders .= $link unless index($HtmlHeaders, /$link/) != -1;
}
*OldPingbackServerInitRequest = \&InitRequest;
*InitRequest = \&NewPingbackServerInitRequest;
sub NewPingbackServerInitRequest {
if($ENV{'QUERY_STRING'} =~ /action=pingback;id=(.*)/) {
my $id = $1;
DoPingbackServer($id);
exit 0;
} else {
return OldPingbackServerInitRequest(@_);
}
}
$Action{pingback} = \&DoPingbackServer;
sub DoPingbackServer {
my $id = FreeToNormal(shift);
if ($ENV{'REQUEST_METHOD'} ne 'POST') {
result('405 Method Not Allowed', -32300,
'Only XML-RPC POST requests recognised.', 'Allow: POST');
# some sanity checks for the request
if ($q->request_method() ne 'POST') {
ReportError(T('Only XML-RPC POST requests recognised'), '405 METHOD NOT ALLOWED');
}
if ($q->content_type() ne 'text/xml') {
ReportError(T('Only XML-RPC POST requests recognised'), '415 UNSUPPORTED MEDIA TYPE');
}
if ($ENV{'CONTENT_TYPE'} ne 'text/xml') {
result('415 Unsupported Media Type', -32300,
'Only XML-RPC POST requests recognised.');
# some sanity checks for the target page name
if (not $id) {
PingbackServerFault('400 NO ID', 33, "No page specified");
}
my $error = ValidId($id);
if ($error) {
PingbackServerFault('400 INVALID ID', 33, "Invalid page name: $id");
}
local $/ = undef;
my $input = <STDIN>;
# check the IP number for bans
my $rule = UserIsBanned();
if ($rule) {
PingbackServerFault('403 FORBIDDEN', 49, "Your IP number is blocked");
}
# parse it
# check that the target page exists
AllPagesList();
if (not $IndexHash{$id}) {
PingbackServerFault('404 NOT FOUND', 32, "Page does not exist: $id");
}
# parse the remote procedure call
my $data = $q->param('POSTDATA');
my $parser = RPC::XML::Parser->new();
my $request = $parser->parse($input);
my $request = $parser->parse($data);
if (not ref($request)) {
result('400 Bad Request', -32700, $request);
PingbackServerFault('400 NO DATA', -32700, "Could not parse XML-RPC");
}
# handle it
# sanity check the function and argument number
my $name = $request->name;
my $arguments = $request->args;
if ($name ne 'pingback.ping') {
result('501 Not Implemented', -32601, "Method $name not supported");
PingbackServerFault('501 NOT IMPLEMENTED', -32601, "Method $name not supported");
}
if (@$arguments != 2) {
result('400 Bad Request', -32602,
"Wrong number of arguments (arguments must be in the form 'from', 'to')");
PingbackServerFault('400 WRONG NUMBER OF ARGS', -32602, "Wrong number of arguments");
}
# extract the two arguments
my $source = $arguments->[0]->value;
my $target = $arguments->[1]->value;
# verify that the source isn't banned
$rule = BannedContent($source);
if ($rule) {
PingbackServerFault('403 FORBIDDEN', 49, "The URL is blocked");
}
# TODO: Since we are _inside_ the wiki seems like we shouldn't have to use LWP
# So comment out all the LWP stuff once the DoPost thingie works
# DoPost($id);
# verify that the pingback is legit
my $ua = LWP::UserAgent->new;
$ua->agent("OddmusePingbackServer/0.1 ");
# Create a request
my $req = HTTP::Request->new(POST => 'http://thelackthereof.org/wiki.pl');
$req->content_type('application/x-www-form-urlencoded');
$req->content("title=$CommentsPrefix$id"
. "&summary=new%20comment"
. "&aftertext=Pingback:%20$source"
. "&save=save"
. "&username=pingback");
my $res = $ua->request($req);
my $out = '';
# Check the outcome of the response
if ($res->is_success) {
$out = $res->content;
} else {
$out = $res->status_line . "\n";
my $response = $ua->get($source);
if (not $response->is_success) {
PingbackServerFault('400 NO SOURCE', 16, "Cannot retrieve $source");
}
my $self = ScriptUrl(UrlEncode($id));
if ($response->decoded_content !~ /$self/) {
PingbackServerFault('403 FORBIDDEN', "$source does not link to $self");
}
$id = $CommentsPrefix . $id;
if (GetPageContent($id) =~ /$source/) {
PingbackServerFault('400 ALREADY REGISTERED', 48, "$source has already been registered");
}
result('200 OK', 0, "Oddmuse PingbackServer! $id OK");
# post a comment without redirect at the end
SetParam('aftertext', 'Pingback: ' . $source);
SetParam('summary', 'Pingback');
SetParam('username', T('Anonymous'));
SetParam($QuestionaskerSecretKey, 1) if $QuestionaskerSecretKey;
local *ReBrowsePage = sub {};
DoPost($id);
# response
my $message = "Oddmuse PingbackServer! $id OK";
my $response = RPC::XML::response->new(RPC::XML::string->new($message));
print GetHttpHeader('text/xml', 'nocache', '200 OK'), $response->as_string, "\n\n";
}
sub result {
my($status, $error, $data, $extra) = @_;
my $response;
if ($error) {
$response = RPC::XML::response->new(
RPC::XML::fault->new($error, $data));
} else {
$response = RPC::XML::response->new(RPC::XML::string->new($data));
}
print "Status: $status\n";
if (defined($extra)) {
print "$extra\n";
}
print "Content-Type: text/xml\n\n";
print $response->as_string;
exit;
sub PingbackServerFault {
my($status, $error, $data) = @_;
my $fault = RPC::XML::response->new(RPC::XML::fault->new($error, $data));
print GetHttpHeader('text/xml', 'nocache', $status), $fault->as_string, "\n\n";
exit 2;
}
=pod
# This doesn't work... but might be a basis for an in-wiki update system
sub DoPost {
my $id = FreeToNormal(shift);
my $source = shift;
ValidIdOrDie($id);
# Lock before getting old page to prevent races
RequestLockOrError(); # fatal
OpenPage($id);
my $string = $Page{text};
my $comment = "Pingback: $source";
$comment =~ s/\r//g; # Remove "\r"-s (0x0d) from the string
$comment =~ s/\s+$//g; # Remove whitespace at the end
$string .= "----\n" if $string and $string ne "\n";
$string .= $comment . "\n\n-- Pingback"
. ' ' . TimeToText(time) . "\n\n";
my $summary = "new pingback"
$Page{summary} = $summary;
$Page{username} = $user;
$Page{text} = $string;
SavePage();
ReleaseLock();
}
=cut

View File

@@ -42,7 +42,7 @@ sub PortraitSupportRule {
$PortraitSupportColorDiv = 0;
return $html;
} elsif ($bol && m/\Gportrait:$UrlPattern/cg) {
return $q->img({-src=>$1, -alt=>T("Portrait"), -class=>'portrait'});
return $q->img({-src=>$1, -alt=>T("Portrait"), -class=>'portrait', -loading=>'lazy'});
} elsif ($bol && m/\G(:*)\[new(.*)\]/cg) {
my $portrait = '';
my $depth = length($1);

View File

@@ -171,7 +171,7 @@ sub ReCaptchaGetQuestion {
# if (defined $ReCaptchaTabIndex) { $recaptcha_options{tabindex} = $ReCaptchaTabIndex; }
eval "use Captcha::reCAPTCHA";
my $captcha_html = Captcha::reCAPTCHA->new()->get_html(
my $captcha_html = Captcha::reCAPTCHA->new()->get_html_v2(
$ReCaptchaPublicKey, undef, $ENV{'HTTPS'} eq 'on', undef);
my $submit_html = $need_button ? $q->submit(-value=> T('Go!')) : '';
my $options_html = '
@@ -239,11 +239,12 @@ sub NewReCaptchaDoPost {
sub ReCaptchaCheckAnswer {
eval "use Captcha::reCAPTCHA";
my $result = Captcha::reCAPTCHA->new()->check_answer(
my $answer = GetParam('g-recaptcha-response');
return 0 unless $answer;
my $result = Captcha::reCAPTCHA->new()->check_answer_v2(
$ReCaptchaPrivateKey,
$q->remote_addr(),
GetParam('recaptcha_challenge_field'),
GetParam('recaptcha_response_field')
$answer,
$q->remote_addr()
);
return $result->{is_valid};
}

71
modules/rename-pages.pl Normal file
View File

@@ -0,0 +1,71 @@
# Copyright (C) 20192023 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;
our ($q, %Page, %Action, %IndexHash, @MyAdminCode);
AddModuleDescription('rename-pages.pl', 'Rename Pages Extension');
$Action{'rename-page'} = \&RenamePage;
sub RenamePage {
my $id = shift;
my $to = GetParam('to', '');
# check target
ValidIdOrDie($to);
OpenPage($to);
ReportError(T('Target page already exists.'), '400 BAD REQUEST')
if $IndexHash{$to} and not PageMarkedForDeletion();
# check the source
ValidIdOrDie($id);
OpenPage($id);
ReportError(T('Source page does not exist.'), '400 BAD REQUEST')
if not $IndexHash{$id} or PageMarkedForDeletion();
{
# prevent posting from browsing the target right away
local *ReBrowsePage = sub {};
# renaming is a minor change
SetParam('recent_edit', 'on');
# copy text
SetParam('text', $Page{text});
SetParam('summary', Ts('Copied from %s', FreeToNormal($id)));
DoPost($to);
# create redirect
SetParam('text', "#REDIRECT [[$to]]");
SetParam('summary', Ts('Moved to %s', FreeToNormal($to)));
DoPost($id);
}
# and now that we're done, go to the target
ReBrowsePage($to);
}
push(@MyAdminCode, \&RenamePageMenu);
sub RenamePageMenu {
my ($id, $menuref, $restref) = @_;
my $name = FreeToNormal($id);
if ($id) {
push(@$menuref, GetFormStart()
. $q->label({-for=>'to'}, Ts('Rename %s to:', $name) . ' ')
. GetHiddenValue('action', 'rename-page')
. GetHiddenValue('id', $id)
. $q->textfield(-name=>'to', -size=>20)
. ' '
. $q->submit('Do it')
. $q->end_form());
}
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2006, 2007, 2008 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 20062023 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
@@ -36,18 +36,12 @@ sub SearchListRule {
$term = GetId();
}
local ($OpenPageName, %Page);
my %hash = ();
my @found;
if ($variation eq 'list') {
foreach my $id (SearchTitleAndBody($term)) {
$hash{$id} = 1 unless $id eq $original; # skip the page with the query
}
@found = grep { $_ ne $original } SearchTitleAndBody($term);
} elsif ($variation eq 'titlelist') {
@found = grep { $_ ne $original } Matched($term, AllPagesList());
}
if ($variation eq 'titlelist') {
foreach my $id (grep(/$term/, AllPagesList())) {
$hash{$id} = 1 unless $id eq $original; # skip the page with the query
}
}
my @found = keys %hash;
if (defined &PageSort) {
@found = sort PageSort @found;
} else {
@@ -63,32 +57,24 @@ sub SearchListRule {
return;
}
# Add a new action list
$Action{list} = \&DoList;
sub DoList {
my $id = shift;
my $match = GetParam('match', '');
my $search = GetParam('search', '');
my $id = shift;
my $match = GetParam('match', '');
my $search = GetParam('search', '');
ReportError(T('The search parameter is missing.')) unless $match or $search;
print GetHeader('', Ts('Page list for %s', $match||$search), '');
local (%Page, $OpenPageName);
my %hash = ();
foreach my $id (grep(/$match/, $search
? SearchTitleAndBody($search)
: AllPagesList())) {
$hash{$id} = 1;
}
my @found = keys %hash;
if (defined &PageSort) {
@found = sort PageSort @found;
} else {
@found = sort(@found);
}
@found = map { $q->li(GetPageLink($_)) } @found;
print $q->start_div({-class=>'search list'}),
$q->ul(@found), $q->end_div;
my @found = Matched($match, $search ? SearchTitleAndBody($search) : AllPagesList());
if (defined &PageSort) {
@found = sort PageSort @found;
} else {
@found = sort(@found);
}
@found = map { $q->li(GetPageLink($_)) } @found;
print $q->start_div({-class=>'search list'}), $q->ul(@found), $q->end_div;
PrintFooter();
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20072014 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20072023 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
@@ -36,8 +36,7 @@ sub SisterPages {
push(@pages, AllPagesList()) if GetParam('pages', 1);
push(@pages, keys %PermanentAnchors) if GetParam('permanentanchors', 1);
push(@pages, keys %NearSource) if GetParam('near', 0);
my $match = GetParam('match', '');
@pages = grep /$match/i, @pages if $match;
@pages = Matched(GetParam('match', ''), @pages);
@pages = sort @pages;
return @pages;
}

View File

@@ -100,7 +100,7 @@ sub StaticGetDownloadLink {
return '[' . ($image ? 'image' : 'link') . ':' . $name . ']' unless $IndexHash{$id};
if ($image) {
return StaticFileName($id) if $image == 2;
my $result = $q->img({-src=>StaticFileName($id), -alt=>$alt, -class=>'upload'});
my $result = $q->img({-src=>StaticFileName($id), -alt=>$alt, -class=>'upload', -loading=>'lazy'});
$result = ScriptLink($id, $result, 'image');
return $result;
} else {
@@ -198,7 +198,7 @@ EOT
my $logo = $LogoUrl;
$logo =~ s|.*/||; # just the filename
my $alt = T('[Home]');
$header .= $q->img({-src=>$logo, -alt=>$alt, -class=>'logo'}) if $logo;
$header .= $q->img({-src=>$logo, -alt=>$alt, -class=>'logo', -loading=>'lazy'}) if $logo;
}
# top toolbar
local $UserGotoBar = ''; # only allow @UserGotoBarPages
@@ -317,7 +317,7 @@ sub GetDownloadLink {
$action = $ScriptName . '?' . $action;
}
return $action if $image == 2;
my $result = $q->img({-src=>$action, -alt=>$alt, -class=>'upload'});
my $result = $q->img({-src=>$action, -alt=>$alt, -class=>'upload', -loading=>'lazy'});
$result = ScriptLink(UrlEncode($id), $result, 'image') unless $id eq $OpenPageName;
return $result;
} else {

View File

@@ -95,7 +95,7 @@ sub StaticGetDownloadLink {
# if the page does not exist
return '[' . ($image ? 'image' : 'link') . ':' . $name . ']' unless $IndexHash{$id};
if ($image) {
my $result = $q->img({-src=>StaticFileName($id), -alt=>$alt, -class=>'upload'});
my $result = $q->img({-src=>StaticFileName($id), -alt=>$alt, -class=>'upload', -loading=>'lazy'});
$result = ScriptLink($id, $result, 'image');
return $result;
} else {
@@ -271,7 +271,7 @@ sub GetDownloadLink {
} else {
$action = $ScriptName . '?' . $action;
}
my $result = $q->img({-src=>$action, -alt=>$alt, -class=>'upload'});
my $result = $q->img({-src=>$action, -alt=>$alt, -class=>'upload', -loading=>'lazy'});
$result = ScriptLink(UrlEncode($id), $result, 'image') unless $id eq $OpenPageName;
return $result;
} else {

View File

@@ -62,16 +62,26 @@ Example:
$TagFeedIcon = 'http://www.example.org/pics/rss.png';
=head2 $TagCloudSize
The number of most used tags when looking at the tag cloud. The
default is 50.
Example:
$TagCloudSize = 20;
=cut
our ($q, $Now, %Action, %Page, $FreeLinkPattern, @MyInitVariables, @MyRules, @MyAdminCode, $DataDir, $ScriptName);
our ($TagUrl, $TagFeed, $TagFeedIcon, $TagFile);
our ($TagUrl, $TagFeed, $TagFeedIcon, $TagFile, $TagCloudSize);
push(@MyInitVariables, \&TagsInit);
sub TagsInit {
$TagUrl = ScriptUrl('action=rc;rcfilteronly=tag:%s') unless $TagUrl;
$TagFeed = ScriptUrl('action=rss;rcfilteronly=tag:%s') unless $TagFeed;
$TagCloudSize = 50 unless $TagCloudSize;
$TagFile = "$DataDir/tag.db";
}
@@ -113,7 +123,8 @@ sub TagsRule {
-title=>T('Feed for this tag'),
-rel=>'feed'
}, $q->img({-src=>$TagFeedIcon,
-alt=>T('RSS')}));
-alt=>T('RSS'),
-loading=>'lazy'}));
}
return $html;
}
@@ -239,6 +250,11 @@ sub TagFind {
return @result;
}
sub TagsTerms {
my $string = shift;
return grep(/./, $string =~ /\"([^\"]+)\"|(\S+)/g);
}
*OldTagFiltered = \&Filtered;
*Filtered = \&NewTagFiltered;
@@ -246,7 +262,7 @@ sub NewTagFiltered { # called within a lock!
my ($string, @pages) = @_;
my %page = map { $_ => 1 } @pages;
# looking at all the "tag:SOME TERMS" and and tag:TERM
my @tagterms = map { FreeToNormal($_) } grep(/^-?tag:/, shift =~ /\"([^\"]+)\"|(\S+)/g);
my @tagterms = map { FreeToNormal($_) } grep(/^-?tag:/, TagsTerms($string));
my @positives = map {substr($_, 4)} grep(/^tag:/, @tagterms);
my @negatives = map {substr($_, 5)} grep(/^-tag:/, @tagterms);
if (@positives) {
@@ -260,8 +276,9 @@ sub NewTagFiltered { # called within a lock!
foreach my $id (TagFind(@negatives)) {
delete $page{$id};
}
# filter out the tags from the search string
$string = join(' ', grep(!/^-?tag:/, $string =~ /\"([^\"]+)\"|(\S+)/g));
# filter out the tags from the search string, and add quotes which might have
# been stripped
$string = join(' ', map { qq{"$_"} } grep(!/^-?tag:/, TagsTerms($string)));
# run the old code for any remaining search terms
return OldTagFiltered($string, sort keys %page);
}
@@ -279,11 +296,12 @@ We're need to remove all tag terms (again) in order to not confuse it.
*SearchString = \&NewTagSearchString;
sub NewTagSearchString {
# filter out the negative tags from the search string
my $string = join(' ', map { NormalToFree($_) }
grep(!/^-tag:/, shift =~ /\"([^\"]+)\"|(\S+)/g));
my ($string, @rest) = @_;
# filter out the negative tags from the search string, and add quotes which
# might have been stripped
$string = join(' ', map { NormalToFree($_) } map { qq{"$_"} } grep(!/^-tag:/, TagsTerms($string)));
return 1 unless $string;
return OldTagSearchString($string, @_);
return OldTagSearchString($string, @rest);
}
=pod
@@ -298,27 +316,16 @@ $Action{tagcloud} = \&TagCloud;
sub TagCloud {
print GetHeader('', T('Tag Cloud'), ''),
$q->start_div({-class=>'content cloud'}) . '<p>';
$q->start_div({-class=>'content cloud'});
require HTML::TagCloud;
my $cloud = HTML::TagCloud->new;
# open the DB file
my %h = TagReadHash();
my $max = 0;
my $min = 0;
my %count = ();
foreach my $tag (grep !/^_/, keys %h) {
$count{$tag} = @{$h{$tag}};
$max = $count{$tag} if $count{$tag} > $max;
$min = $count{$tag} if not $min or $count{$tag} < $min;
$cloud->add(NormalToFree($tag), "$ScriptName?search=tag:" . UrlEncode($tag), scalar @{$h{$tag}});
}
foreach my $tag (sort keys %count) {
my $n = $count{$tag};
print $q->a({-href => "$ScriptName?search=tag:" . UrlEncode($tag),
-title => $n,
-style => 'font-size: '
. int(80+120*($max == $min ? 1 : ($n-$min)/($max-$min)))
. '%;',
}, NormalToFree($tag)), ' ... ';
}
print '</p></div>';
print $cloud->html_and_css($TagCloudSize);
print '</div>';
PrintFooter();
}
@@ -418,13 +425,12 @@ sub TagsMenu {
my ($id, $menuref, $restref) = @_;
push(@$menuref,
ScriptLink('action=reindex', T('Rebuild tag index'), 'reindex')
. ', ' . ScriptLink('action=taglist', T('list tags'), 'taglist')
. ', ' . ScriptLink('action=tagcloud', T('tag cloud'), 'tagcloud'));
}
=head1 COPYRIGHT AND LICENSE
Copyright (C) 20052015 Alex Schroeder <alex@gnu.org>
Copyright (C) 20052019 Alex Schroeder <alex@gnu.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -229,8 +229,8 @@ Important pages:
Páginas importantes:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Para marcar uma página para exclusão, coloque <strong>%s</strong> na primeira linha
from %s
de %s
Anonymous
Anônimo
redirected from %s
redirecionado de %s
%s:
@@ -469,8 +469,6 @@ The changes conflict. Please check the page again.
As alterações são conflitantes. Por favor, verifique essa página novamente.
Please check whether you overwrote those changes.
Por favor, verifique se você sobrescreveu aquelas alterações.
Anonymous
Anônimo
Cannot delete the index file %s.
Não foi possível excluir o arquivo de índice %s.
Please check the directory permissions.
@@ -505,6 +503,8 @@ Note: This error is normal if no changes have been made.
Observação: Esse erro é normal se nenhuma alteração foi feita.
Moving %s log entries.
Movendo %s entradas de log.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Marcar ou Remover o bloqueio global de edição
Edit lock created.
@@ -1236,6 +1236,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -229,8 +229,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
от %s
Anonymous
Анонимен
redirected from %s
Препратка от %s
%s:
@@ -469,8 +469,6 @@ The changes conflict. Please check the page again.
Промените се застъпват. Моля провери страницата още веднъж.
Please check whether you overwrote those changes.
Моля провери, дали няма конфликт между промените.
Anonymous
Анонимен
Cannot delete the index file %s.
Please check the directory permissions.
@@ -505,6 +503,8 @@ Note: This error is normal if no changes have been made.
Забележка: Тази грешка е нормална, ако не са направени промени.
Moving %s log entries.
Преместване на %s лог-записа.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Налагане или отмяна на глобална защита от редакции.
Edit lock created.
@@ -1236,6 +1236,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -228,8 +228,8 @@ Important pages:
Pàgines importants:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Per marcar una pàgina per esborrar, escriure <strong>%s</strong> en la primera línia.
from %s
des de %s
Anonymous
Anònim
redirected from %s
redirigit des de %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Els canvis entren en conflicte. Si us plau, comprova la pàgina un altre cop.
Please check whether you overwrote those changes.
Si us plau, comprova si sobreescrius aquests canvis.
Anonymous
Anònim
Cannot delete the index file %s.
No es pot esborrar el arxiu índex de %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
Nota: Aquest error és normal si no s'han fet canvis.
Moving %s log entries.
Movent %s entrades del registre.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
(Des)activar bloqueig global de edició
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -229,8 +229,8 @@ Important pages:
重要頁面:
To mark a page for deletion, put <strong>%s</strong> on the first line.
在該頁首行加入 <strong>%s</strong> 可將頁面標記為刪除
from %s
%s
Anonymous
匿名者
redirected from %s
%s 轉址
%s:
@@ -469,8 +469,6 @@ The changes conflict. Please check the page again.
你的修改和他人發生衝突請再次確認
Please check whether you overwrote those changes.
請確認一下是否你要覆蓋這些修改
Anonymous
匿名者
Cannot delete the index file %s.
無法刪除索引檔 %s
Please check the directory permissions.
@@ -505,6 +503,8 @@ Note: This error is normal if no changes have been made.
如果還沒有做過任何修改的話則不用理會這個錯誤訊息
Moving %s log entries.
移除了 %s 個記錄項目
Removing IP numbers from %s log entries.
Set or Remove global edit lock
設定或移除整個網站的編輯鎖定
Edit lock created.
@@ -1236,6 +1236,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -245,8 +245,8 @@ Important pages:
重要页面
To mark a page for deletion, put <strong>%s</strong> on the first line.
在首行加入 <strong>%s</strong>以将页面标记为删除
from %s
%s
Anonymous
匿名者
redirected from %s
%s重定向
%s:
@@ -485,8 +485,6 @@ The changes conflict. Please check the page again.
您的修改和他人发生冲突请再次确认
Please check whether you overwrote those changes.
请您确认一下是否要覆盖这些修改
Anonymous
匿名者
Cannot delete the index file %s.
无法删除索引档%s
Please check the directory permissions.
@@ -521,6 +519,8 @@ Note: This error is normal if no changes have been made.
如果还没有做过任何修改请不用理会这个错误讯息
Moving %s log entries.
移除了%s个日志项目
Removing IP numbers from %s log entries.
Set or Remove global edit lock
设定或移除整个网站的编辑锁定
Edit lock created.
@@ -1252,6 +1252,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -235,8 +235,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
vanaf %s
Anonymous
Anoniem
redirected from %s
doorverwezen vanaf %s
%s:
@@ -475,8 +475,6 @@ The changes conflict. Please check the page again.
De wijzigingen zijn strijdig met elkaar. Controleer de pagina a.u.b. opnieuw.
Please check whether you overwrote those changes.
Controleer a.u.b. of u die wijzigingen overschreven heeft.
Anonymous
Anoniem
Cannot delete the index file %s.
Please check the directory permissions.
@@ -511,6 +509,8 @@ Note: This error is normal if no changes have been made.
NB: Deze foutmelding is gebruikelijk als er geen wijzigingen zijn gemaakt.
Moving %s log entries.
Bezig met verplaatsen van %s items.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Ver-/ontgrendelen van de gehele site
Edit lock created.
@@ -1242,6 +1242,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Tähtsad leheküljed:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
Anonymous
Anonüümne
redirected from %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Anonüümne
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
%s:stä
Anonymous
Nimetön
redirected from %s
uudelleenohjattu sivulta %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Muutokset ovat ristiriidassa. Tarkista sivu uudelleen.
Please check whether you overwrote those changes.
Tarkista, ylikirjoititko joitakin noista muutoksista.
Anonymous
Nimetön
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Huom: Tämä virhe on normaali, jos muutoksia ei ole tehty.
Moving %s log entries.
Siirretään %s kirjausta.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Aseta tai poista sivuston muokkauslukitus
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -49,7 +49,7 @@ Le nom dutilisateur ne doit pas dépasser 50 caractères : non sauvegardé
This page contains an uploaded file:
Cette page contient un fichier téléversé :
No summary was provided for this file.
Aucun résumé fourni pour ce fichier.
Recursive include of %s!
Inclusion par récursivité de %s !
Clear Cache
@@ -95,13 +95,13 @@ Page non valide %s (ne doit pas se terminer par .lck)
Invalid Page %s
Page non valide %s
There are no comments, yet. Be the first to leave a comment!
Pas encore de commentaires. Soyez le premier à laisser un commentaire !
Welcome!
Bienvenue !
This page does not exist, but you can %s.
Cette page nexiste pas, mais vous pouvez %s.
create it now
la créer maintenant
Too many redirections
Trop de redirections
No redirection for old revisions
@@ -113,7 +113,7 @@ SVP allez à %s.
Updates since %s
Mises à jour depuis %s
up to %s
jusquà
jusquà %s
Updates in the last %s days
Mises à jour durant les derniers %s jours
Updates in the last day
@@ -133,7 +133,7 @@ Lister seulement les modifications majeures
Include minor changes
Inclure les modifications mineures
days
jours
List later changes
Lister les modifications plus récentes
RSS
@@ -232,8 +232,8 @@ Important pages:
Pages importantes :
To mark a page for deletion, put <strong>%s</strong> on the first line.
Pour marquer une page comme étant à supprimer, ajoutez <strong>%s</strong> à la première ligne
from %s
depuis %s
Anonymous
Anonyme
redirected from %s
redirigé(e) à partir de %s
%s:
@@ -263,9 +263,9 @@ par %s
(diff)
(diff)
a
a
c
c
Edit revision %s of this page
Modifier la version %s de cette page
e
@@ -409,13 +409,13 @@ Vous êtes actuellement éditeur de ce site.
You are a normal user on this site.
Vous êtes un utilisateur normal de ce site.
You do not have a password set.
Vous navez pas défini de mot de passe.
Your password does not match any of the administrator or editor passwords.
Vote mot de passe ne correspond ni au mot de passe administrateur ni au mot de passe éditeur.
Password:
Mot de passe :
Return to %s
Retour à %s
This operation is restricted to site editors only...
Cette opération est réservée aux éditeurs du site seulement...
This operation is restricted to administrators only...
@@ -445,7 +445,7 @@ Raison inconnue.
%s pages found.
%s pages trouvées.
Preview: %s
Prévisualiser: %s
Replaced: %s
Remplacé(e) : %s
Search for: %s
@@ -472,8 +472,6 @@ The changes conflict. Please check the page again.
Conflit sur les modifications. SVP vérifiez la page à nouveau.
Please check whether you overwrote those changes.
SVP vérifiez si vous avez écrasé ces modifications.
Anonymous
Anonyme
Cannot delete the index file %s.
Impossible de supprimer le fichier index %s.
Please check the directory permissions.
@@ -508,6 +506,8 @@ Note: This error is normal if no changes have been made.
Remarque : Cette erreur est normale si aucune modification na été effectuée.
Moving %s log entries.
Déplace %s entrées du log.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Positionne ou Retire le verrou global dédition
Edit lock created.
@@ -544,7 +544,7 @@ Deleted %s
Renaming %1 to %2.
Renomme %1 en %2.
The page %s does not exist
La page %s n'existe pas
La page %s nexiste pas
The page %s already exists
La page %s existe déjà
Cannot rename %1 to %2
@@ -563,9 +563,9 @@ Renommer %s en :
# modules/advanced-uploads.pl
################################################################################
Attach file:
Joindre un fichier:
Upload
Uploader
################################################################################
# modules/aggregate.pl
################################################################################
@@ -609,7 +609,7 @@ Ban Contributors to %s
Ban!
Regular expression:
Expression régulière :
%s is banned
These URLs were rolled back. Perhaps you want to add a regular expression to %s?
@@ -620,9 +620,9 @@ Consider banning the IP number as well:
# modules/banned-regexps.pl
################################################################################
Regular expression "%1" matched "%2" on this page.
Expression régulière "%1" correspond à "%2" sur cette page.
Regular expression "%s" matched on this page.
Expression régulière "%s" correspond à cette page.
################################################################################
# modules/big-brother.pl
################################################################################
@@ -724,7 +724,7 @@ Une expression régulière manque au tag de compilation.
# modules/creationdate.pl
################################################################################
Add creation date to page files
Ajouter une date de création aux fichiers des pages
################################################################################
# modules/css-install.pl
################################################################################
@@ -733,7 +733,7 @@ Installer CSS
Copy one of the following stylesheets to %s:
Copier une des feuilles de style suivantes sur %s.
Reset
Réinitialiser
################################################################################
# modules/dates.pl
################################################################################
@@ -764,9 +764,9 @@ Impossible de trouver une version sans texte indésirable.
# modules/diff.pl
################################################################################
Page diff
Page diff
Diff
Diff
################################################################################
# modules/drafts.pl
################################################################################
@@ -806,11 +806,11 @@ modifications ordinaires
# modules/edit-paragraphs.pl
################################################################################
Could not identify the paragraph you were editing
Impossible d'identifier le paragraphe que vous avez édité
This is the section you edited:
Cest la section que vous avez éditée :
This is the current page:
Cest la page actuelle
################################################################################
# modules/find.pl
################################################################################
@@ -820,53 +820,53 @@ Pages correspondant aux noms :
# modules/fix-encoding.pl
################################################################################
Fix character encoding
Corriger lencodage des caractères
Fix HTML escapes
Corriger les caractères déchappement HTML
################################################################################
# modules/form_timeout.pl
################################################################################
Set $FormTimeoutSalt.
Définir $FormTimeoutSalt.
Form Timeout
################################################################################
# modules/gd_security_image.pl
################################################################################
GD or Image::Magick modules not available.
modules GD ou Image::Magick non disponibles.
GD::SecurityImage module not available.
module GD::SecurityImage non disponible.
Image storing failed. (%s)
Erreur denregistrement de limage. (%s)
Bad gd_security_image_id.
Invalide gd_security_image_id.
Please type the six characters from the anti-spam image
Entrez les six caractères de limage anti-spam
Submit
Soumettre
CAPTCHA
CAPTCHA
You did not answer correctly.
Vous navez pas répondu correctement.
$GdSecurityImageFont is not set.
$GdSecurityImageFont nest pas défini.
################################################################################
# modules/git-another.pl
################################################################################
No summary provided
Aucun résumé fourni
################################################################################
# modules/git.pl
################################################################################
no summary available
aucun résumé disponible
page was marked for deletion
page marquée pour suppression
Oddmuse
Oddmuse
Cleaning up git repository
Nettoyage du dépôt git
################################################################################
# modules/google-plus-one.pl
################################################################################
@@ -880,7 +880,7 @@ This page lists the twenty last diary entries and their +1 buttons.
# modules/gravatar.pl
################################################################################
Email:
Courriel :
################################################################################
# modules/header-and-footer-templates.pl
################################################################################
@@ -902,53 +902,53 @@ Index
# modules/joiner.pl
################################################################################
The username %s already exists.
Le nom dutilisateur %s existe déjà.
The email address %s has already been used.
Ladresse courriel %s a déjà été utilisée.
Wait %s minutes before try again.
Attendez %s minutes avant de réessayer.
Registration Confirmation
Confirmation de lenregistrement
Visit the link below to confirm registration.
Visitez le lien ci-dessous pour confirmer lenregistrement.
Recover Account
Restaurer le compte
You can login by following the link below. Then set new password.
Change Email Address
Changer ladresse courriel
To confirm changing email address, follow the link below.
To submit this form you must answer this question:
Question:
Question:
CAPTCHA:
CAPTCHA:
Registration
Enregistrement
The username must be valid page name.
Le nom dutilisateur doit être un nom de page valide.
Confirmation email will be sent to the email address.
Un courriel de confirmation sera envoyé à ladresse courriel.
Repeat Password:
Répétez le mot de passse :
Bad email address format.
Format dadresse courriel invalide.
Password needs to have at least %s characters.
Le mot de passe doit avoir au moins %s caractères.
Passwords differ.
Mots de passe différents.
Email Sent
Courriel envoyé
Confirmation email has been sent to %s. Visit the link on the mail to confirm registration.
Courriel de confirmation envoyé à %s. Visitez le lien du courriel de confirmation d'enregistrement.
Failed to Confirm Registration
Echec de confirmation d'enregistrement.
Invalid key.
Clé non valide.
The key expired.
Clé expirée.
Registration Confirmed
Now, you can login by using username and password.
@@ -956,65 +956,65 @@ Now, you can login by using username and password.
Forgot your password?
Mot de passe oublié ?
Login failed.
Connexion échouée.
You are banned.
You must confirm email address.
Vous devez confirmer l'adresse courriel.
Logged in
Connecté
%s has logged in.
%s est connecté
You should set new password immediately.
Vous devriez définir un nouveau mot de passe immédiatement.
Change Password
Changer le mot de passe
Logged out
Déconnecté
%s has logged out.
%s sest déconnecté
Account Settings
Paramètres de compte
Logout
Se déconnecter
Current Password:
Mot de passe actuel:
New Password:
Nouveau mot de passe:
Repeat New Password:
Répétez le mot de passe:
Password is wrong.
Mot de passe incorrect.
Password Changed
Mot de passe modifié
Your password has been changed.
Votre mot de passe a été modifié.
Forgot Password
Mot de passe oublié
Enter email address, and recovery login ticket will be sent.
Entrez une adresse courriel, un ticket de récupération de connexion sera envoyé.
Not found.
Non trouvé.
The mail address is not valid anymore.
Ladresse courriel nest plus valide.
An email has been sent to %s with further instructions.
Un courriel a été envoyé à %s avec les instructions complémentaires.
New Email Address:
Nouvelle adresse courriel:
Failed to load account.
Echec du chargement du compte.
An email has been sent to %s with a login ticket.
Un courriel a été envoyé à %s avec un ticket de connexion.
Confirmation Failed
Echec de confirmation
Failed to confirm.
Echec de confirmation
Email Address Changed
Adresse courriel modifiée
Email address for %1 has been changed to %2.
Adresse courriel pour %1 modifiée en %2.
Account Management
Gestion des comptes
Ban Account
Enter username of the account to ban:
@@ -1034,7 +1034,7 @@ Unban
%s has been unbanned.
Register
Enregistrement
################################################################################
# modules/lang.pl
################################################################################
@@ -1052,7 +1052,7 @@ Voir !
====1 person liked this====
I like this!
Jaime!
################################################################################
# modules/link-all.pl
################################################################################
@@ -1091,9 +1091,9 @@ Le modèle %s est soit vide soit n'existe pas.
# modules/localnames.pl
################################################################################
Name:
Nom :
URL:
URL:
Define Local Names
Define external redirect:
@@ -1106,7 +1106,7 @@ Noms locaux définis sur %1 : %2
# modules/logbannedcontent.pl
################################################################################
IP number matched %s
Numéro IP correspond à %s
################################################################################
# modules/login.pl
################################################################################
@@ -1209,8 +1209,8 @@ All mail subscriptions
Tous les abonnements e-mail
Subscriptions
Abonnements
Email:
Email:
Courriel :
Show
Voir
Subscriptions for %s:
@@ -1239,27 +1239,38 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Conversion de %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################
Bisect modules
Modules Bisect
Module Bisect
Module Bisect
All modules enabled now!
Tous les modules activés maintenant !
Go back
Retour
Test / Always enabled / Always disabled
Start
Démarrer
Bisecting proccess is already active.
Stop
Stop
It seems like module %s is causing your problem.
Le module %s sembler causer votre problème.
Please note that this module does not handle situations when your problem is caused by a combination of specific modules (which is rare anyway).
Good luck fixing your problem! ;)
@@ -1267,13 +1278,13 @@ Good luck fixing your problem! ;)
Module count (only testable modules):
Current module statuses:
Statuts du module courant :
Good
Bon
Bad
Mauvais
Enabling %s
Activer %s
################################################################################
# modules/module-updater.pl
################################################################################
@@ -1346,9 +1357,9 @@ L'extension "404 handler" nécessite une base de données de liens (links.pl).
# modules/offline.pl
################################################################################
Make available offline
Rendre disponible hors ligne
Offline
Hors ligne
You are currently offline and what you requested is not part of the offline application. You need to be online to do this.
################################################################################
@@ -1413,25 +1424,25 @@ Portrait
# modules/preview.pl
################################################################################
Pages with changed HTML
Pages avec HTML modifié
Preview changes in HTML output
Visualiser les changenements de la sortie HTML
################################################################################
# modules/private-pages.pl
################################################################################
This page is password protected. If you know the password, you can %s. Once you have done that, return and reload this page.
supply the password now
donner le mot de passe maintenant
################################################################################
# modules/private-wiki.pl
################################################################################
This error should not happen. If your password is set correctly and you are still seeing this message, then it is a bug, please report it. If you are just a stranger and trying to get unsolicited access, then keep in mind that all of the data is encrypted with AES-256 and the key is not stored on the server, good luck.
Attempt to read encrypted data without a password.
Tentative de lire des données cryptés sans mot de passe.
Cannot refresh index.
Impossible de mettre à jour l'index
################################################################################
# modules/publish.pl
################################################################################
@@ -1482,17 +1493,17 @@ Index de toutes les pages de petite taille
# modules/sort.pl
################################################################################
Sort alphabetically
Trier alphabétiquement
Sorted alphabetically
Trié alphabétiquement
Sorted by last update first
Trié par dernière modification en premier
Sort by last update
Trier par dernière modification
Sorted by creation date
Trié par date de création
Sort by creation date
Trier par date de création
################################################################################
# modules/static-copy.pl
################################################################################
@@ -1570,7 +1581,7 @@ Failed to run %1 to create thumbnail: %2
%s ran into an error
%s produced no output
%s na produit aucun résultat
Failed to parse %s.
################################################################################
@@ -1604,13 +1615,13 @@ Traduire %s
Thank you for writing a translation of %s.
Merci pour la traduction de %s.
Please indicate what language you will be using.
Merci d'indiquer quelle langue vous allez utiliser.
Merci dindiquer quelle langue vous allez utiliser.
Language is missing
La langue est manquante
Suggested languages:
Langues suggérées
Please indicate a page name for the translation of %s.
Indiquez s'il vous plaît un nom de page pour la traduction de %s.
Indiquez sil vous plaît un nom de page pour la traduction de %s.
More help may be available here: %s.
Plus d'aide disponible ici : %s.
Translated page:
@@ -1636,11 +1647,11 @@ Upgrading Database
Did the previous upgrade end with an error? A lock was left behind.
Unlock wiki
Déverrouiller le wiki
Upgrade complete.
Mise à jour terminée
Upgrade complete. Please remove $ModuleDir/upgade.pl, now.
Mise à jour terminée. SVP, supprimez $ModuleDir/upgade.pl maintenant.
################################################################################
# modules/usemod.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Wichtige Seiten:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Um eine Seite zu löschen, setzt man <strong>%s</strong> auf die erste Zeile.
from %s
von %s
Anonymous
Unbekannt
redirected from %s
Umgeleitet von %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Die Änderungen beeinträchtigen sich gegenseitig. Bitte überprüfen Sie die Seite nochmals.
Please check whether you overwrote those changes.
Bitte überprüfen Sie, ob diese Änderungen überschrieben worden sind.
Anonymous
Unbekannt
Cannot delete the index file %s.
Die Index Datei %s kann nicht gelöscht werden.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
HINWEIS: Dieser Fehler ist normal, wenn keine Änderungen durchgeführt wurden.
Moving %s log entries.
Verschiebe %s Log-Einträge.
Removing IP numbers from %s log entries.
IP Nummbern wurden as %s Log Einträgen entfernt.
Set or Remove global edit lock
Erzeugen oder Aufheben der globalen Bearbeitungssperre
Edit lock created.
@@ -1203,8 +1203,8 @@ All mail subscriptions
Alle Email Abonnements
Subscriptions
Abonnements
Email:
Email:
E-Mail:
Show
Zeigen
Subscriptions for %s:
@@ -1234,6 +1234,17 @@ Es wurden keine Email Adressen gefundne, die zu migrieren wären.
Migrated %s rows.
%s Zeilen wurden migriert.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
%s zu Markdown convertieren
List all non-Markdown pages
Liste aller Seiten, die noch nicht zu Markdown konvertiert wurden
Converting %s
%s konvertieren
Candidates for Conversion to Markdown
Kandidaten für die Konvertierung zu Mardown
################################################################################
# modules/module-bisect.pl
################################################################################
Bisect modules

View File

@@ -226,8 +226,8 @@ Important pages:
Σημαντικές σελίδες:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Για να σημειωθεί μια σελίδα προς διαγραφή, εισάγετε το κείμενο <strong>%s</strong> στην πρώτη γραμμή της σελίδας.
from %s
από %s
Anonymous
Ανώνυμος
redirected from %s
προώθηση από το %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Οι αλλαγές έρχονται σε σύγκρουση. Παρακαλώ δείτε τη σελίδα πάλι.
Please check whether you overwrote those changes.
Παρακαλώ ελέγξτε ότι δεν σβήσατε κάποιες από τις αλλαγές.
Anonymous
Ανώνυμος
Cannot delete the index file %s.
Σφάλμα διαγραφής του αρχείου %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Σημείωση: Αυτό το σφάλμα δεν είναι σημαντικό αν δεν έχουν γίνει αλλαγές.
Moving %s log entries.
Μετακίνηση %s εγγραφών καταγραφής.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Κλείδωμα ή Ξεκλείδωμα του κεντρικού κλειδώματος εκδότη
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -228,8 +228,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
מ %s
Anonymous
אנונימי
redirected from %s
הוכוון מחדש מ %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
אנונימי
Cannot delete the index file %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
הערה: שגיאה זו היא רגילה אם לא בוצעו שינויים.
Moving %s log entries.
מזיז %s ערכי יומן.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
קבע או הסר נעילת עריכה גלובלית
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Pagine importanti
To mark a page for deletion, put <strong>%s</strong> on the first line.
Per impostare una pagine per la cancellazione, inserire <strong>%s</strong> sulla prima riga.
from %s
da %s
Anonymous
Anonimo
redirected from %s
rediretto da %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Le modifiche sono in conflitto. Prego ricontrollare la pagina.
Please check whether you overwrote those changes.
Prego controllate se avete sovrascritto quelle modifiche.
Anonymous
Anonimo
Cannot delete the index file %s.
Impossibile cancellare il file di indice %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Nota: questo errore è normale se non sono stati apportati cambiamenti.
Moving %s log entries.
Spostamento di %s elementi di log.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Imposta o rilascia il blocco globale di modifica
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,7 +226,7 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
Anonymous
redirected from %s
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
중요한 페이지들:
To mark a page for deletion, put <strong>%s</strong> on the first line.
특정 페이지를 삭제 요청페이지로 하려면, <strong>%s</strong> 첫줄에 입력하세요.
from %s
%s 부터
Anonymous
익명
redirected from %s
%s 에서 재전송됨
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
페이지들이 충돌합니다. 페이지를 다시 확인하세요.
Please check whether you overwrote those changes.
변경사항들에 덮어쓰기 하지 않았는지 확인하세요.
Anonymous
익명
Cannot delete the index file %s.
%s 인덱스 파일을 삭제 없습니다.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
주의: 변경이 전혀 없었다면 오류는 정상적인 것입니다.
Moving %s log entries.
%s 로그 엔트리를 옭김.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
전역 편집 잠금을 설정하거나 제거
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,7 +226,7 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
Anonymous
redirected from %s
@@ -296,6 +296,8 @@ Delete
Filter:
Summary:
Last edit
revision %s
@@ -310,8 +312,6 @@ later minor edits
No diff available.
Summary:
Old revision:
Changed:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1201,6 +1201,8 @@ Your mail subscriptions
All mail subscriptions
All mail subscribers
Subscriptions
Email:
@@ -1227,12 +1229,25 @@ The remaining pages do not exist.
Unsubscribed %s from the following pages:
Unsubscribed %s from all pages.
Migrating Subscriptions
No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################
@@ -1398,6 +1413,11 @@ Click to search for references to this permanent anchor
Include permanent anchors
################################################################################
# modules/pingback-server.pl
################################################################################
Only XML-RPC POST requests recognised
################################################################################
# modules/portrait-support.pl
################################################################################
@@ -1462,6 +1482,17 @@ Referrers
################################################################################
All Referrers
################################################################################
# modules/rename-pages.pl
################################################################################
Target page already exists.
Source page does not exist.
Copied from %s
Moved to %s
################################################################################
# modules/search-list.pl
################################################################################
@@ -1528,8 +1559,6 @@ Rebuilding index not done.
Rebuild tag index
list tags
tag cloud
################################################################################
@@ -1686,5 +1715,56 @@ Edit %s.
################################################################################
Tags:
################################################################################
# modules/webmention.pl
################################################################################
Webmention module requires $CommentsPrefix to be set
Webmention requires a POST request
Webmention requires x-www-form-urlencoded requests
Webmention must mention a specific page
Webmention must mention a valid page
Your IP number is blocked: %s
Webmention must mention an existing page
Webmention must mention source
Webmention must mention target
The URL is blocked: %s
Webmention source cannot be verified: %1 returns %2 %3
Webmention source does not link to %s
Webmention for %s already exists
Webmention OK!
Add webmentions
Webmentioning others from %s
Webmention!
No links found.
Webmentioning somebody from %s
Contacting %s
Target reports an error: %s
No Webmention URL found
Success: %s
Failure: %s
#
END_OF_TRANSLATION

View File

@@ -228,8 +228,8 @@ Important pages:
Ważne strony:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Aby zaznaczyć stronę do usunięcia, wpisz <strong>%s</strong> w pierwszej linii.
from %s
z %s
Anonymous
Anonimowy
redirected from %s
przekierowano z %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Wystąpił konflikt zmian. Proszę sprawdzić stronę ponownie.
Please check whether you overwrote those changes.
Proszę sprawdzić, czy nadpisałeś te zmiany.
Anonymous
Anonimowy
Cannot delete the index file %s.
Nie można skasować pliku indeksu %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
Uwaga: Ten błąd jest normalny, jeśli nie było żadnych zmian.
Moving %s log entries.
Przenoszenie %s wpisów logów.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Ustaw lub zwolnij globalną blokadę edycji
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -231,8 +231,8 @@ Important pages:
Páginas importantes:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Para marcar uma página a ser apagada, ponha <strong>%s</strong> na primeira linha.
from %s
de %s
Anonymous
Anónimo
redirected from %s
redireccionado de %s
%s:
@@ -471,8 +471,6 @@ The changes conflict. Please check the page again.
Conflito entre alterações. Por favor reverifique esta página.
Please check whether you overwrote those changes.
Por favor verifique se sobrescreveu essas alterações.
Anonymous
Anónimo
Cannot delete the index file %s.
Impossível apagar o ficheiro de índice %s.
Please check the directory permissions.
@@ -507,6 +505,8 @@ Note: This error is normal if no changes have been made.
NOTA: Este erro é normal se nenhuma alteração foi feita.
Moving %s log entries.
Movendo entradas de log %s.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Marque ou Remova bloqueio global de edição
Edit lock created.
@@ -1238,6 +1238,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Pagini importante:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
de la %s
Anonymous
Anonim
redirected from %s
redirecţionat de la %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Anonim
Cannot delete the index file %s.
Nu am putut şterge fişierul index %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -232,8 +232,8 @@ Important pages:
Служебные страницы:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Чтобы пометить любую страницу к удалению, поместите <strong>%s</strong> первой строкой страницы.
from %s
с %s
Anonymous
Аноним
redirected from %s
перенаправлено с %s
%s:
@@ -472,8 +472,6 @@ The changes conflict. Please check the page again.
Изменения конфликтуют. Проверьте страницу снова.
Please check whether you overwrote those changes.
Пожалуйста удостоверьтесь, что вы не перезаписали чужие изменения.
Anonymous
Аноним
Cannot delete the index file %s.
Не могу удалить индекс-файл %s.
Please check the directory permissions.
@@ -508,6 +506,8 @@ Note: This error is normal if no changes have been made.
Примечание: Эта ошибка - нормально, если не было сделано изменений.
Moving %s log entries.
Перемещаю %s лог записей.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Установка или снятие глобальной блокировки на редактирование
Edit lock created.
@@ -1239,6 +1239,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -228,8 +228,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
од %s
Anonymous
Анонимони
redirected from %s
преусмерено са %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Измене су у сукобу. Проверите страну поново.
Please check whether you overwrote those changes.
Проверите да нисте пребрисали ове те измене.
Anonymous
Анонимони
Cannot delete the index file %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
Пажња: Ова грешка је уобичајена уколико ништа нисте изменили.
Moving %s log entries.
Премештам %s дневничких ставки.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Постави или уклони глобалну браву за уређивање
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -230,8 +230,8 @@ Important pages:
Páginas importantes:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Para marcar una página para su eliminación, escribir <strong>%s</strong> en la primera línea.
from %s
desde %s
Anonymous
Anónimo
redirected from %s
redirigido desde %s
%s:
@@ -470,8 +470,6 @@ The changes conflict. Please check the page again.
Los cambios entran en conflicto. Por favor, comprueba la página otra vez.
Please check whether you overwrote those changes.
Por favor, comprueba si sobreescribes esos cambios.
Anonymous
Anónimo
Cannot delete the index file %s.
No se puede borrar el archivo índice %s.
Please check the directory permissions.
@@ -506,6 +504,8 @@ Note: This error is normal if no changes have been made.
Nota: Este error es normal si no se han hecho cambios.
Moving %s log entries.
Moviendo %s entradas del registro.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Establecer o quitar bloqueo global de edición
Edit lock created.
@@ -1237,6 +1237,17 @@ No non-migrated email addresses found, migration not necessary.
No se encontraron direcciones de correo no migradas, la migración no es necesaria.
Migrated %s rows.
Migrado %s filas.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -5,6 +5,7 @@
# Zrajm C Akfohg <zrajm@klingonska.org>
# Copyright (c) 2004-06 Johan Adler <alltid@nyfiken.org>
# Copyright (c) 2004 Zrajm C Akfohg <zrajm@klingonska.org>
# Copyright (c) 2021 Tobias Fendin
#
# Permission is granted to copy, distribute and/or modify this
# document under the terms of the GNU Free Documentation License,
@@ -230,8 +231,8 @@ Important pages:
Viktiga sidor:
To mark a page for deletion, put <strong>%s</strong> on the first line.
För att markera en sida för radering, skriv <strong>%s</strong> första raden.
from %s
från %s
Anonymous
Anonym
redirected from %s
omdirigerad från %s
%s:
@@ -300,6 +301,8 @@ Delete
Filter:
Summary:
Sammanfattning:
Last edit
revision %s
@@ -314,8 +317,6 @@ later minor edits
No diff available.
Information om ändring är inte tillgänglig.
Summary:
Sammanfattning:
Old revision:
Gammal version:
Changed:
@@ -470,8 +471,6 @@ The changes conflict. Please check the page again.
Ändringarna står i konflikt. Kontrollera sidan igen.
Please check whether you overwrote those changes.
Kontrollera om du skrev över de ändringarna.
Anonymous
Anonym
Cannot delete the index file %s.
Kan inte radera index-filen %s.
Please check the directory permissions.
@@ -506,6 +505,8 @@ Note: This error is normal if no changes have been made.
Observera: Det här felet är normalt om inga ändringar gjorts.
Moving %s log entries.
Flyttar %s loggnotering.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Slå eller av globalt redigeringslås
Edit lock created.
@@ -699,11 +700,6 @@ Clustermap
Klusterkarta
Pages without a Cluster
Sidor utan kluster
################################################################################
# modules/comment-div-wrapper.pl
################################################################################
Comments:
################################################################################
# modules/commentcount.pl
################################################################################
@@ -711,6 +707,11 @@ Comments on
Kommentarer till
Comment on
Kommentar till
################################################################################
# modules/comment-div-wrapper.pl
################################################################################
Comments:
################################################################################
# modules/compilation.pl
################################################################################
@@ -1205,6 +1206,8 @@ Your mail subscriptions
All mail subscriptions
All mail subscribers
Subscriptions
Email:
@@ -1231,12 +1234,25 @@ The remaining pages do not exist.
Unsubscribed %s from the following pages:
Unsubscribed %s from all pages.
Migrating Subscriptions
No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################
@@ -1293,6 +1309,11 @@ You linked more than %s times to the same domain. It would seem that only a spam
Namespaces
################################################################################
# modules/nearlink-create.pl
################################################################################
(create locally)
################################################################################
# modules/near-links.pl
################################################################################
@@ -1312,11 +1333,6 @@ EditNearLinks
Redigera närlänkar
The same page on other sites:
Samma sida andra siter:
################################################################################
# modules/nearlink-create.pl
################################################################################
(create locally)
################################################################################
# modules/no-question-mark.pl
################################################################################
@@ -1402,6 +1418,11 @@ Click to search for references to this permanent anchor
Klicka för att söka efter referenser till det här permanenta ankaret
Include permanent anchors
Med permanenta ankare
################################################################################
# modules/pingback-server.pl
################################################################################
Only XML-RPC POST requests recognised
################################################################################
# modules/portrait-support.pl
################################################################################
@@ -1466,6 +1487,17 @@ Sidor som länkat hit
################################################################################
All Referrers
Alla som länkat hit
################################################################################
# modules/rename-pages.pl
################################################################################
Target page already exists.
Source page does not exist.
Copied from %s
Moved to %s
################################################################################
# modules/search-list.pl
################################################################################
@@ -1475,22 +1507,22 @@ Page list for %s
# modules/small.pl
################################################################################
Index of all small pages
Index av alla små sidor
################################################################################
# modules/sort.pl
################################################################################
Sort alphabetically
Sortera alfabetiskt
Sorted alphabetically
Sorterad alfabetiskt
Sorted by last update first
Sorterad med senast uppdaterad först
Sort by last update
Sortera uppdateringstid
Sorted by creation date
Sorterad skapande datum
Sort by creation date
Sortera skapande datum
################################################################################
# modules/static-copy.pl
################################################################################
@@ -1507,35 +1539,33 @@ Editing not allowed for %s.
# modules/svg-edit.pl
################################################################################
Edit image in the browser
Redigera bilden i webbläsaren
Summary of your changes:
Sammanställning av dina ändringar:
################################################################################
# modules/sync.pl
################################################################################
Copy to %1 succeeded: %2.
Kopiering till %1 lyckades: %2.
Copy to %1 failed: %2.
Kopiering till %1 misslyckades: %2.
################################################################################
# modules/tags.pl
################################################################################
Tag
Tagg
Feed for this tag
Flöde för denna tagg
Tag Cloud
Taggmoln
Rebuilding index not done.
Ombyggnad av index inte klart.
(Rebuilding the index can only be done once every 12 hours.)
(Ombyggnad av indexet kan endast göras var 12:e timme.)
Rebuild tag index
list tags
Bygg om tagg index
tag cloud
taggmoln
################################################################################
# modules/templates.pl
################################################################################
@@ -1545,41 +1575,41 @@ Eller använd en av följande mallar:
# modules/throttle.pl
################################################################################
Too many instances. Only %s allowed.
För många instanser. Endast %s tillåtna.
Please try again later. Perhaps somebody is running maintenance or doing a long search. Unfortunately the site has limited resources, and so we must ask you for a bit of patience.
Vänligen försök senare. Kanske pågår ett underhåll eller har någon gjort en lång sökning. Dessvärre har denna sida begränsade resurser, vi ber dig ha tålamod.
################################################################################
# modules/thumbs.pl
################################################################################
thumb
Error creating thumbnail from nonexisting page %s.
Misslyckades med att skapa miniatyr av en icke existerande sida %s.
Can not create thumbnail for file type %s.
Kan inte skapa miniatyr för filtypen %s.
Can not create thumbnail for a text document
Kan inte skapa en miniatyr av ett textdokument
Can not create path for thumbnail - %s
Kan inte skapa sökväg för miniatyr - %s
Could not open %s for writing whilst trying to save image before creating thumbnail. Check write permissions.
Kan inte öppna %s för skrivning före skapande av miniatyrbilden. Kontrollera skrivrättigheter.
Failed to run %1 to create thumbnail: %2
Kunde inte köra %1 för att skapa miniatyren: %2
%s ran into an error
%s misslyckades
%s produced no output
%s producerade ingen utdata
Failed to parse %s.
Misslyckades med att tolka %s.
################################################################################
# modules/timezone.pl
################################################################################
Timezone
Tidszon
Pick your timezone:
Välj din tidszon:
Set
Sätt
################################################################################
# modules/toc-headers.pl
################################################################################
@@ -1589,7 +1619,7 @@ Innehåll
# modules/today.pl
################################################################################
Create a new page for today
Skapa en ny sida för idag
################################################################################
# modules/translation-links.pl
################################################################################
@@ -1630,15 +1660,15 @@ Sidan finns inte.
# modules/upgrade.pl
################################################################################
Upgrading Database
Uppgradering av databas
Did the previous upgrade end with an error? A lock was left behind.
Misslyckades den senaste uppgraderingen med ett fel? Ett lås finns kvar.
Unlock wiki
Lås upp wiki
Upgrade complete.
Uppgradering klar.
Upgrade complete. Please remove $ModuleDir/upgade.pl, now.
Uppgradering klar. Vänlingen ta bort $ModuleDir/upgade.pl nu.
################################################################################
# modules/usemod.pl
################################################################################
@@ -1665,7 +1695,7 @@ Wanted Pages
# modules/webapp.pl
################################################################################
Web application for offline browsing
Webbapplikation för off-line visning
################################################################################
# modules/webdav.pl
################################################################################
@@ -1680,15 +1710,68 @@ Blogg
# modules/weblog-3.pl
################################################################################
Matching pages:
Matchande sidor:
New
Nytt
Edit %s.
Redigera %s.
################################################################################
# modules/weblog-4.pl
################################################################################
Tags:
Taggar:
################################################################################
# modules/webmention.pl
################################################################################
Webmention module requires $CommentsPrefix to be set
Webmention modulen kräver att $CommentsPrefix är satt
Webmention requires a POST request
Webmention kräver en POST förfrågan
Webmention requires x-www-form-urlencoded requests
Webmention kräver x-www-form-urlencoded förfrågan
Webmention must mention a specific page
Webmention måste ange en specifik sida
Webmention must mention a valid page
Webmention måste ange en giltig sida
Your IP number is blocked: %s
Ditt IP-nummer är blockerat: %s
Webmention must mention an existing page
Webmention måste ange en existerande sida
Webmention must mention source
Webmention måste ange en källa
Webmention must mention target
Webmention måste ange ett mål
The URL is blocked: %s
URL:en är blockerad: %s
Webmention source cannot be verified: %1 returns %2 %3
Webmention källan kan inte verifieras: %1 returnerar %2 %3
Webmention source does not link to %s
Webmention källan länkar inte till %s
Webmention for %s already exists
Webmention för %s finns redan
Webmention OK!
Add webmentions
Lägg till webmention
# Could not find a good translation of "Webmentioning"
Webmentioning others from %s
Webmentioning andra från %s
Webmention!
No links found.
Inga länkar hittade
Webmentioning somebody from %s
Webmentioning någon från %s
Contacting %s
Kontakta %s
Target reports an error: %s
Målet rapporterar ett fel: %s
No Webmention URL found
Ingen Webmention URL hittad
Success: %s
Lyckat: %s
Failure: %s
Misslyckat: %s
#
END_OF_TRANSLATION

View File

@@ -226,8 +226,8 @@ Important pages:
Важливі сторінки:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Для того, щоб помітити сторінку для видалення, вставте <strong>%s</strong> на першу строчку.
from %s
від %s
Anonymous
Анонім
redirected from %s
перенаправлено з %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Зміни конфліктують. Будь ласка, перевірте цю сторінку ще раз.
Please check whether you overwrote those changes.
Будь ласка, перевірте, чи перезаписали ви ці зміни.
Anonymous
Анонім
Cannot delete the index file %s.
Не можу видалити файл з покажчиком %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Примітка: Ця помилка припустима, якщо не було зроблено жодних змін.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Встановити або Видалити глобальний блок редагування
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

227
modules/webmention.pl Normal file
View File

@@ -0,0 +1,227 @@
# Copyright (C) 2004 Brock Wilcox <awwaiid@thelackthereof.org>
# Copyright (C) 2019 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;
use LWP::UserAgent;
use Modern::Perl;
use XML::LibXML;
AddModuleDescription('webmention.pl', 'Webmention Server Extension');
# Specification: https://www.w3.org/TR/webmention/
our ($CommentsPrefix, $q, $HtmlHeaders, %Action, $QuestionaskerSecretKey,
@MyInitVariables, %IndexHash, $BannedContent, $UsePathInfo, $HomePage,
$Message, @MyAdminCode, $FullUrlPattern);
push(@MyInitVariables, \&WebmentionServerAddLink, \&WebmentionAddAction);
# Add webmentions metadata to our pages
sub WebmentionServerAddLink {
$Message .= T('Webmention module requires $CommentsPrefix to be set') unless $CommentsPrefix;
# only allow linking to reasonable pages: no URL parameters!
my @params = $q->param;
return unless GetParam('action', 'browse') eq 'browse';
return if GetParam('revision');
my $id = GetId() || $HomePage;
return if $id =~ /^$CommentsPrefix/;
my $link = '<link rel="webmention" type="application/wiki" href="'
. ScriptUrl('webmention/' . UrlEncode($id)) . '" />';
$HtmlHeaders .= $link unless $HtmlHeaders =~ /rel="webmention"/;
}
sub WebmentionAddAction {
SetParam('action', 'webmention') if $q->path_info =~ m|/webmention\b|;
}
# Process incoming webmentions
$Action{webmention} = \&DoWebmentionServer;
sub DoWebmentionServer {
my $id = FreeToNormal(shift);
# some sanity checks for the request
if ($q->request_method() ne 'POST') {
ReportError(T('Webmention requires a POST request'), '400 BAD REQUEST');
}
if ($q->content_type() ne 'application/x-www-form-urlencoded') {
ReportError(T('Webmention requires x-www-form-urlencoded requests'), '400 BAD REQUEST');
}
# some sanity checks for the target page name
if (not $id) {
ReportError(T('Webmention must mention a specific page'), '400 BAD REQUEST');
}
my $error = ValidId($id);
if ($error) {
ReportError(T('Webmention must mention a valid page'), '400 BAD REQUEST');
}
# check the IP number for bans
my $rule = UserIsBanned();
if ($rule) {
ReportError(Ts('Your IP number is blocked: %s', $rule), '403 FORBIDDEN');
}
# check that the target page exists
AllPagesList();
if (not $IndexHash{$id}) {
ReportError(T('Webmention must mention an existing page'), '404 NOT FOUND');
}
# verify parameters
my $source = GetParam('source', undef) or ReportError(T('Webmention must mention source'), '400 BAD REQUEST');
my $target = GetParam('target', undef) or ReportError(T('Webmention must mention target'), '400 BAD REQUEST');
# verify that the source isn't banned
$rule = BannedContent($source);
if ($rule) {
ReportError(Ts('The URL is blocked: %s', $rule), '403 FORBIDDEN');
}
# verify that the webmention is legit
my $ua = LWP::UserAgent->new(agent => 'Oddmuse Webmention Server/0.1');
my $response = $ua->get($source);
if (not $response->is_success) {
ReportError(Tss('Webmention source cannot be verified: %1 returns %2 %3',
$source, $response->code, $response->message), '400 BAD REQUEST');
}
my $self = ScriptUrl(UrlEncode($id));
if ($response->decoded_content !~ /$self/) {
ReportError(Ts('Webmention source does not link to %s', $self), '400 BAD REQUEST');
}
$id = $CommentsPrefix . $id;
if (GetPageContent($id) =~ /$source/) {
ReportError(Ts('Webmention for %s already exists', $source), '400 BAD REQUEST');
}
# try to determine a name and a link
my ($username, $homepage);
my $parser = XML::LibXML->new(recover => 2);
my $dom = $parser->load_html(string => $response->decoded_content);
my @nodes = $dom->findnodes('//*[@rel="author"]');
if (@nodes) {
my $node = shift @nodes;
$username = $node->textContent;
$homepage = $node->getAttribute('href');
}
# post a comment without redirect at the end
SetParam('aftertext', 'Webmention: ' . $source);
SetParam('summary', 'Webmention');
SetParam('username', $username || T('Anonymous'));
SetParam('homepage', $homepage);
SetParam($QuestionaskerSecretKey, 1) if $QuestionaskerSecretKey;
local *ReBrowsePage = sub {};
DoPost($id);
# response
print GetHeader('', T('Webmention OK!'));
print $q->start_div({-class=>'content webmention'}),
$q->p(GetPageLink($BannedContent)),
$q->end_div;
PrintFooter();
}
# Allow user to webmention other sites
push(@MyAdminCode, \&WebmentionMenu);
sub WebmentionMenu {
my ($id, $menuref, $restref) = @_;
if ($id) {
push(@$menuref, ScriptLink('action=webmentions;id=' . $id, T('Add webmentions'), 'webmentions'));
}
}
$Action{webmentions} = \&DoWebmentionMenu;
sub DoWebmentionMenu {
my $id = GetId();
ValidIdOrDie($id);
print GetHeader('', Ts('Webmentioning others from %s', NormalToFree($id)), '');
my $text = GetPageContent($id);
my @urls = $text =~ /$FullUrlPattern/g;
if (@urls) {
print GetFormStart();
print GetHiddenValue('action', 'webmentioning');
print GetHiddenValue('from', UrlEncode($id));
print '<p>';
print $q->checkbox_group('to', \@urls, undef, 'true');
print '</p>';
print $q->submit('go', T('Webmention!'));
} else {
print $q->p(T('No links found.'));
}
PrintFooter();
}
$Action{webmentioning} = \&DoWebmention;
sub DoWebmention {
my $id = GetParam('from');
ValidIdOrDie($id);
my $from = ScriptUrl($id);
my @to = $q->multi_param('to');
ReportError('Missing target') unless @to;
print GetHeader('', Ts('Webmentioning somebody from %s', NormalToFree($id)), '');
for my $to (@to) {
Webmention($from, $to);
}
PrintFooter();
}
sub Webmention {
my ($from, $to) = @_;
ReportError('Target must be an URL', '400 BAD REQUEST', 0, $q->p($to)) unless $to =~ /$FullUrlPattern/;
my $ua = LWP::UserAgent->new(agent => "Oddmuse Webmention Client/0.1");
print $q->p(Ts('Contacting %s', $to));
my $response = $ua->get($to);
if (!$response->is_success) {
print $q->p(Ts('Target reports an error: %s', $response->status_line));
return;
}
print $q->p("Parsing response");
my $data = $response->decoded_content;
my $parser = XML::LibXML->new(recover => 2);
my $dom = $parser->load_html(string => $data);
my $webmention = $dom->findvalue('//link[@rel="webmention"]/@href');
if (!$webmention) {
print $q->p(T('No Webmention URL found'));
return;
}
print $q->p("Webmention URL is $webmention");
$response = $ua->post($webmention, { source => $from, target => $to });
my $message = $response->code . " " . $response->message;
if ($response->is_success) {
print $q->p(Ts("Success: %s", $message));
} else {
print $q->p(Ts("Failure: %s", $message));
$dom = $parser->load_html(string => $response->decoded_content());
for my $node ($dom->getElementsByTagName('script')) { $node->parentNode->removeChild($node) };
for my $node ($dom->getElementsByTagName('style')) { $node->parentNode->removeChild($node) };
print $q->p($dom->textContent);
}
}

61
modules/wordcount.pl Normal file
View File

@@ -0,0 +1,61 @@
# Copyright (C) 2005 Robin V. Stacey (robin@greywulf.net)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.
# 59 Temple Place, Suite 330
# Boston, MA 02111-1307 USA
# This module adds a wordcount to the bottom of edit boxes. The javascript code is munged from
# Richard Livsey's Textarea Tools page: http://livsey.org/experiments/textareatools/
# Though I've stripped it down to it's barest necessities
use strict;
use v5.10;
our (@MyInitVariables, $HtmlHeaders, $EditNote);
AddModuleDescription('wordcount.pl', 'Word Count Extension');
push(@MyInitVariables, \&WordcountAddScript);
sub WordcountAddScript {
$HtmlHeaders .= "<script type='text/javascript'>
function addEvent(obj, evType, fn) {
if (obj.addEventListener) {
obj.addEventListener(evType, fn, true);
return true;
} else if (obj.attachEvent) {
var r = obj.attachEvent('on'+evType, fn);
return r;
} else { return false; }
}
addEvent(window, 'load', function() {
document.getElementById('textWordCount').innerHTML = numWords(document.getElementById('text').value);
document.getElementById('text').onkeyup = function() {
document.getElementById('textWordCount').innerHTML = numWords(document.getElementById('text').value);
}
});
function numWords(string) {
string = string + ' ';
string = string.replace(/^[^A-Za-z0-9]+/gi, '');
string = string.replace(/[^A-Za-z0-9]+/gi, ' ');
var items = string.split(' ');
return items.length -1;
}
</script>";
}
$EditNote = "Words: <span id='textWordCount'></span>" . $EditNote;

94
scripts/ip-to-regexp.pl Normal file
View File

@@ -0,0 +1,94 @@
use Modern::Perl;
use Net::Whois::Parser qw/parse_whois/;
sub main {
my $ip = shift(@ARGV);
die "Provide an IP number as argument.\n" unless $ip;
print get_regexp_ip(get_range($ip)), "\n";
}
sub get_range {
my $ip = shift;
my $response = parse_whois(domain => $ip);
my $re = '(?:[0-9]{1,3}\.){3}[0-9]{1,3}';
my ($start, $end) = $response->{inetnum} =~ /($re) *- *($re)/;
return $start, $end;
}
sub get_groups {
my ($from, $to) = @_;
my @groups;
if ($from < 10) {
my $to = $to >= 10 ? 9 : $to;
push(@groups, [$from, $to]);
$from = $to + 1;
}
while ($from < $to) {
my $to = int($from/100) < int($to/100) ? $from + 99 - $from % 100 : $to;
if ($from % 10) {
push(@groups, [$from, $from + 9 - $from % 10]);
$from += 10 - $from % 10;
}
if (int($from/10) < int($to/10)) {
if ($to % 10 == 9) {
push(@groups, [$from, $to]);
$from = 1 + $to;
} else {
push(@groups, [$from, $to - 1 - $to % 10]);
$from = $to - $to % 10;
}
} else {
push(@groups, [$from - $from % 10, $to]);
last;
}
if ($to % 10 != 9) {
push(@groups, [$from, $to]);
$from = 1 + $to; # jump from 99 to 100
}
}
return \@groups;
}
sub get_regexp_range {
my @chars;
for my $group (@{get_groups(@_)}) {
my ($from, $to) = @$group;
my $char;
for (my $i = length($from); $i >= 1; $i--) {
if (substr($from, - $i, 1) eq substr($to, - $i, 1)) {
$char .= substr($from, - $i, 1);
} else {
$char .= '[' . substr($from, - $i, 1) . '-' . substr($to, - $i, 1). ']';
}
}
push(@chars, $char);
}
return join('|', @chars);
}
sub get_regexp_ip {
my ($from, $to) = @_;
my @start = split(/\./, $from);
my @end = split(/\./, $to);
my $regexp = "^";
for my $i (0 .. 3) {
if ($start[$i] eq $end[$i]) {
$regexp .= $start[$i];
} elsif ($start[$i] eq '0' and $end[$i] eq '255') {
last;
} elsif ($start[$i + 1] > 0) {
$regexp .= '(' . $start[$i] . '\.('
. get_regexp_range($start[$i + 1], '255') . ')|'
. get_regexp_range($start[$i] + 1, $end[$i + 1]) . ')';
$regexp .= '\.';
last;
} else {
$regexp .= '(' . get_regexp_range($start[$i], $end[$i]) . ')$';
last;
}
$regexp .= '\.' if $i < 3;
}
return $regexp;
}
main();

71
scripts/pingback-client.pl Executable file
View File

@@ -0,0 +1,71 @@
#! /usr/bin/perl -w
# Copyright (C) 2013 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/>.
=head1 Pingback from one site to another
If you link from page A on your site to page B on some other site, you can
invoke this script with the command-line arguments A and B. In theory, this will
create a link back from B to A, letting them and all their visitors know that
you wrote something in response.
=cut
use Modern::Perl;
use RPC::XML;
use RPC::XML::Client;
use XML::LibXML;
use LWP::UserAgent;
use Data::Dumper;
if (@ARGV != 2) {
die "Usage: pingback-client FROM TO\n";
}
my ($from, $to) = @ARGV;
my $ua = LWP::UserAgent->new;
$ua->agent("OddmusePingbackClient/0.1");
print "Getting $to\n";
my $response = $ua->get($to);
if (!$response->is_success) {
die $response->status_line;
}
print "Parsing $to\n";
my $data = $response->decoded_content;
my $parser = XML::LibXML->new(recover => 2);
my $dom = $parser->load_html(string => $data);
my $pingback = $dom->findvalue('//link[@rel="pingback"]/@href');
if (!$pingback) {
die "Pingback URL not found in $to\n";
}
print "Pingback URL is $pingback\n";
my $request = RPC::XML::request->new(
'pingback.ping', $from, $to);
my $client = RPC::XML::Client->new($pingback);
$response = $client->send_request($request);
if (!ref($response)) {
die $response;
}
print Dumper($response->value);

View File

@@ -1,5 +1,5 @@
#! /usr/bin/perl
# Copyright (C) 2010 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20102019 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
@@ -22,6 +22,8 @@ use MIME::Entity;
use File::Temp;
use File::Basename;
use File::Path;
use Net::SMTP;
use Authen::SASL qw(Perl);
# This script can be invoked as follows:
# perl rc2mail.pl -r http://localhost/cgi-bin/wiki \
@@ -36,7 +38,8 @@ use File::Path;
# gets http://localhost/cgi-bin/wiki?action=rss;days=1;full=1;short=0
# And http://localhost/cgi-bin/wiki?action=subscriptionlist;raw=1;pwd=foo
# -m user:password@mailhost for sending email using SMTP Auth. Without this
# information, the script will send mail to localhost.
# information, the script will send mail to localhost. The host can end
# in a port number, e.g. "kensanata:*secret*@smtp.migadu.com:587"
# -f email address to use as the sender.
# -t timestamp file; it's last modified date is used to determine when the
# the last run was and an appropriate URL is used. Instead of days=1 it
@@ -137,7 +140,7 @@ sub send_files {
$sent += @subscribers;
send_file($id, $title, $item, @subscribers);
}
print "$sent messages sent\n" if $sent;
print "$sent messages sent\n" if $sent and not $quiet;
}
sub send_file {
@@ -170,46 +173,31 @@ sub send_mail {
Path => $fh,
Type=> "text/html");
if ($host) {
print "Sending $title to $subscriber using ${user}\@${host}\n" if $verbose;
eval {
require Net::SMTP::TLS;
my $smtp = Net::SMTP::TLS->new($host,
User => $user,
Password => $password);
$smtp->mail($from);
$smtp->to($subscriber);
print "$root\nSending $title to $subscriber using ${user}\@${host}\n" if $verbose;
my $smtp = Net::SMTP->new($host, Debug => $debug);
$smtp->starttls();
# the following requires Authen::SASL!
$smtp->auth($user, $password);
$smtp->mail($from);
if ($smtp->to($subscriber)) {
$smtp->data;
$smtp->datasend($mail->stringify);
$smtp->dataend;
$smtp->quit;
};
if ($@) {
require Net::SMTP::SSL;
my $smtp = Net::SMTP::SSL->new($host, Port => 465);
$smtp->auth($user, $password);
$smtp->mail($from);
$smtp->to($subscriber);
$smtp->data;
$smtp->datasend($mail->stringify);
$smtp->dataend;
$smtp->quit;
}
} else {
my @recipients = $mail->smtpsend();
if (@recipients) {
print "Sent $title to ", join(', ', @recipients), "\n" unless $quiet;
} else {
print "Failed to send $title to $subscriber\n" unless $quiet;
warn "Error: ", $smtp->message();
}
$smtp->quit;
}
}
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();
}

91
scripts/stats.pl Executable file
View File

@@ -0,0 +1,91 @@
#! /usr/bin/perl -w
# Copyright (C) 2005, 2007, 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 Modern::Perl;
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 main {
my ($PageDir) = @_;
my $pages = 0;
my $texts = 0;
my $redirects = 0;
my $files = 0;
my $big = 0;
# include dotfiles!
local $/ = undef; # Read complete files
say "Reading files...";
my @files = glob("$PageDir/*.pg $PageDir/.*.pg");
my $n = @files;
local $| = 1; # flush!
foreach my $file (@files) {
if (not --$n % 10) {
printf("\r%06d files to go", $n);
}
next unless $file =~ m|.*/(.+)\.pg$|;
my $page = $1;
open(F, $file) or die "Cannot read $page file: $!";
my $data = <F>;
close(F);
my %result = ParseData($data);
$pages++;
if ($result{text} =~ /^#FILE /) {
$files++;
} elsif ($result{text} =~ /^#REDIRECT /) {
$redirects++;
} else {
$texts++;
$big++ if length($result{text}) > 15000;
}
}
printf("\r%06d files to go\n", 0);
printf("Pages: %7d\n", $pages);
printf("Files: %7d\n", $files);
printf("Redirects: %6d\n", $redirects);
printf("Texts: %7d\n", $texts);
printf("Big: %7d\n", $big);
}
use Getopt::Long;
my $regexp = undef;
my $page = 'page';
my $help;
GetOptions ("page=s" => \$page,
"help" => \$help);
if ($help) {
print qq{
Usage: $0 [--page DIR]
Prints some stats about the pages in DIR.
--page designates the page directory. By default this is 'page' in the
current directory. If you run this script in your data directory,
the default should be fine.
}
} else {
main ($page);
}

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

View File

@@ -1,24 +1,41 @@
#!/usr/bin/env perl
use Modern::Perl;
use Mojolicious::Lite;
use Mojo::Cache;
use Archive::Tar;
use File::Basename;
use Sort::Versions;
use Encode qw(decode_utf8);
my $dir = "/home/alex/oddmuse.org/releases";
my $cache = Mojo::Cache->new(max_keys => 50);
get '/' => sub {
my $c = shift;
my @tarballs = sort map {
sub tarballs {
my @tarballs = reverse sort versioncmp map {
my ($name, $path, $suffix) = fileparse($_, '.tar.gz');
$name;
} <$dir/*.tar.gz>;
$c->render(template => 'index', tarballs => \@tarballs);
return \@tarballs;
}
sub tarball {
my $tarball = shift;
if ($tarball eq 'latest') {
my $tarballs = tarballs();
$tarball = shift @$tarballs;
}
return $tarball;
}
get '/' => sub {
my $c = shift;
my $tarballs = tarballs();
unshift @$tarballs, 'latest';
$c->render(template => 'index', tarballs => $tarballs);
} => 'main';
get '/#tarball' => sub {
my $c = shift;
my $tarball = $c->param('tarball');
my $tarball = tarball $c->param('tarball');
my $files = $cache->get($tarball);
if (not $files) {
$c->app->log->info("Reading $tarball.tar.gz");
@@ -36,7 +53,7 @@ get '/#tarball' => sub {
get '/#tarball/#file' => sub {
my $c = shift;
my $tarball = $c->param('tarball');
my $tarball = tarball $c->param('tarball');
my $file = $c->param('file');
my $text = $cache->get("$tarball/$file");
if (not $text) {
@@ -68,7 +85,9 @@ versions of Oddmuse.</p>
<ul>
% for my $tarball (@$tarballs) {
<li>
% if ($tarball ne 'latest') {
<a href="https://oddmuse.org/releases/<%= $tarball %>.tar.gz"><%= $tarball %>.tar.gz</a>
% }
(files for <%= link_to release => {tarball => $tarball} => begin %>\
<%= $tarball =%><%= end %>)
</li>

View File

@@ -30,7 +30,7 @@ if (@ARGV == 1 and $ARGV[0] eq '--help') {
sub AddModuleDescription { print $_[0], ' ' };
our %Translate;
my @files = <modules/translations/*-utf8.pl>;
my @files = <./modules/translations/*-utf8.pl>;
for (@files) {
if (@ARGV) { # some specific modules
my $files = join ' ', map { quotemeta } @ARGV; # quick and dirty

120
scripts/unsubscribe.pl Normal file
View File

@@ -0,0 +1,120 @@
#! /usr/bin/perl
# Copyright (C) 20102021 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/>.
=head1 NAME
unsubscribe.pl - mass unsubscribe from Oddmuse
=head2 SYNOPSIS
B<perl unsubscribe.pl> F<MAILDB> [B<--regexp=>I<REGEXP>]
B<perl unsubscribe.pl> F<MAILDB> [B<--dump>]
=head2 DESCRIPTION
If you use the Mail Extension to Oddmuse, you end up with subscriptions to very
old pages. This script helps you unsubsribe people from old pages.
C<--regexp> indicates a regular expression matching pages names
The mandatory F<MAILDB> argument is the file containing all the mail
subscriptions.
=head2 EXAMPLES
Make a copy, unsubscribe people, check a dump of the remaining subscriptions,
and move the file back to the wiki data directory.
cp ~/alexschroeder/mail.db copy.db
perl ~/src/oddmuse/scripts/unsubscribe.pl copy.db --regexp='20[01][0-9]'
perl ~/src/oddmuse/scripts/unsubscribe.pl copy.db --dump
mv copy.db ~/alexschroeder/mail.db
=cut;
use Modern::Perl;
use Getopt::Long;
use Encode qw(encode_utf8 decode_utf8);
use DB_File;
binmode(STDOUT, ":utf8");
my $re = "";
my $confirm;
my $dump;
GetOptions ("regexp=s" => \$re,
"dump" => \$dump,
"confirm" => \$confirm, );
my $file = shift;
die "Not a file: $file" unless -f $file;
die "Unknown arguments: @ARGV" if @ARGV;
sub UrlEncode {
my $str = shift;
return '' unless $str;
my @letters = split(//, encode_utf8($str));
my %safe = map {$_ => 1} ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.', '!', '~', '*', "'", '(', ')', '#');
foreach my $letter (@letters) {
$letter = sprintf("%%%02x", ord($letter)) unless $safe{$letter};
}
return join('', @letters);
}
sub UrlDecode {
my $str = shift;
return '' unless $str;
$str =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eig;
return decode_utf8($str);
}
tie my %h, "DB_File", $file;
my $FS = "\x1e";
if ($dump) {
for my $key (keys %h) {
my @value = split /$FS/, UrlDecode($h{$key});
say UrlDecode($key), ": @value";
}
exit;
}
for my $raw (keys %h) {
if ($raw =~ /@/) {
# email address
my $mail = UrlDecode($raw);
my $value = $h{$raw};
my @subscriptions = grep !/$re/, map { UrlDecode($_) } split /$FS/, $value;
if (@subscriptions) {
$h{$raw} = join $FS, map { UrlEncode($_) } @subscriptions if $confirm;
say "> $mail: remains subscribed to @subscriptions";
} else {
delete $h{$raw} if $confirm;
say "> $mail: unsubscribe from all pages";
}
} else {
my $id = UrlDecode($raw);
next unless $id =~ /$re/;
delete $h{$raw} if $confirm;
say "Delete $id";
}
}
untie %h;
say "Use --confirm to actually do it" unless $confirm;

84
scripts/webmention.pl Normal file
View File

@@ -0,0 +1,84 @@
#! /usr/bin/perl -w
# Copyright (C) 2019 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/>.
=head1 Webmention from one site to another
If you link from page A on your site to page B on some other site, you can
invoke this script with the command-line arguments A and B. In theory, this will
create a link back from B to A, letting them and all their visitors know that
you wrote something in response.
=cut
use Modern::Perl;
use XML::LibXML;
use LWP::UserAgent;
use Data::Dumper;
if (@ARGV != 2) {
die "Usage: webmention FROM TO\n";
}
my $parser = XML::LibXML->new(recover => 2);
my ($from, $to) = @ARGV;
my $ua = LWP::UserAgent->new(agent => "Oddmuse Webmention Client/0.1");
print "Getting $from\n";
my $response = $ua->get($from);
if (!$response->is_success) {
die $response->status_line;
}
print "Parsing $from\n";
my ($username, $homepage);
my $dom = $parser->load_html(string => $response->decoded_content);
my @nodes = $dom->findnodes('//*[@rel="author"]');
if (@nodes) {
my $node = shift @nodes;
$username = $node->textContent;
$homepage = $node->getAttribute('href');
}
print "Webmention from " . join(", ", $username, $homepage) . "\n"
if $username or $homepage;
print "Getting $to\n";
$response = $ua->get($to);
if (!$response->is_success) {
die $response->status_line;
}
print "Parsing $to\n";
$dom = $parser->load_html(string => $response->decoded_content);
my $webmention = $dom->findvalue('//link[@rel="webmention"]/@href');
if (!$webmention) {
die "Webmention URL not found in $to\n";
}
print "Webmention URL is $webmention\n";
$response = $ua->post($webmention, { source => $from, target => $to });
my $message = $response->code . " " . $response->message . "\n";
if ($response->is_success) {
print $message;
} else {
die $message;
}

Some files were not shown because too many files have changed in this diff Show More