forked from github/kensanata.oddmuse
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
305 lines
10 KiB
Perl
305 lines
10 KiB
Perl
#!/usr/bin/env perl
|
|
use strict;
|
|
use v5.10;
|
|
|
|
# ====================[ crossbar.pl ]====================
|
|
|
|
=head1 NAME
|
|
|
|
crossbar - An Oddmuse module for adding a site-wide footer, header, or other
|
|
summary markup to all Oddmuse Wiki pages.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
crossbar is a drop-in substitute for the Sidebar module, which, as it is not
|
|
entirely "backwards compatible" with the Sidebar module, is provided as a
|
|
separate module and not revision of that module.
|
|
|
|
crossbar provides additional functionality, including:
|
|
|
|
=over
|
|
|
|
=item Support for the Table of Contents and Footnotes modules. (The Sidebar
|
|
module does not support these modules.)
|
|
|
|
=item Support for displaying the crossbar anywhere in a page. (The Sidebar
|
|
module does not permit the sidebar to be displayed anywhere except
|
|
immediately after the header div and before the content div.)
|
|
|
|
=back
|
|
|
|
And so on.
|
|
|
|
=head1 INSTALLATION
|
|
|
|
crossbar is easily installable; move this file into the B<wiki/modules/>
|
|
directory for your Oddmuse Wiki.
|
|
|
|
=cut
|
|
AddModuleDescription('crossbar.pl', 'Crossbar Extension');
|
|
|
|
our ($q, $bol, $OpenPageName, @HtmlStack, @MyInitVariables, @MyRules, %AdminPages, $DeletedPage, $SidebarName, $TocIsApplyingAutomaticRules);
|
|
|
|
# ....................{ CONFIGURATION }....................
|
|
our ($CrossbarPageName,
|
|
$CrossbarDivIsOutsideContentDiv,
|
|
$CrossbarSubstitutionPattern);
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
crossbar is easily configurable; set these variables in the B<wiki/config.pl>
|
|
file for your Oddmuse Wiki.
|
|
|
|
=cut
|
|
|
|
=head2 $CrossbarPageName
|
|
|
|
The name of the page having crossbar markup. This markup will be added,
|
|
automatically, to every Wiki page at the position matched by the
|
|
C<$CrossbarSubstitutionPattern>, below.
|
|
|
|
=cut
|
|
$CrossbarPageName = 'Crossbar';
|
|
|
|
=head2 $CrossbarDivIsOutsideContentDiv
|
|
|
|
A boolean that, if true, places the <div class="crossbar">...</div> block
|
|
"outside" the <div class="content browse">...</div> block; otherwise, this
|
|
places it inside the <div class="content browse">...</div> block. Generally,
|
|
placing the crossbar div outside the content div gives a cleaner, sensibler
|
|
aesthetic. (Your mileage may vary!)
|
|
|
|
By default, this boolean is true.
|
|
|
|
=cut
|
|
$CrossbarDivIsOutsideContentDiv = 1;
|
|
|
|
=head2 $CrossbarSubstitutionPattern
|
|
|
|
The regular expression matching the position in each page to place the crossbar
|
|
for that page. While, theoretically, this can be any pattern, it tends to be one
|
|
the following two:
|
|
|
|
=over
|
|
|
|
=item '^'. This places the sidebar for each page immediately after that page's
|
|
header and before that page's content.
|
|
|
|
=item '$'. This places the sidebar for each page immediately after that page's
|
|
content and before that page's footer.
|
|
|
|
=back
|
|
|
|
This module uses the first regular expression, by default.
|
|
|
|
=cut
|
|
$CrossbarSubstitutionPattern = '^';
|
|
|
|
# ....................{ INITIALIZATION }....................
|
|
push(@MyInitVariables, \&CrossbarInit);
|
|
|
|
# A boolean that, if true, indicates that a crossbar has already been applied to
|
|
# this page. This prevents application of a crossbar onto pages included by this
|
|
# current page -- and, in general, protects against "reentrant recursion."
|
|
my $CrossbarIsApplied;
|
|
|
|
sub CrossbarInit {
|
|
$CrossbarIsApplied = '';
|
|
$CrossbarPageName = FreeToNormal($CrossbarPageName); # spaces to underscores
|
|
|
|
# Add a link to the crossbar page to the "Administration" page.
|
|
$AdminPages{$CrossbarPageName} = 1;
|
|
|
|
# If pulling the crossbar div outside the content div, we redefine the
|
|
# default PrintPageContent() function to do this.
|
|
if ($CrossbarDivIsOutsideContentDiv) {
|
|
*PrintPageContentCrossbarOld = \&PrintPageContent;
|
|
*PrintPageContent = \&PrintPageContentCrossbar;
|
|
}
|
|
|
|
# If this user is an authenticated administrator, forcefully clear the page
|
|
# cache whenever saving the crossbar page.
|
|
if (UserIsAdmin()) {
|
|
*SaveCrossbarOld = \&Save;
|
|
*Save = \&SaveCrossbar;
|
|
}
|
|
|
|
# If the Table of Contents module is also installed, we must prevent handling
|
|
# of any Table of Contents-specific code when in the
|
|
# '<div class="crossbar">...</div>' block. Why? Because: Table of Contents-
|
|
# specific code adds unique identifiers to HTML headers, Crossbar pages may
|
|
# contain HTML headers, and those HTML headers should not have unique
|
|
# identifiers added to them, since adding unique identifiers to Crossbar page
|
|
# headers would add those headers to the Table of Contents for //every// page.
|
|
# (Trust us on this one...)
|
|
if (defined &RunMyRulesToc) {
|
|
*RunMyRulesCrossbarOld = \&RunMyRules;
|
|
*RunMyRules = \&RunMyRulesCrossbar;
|
|
}
|
|
}
|
|
|
|
# ....................{ MARKUP =before }....................
|
|
*OldCrossbarApplyRules = \&ApplyRules;
|
|
*ApplyRules = \&NewCrossbarApplyRules;
|
|
|
|
sub NewCrossbarApplyRules {
|
|
my $text = shift;
|
|
if (not $CrossbarIsApplied
|
|
and not TextIsFile($text)
|
|
and (not $SidebarName or $OpenPageName ne $SidebarName)) {
|
|
my $crossbar_markup = GetPageContent($CrossbarPageName);
|
|
if ($crossbar_markup and $crossbar_markup !~ m~^(\s*$|$DeletedPage)~) {
|
|
$CrossbarIsApplied = 1;
|
|
$text =~ s~$CrossbarSubstitutionPattern~
|
|
"\n\n<crossbar>\n\n".QuoteHtml($crossbar_markup).
|
|
"\n\n</crossbar>\n\n"~e;
|
|
}
|
|
}
|
|
return OldCrossbarApplyRules($text, @_);
|
|
}
|
|
|
|
# ....................{ MARKUP }....................
|
|
push(@MyRules, \&CrossbarRule);
|
|
SetHtmlEnvironmentContainer('div', '^class="crossbar"$');
|
|
|
|
sub CrossbarRule {
|
|
if ($bol) {
|
|
if ( m~\G\<crossbar\>~cg) {
|
|
return ($HtmlStack[0] eq 'p' ? CloseHtmlEnvironment() : '')
|
|
.AddHtmlEnvironment ('div', 'class="crossbar"');
|
|
}
|
|
elsif (m~\G\</crossbar\>~cg) {
|
|
return
|
|
CloseHtmlEnvironment('div', '^class="crossbar"$')
|
|
# If pulling the crossbar div outside the content div, we mark the point
|
|
# immediately after the close of the crossbar div with an HTML comment;
|
|
# this allows us to match the contents of the div with a clean regular
|
|
# expression. (A bit complicated, that one...)
|
|
.($CrossbarDivIsOutsideContentDiv ? '<!-- crossbar/-->' : '');
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
=head2 RunMyRulesCrossbar
|
|
|
|
Redefines the Table of Contents module's C<RunMyRulesToc> function, when that
|
|
module is installed, so as to apply a hacky, Crossbar-specific fix.
|
|
|
|
=cut
|
|
sub RunMyRulesCrossbar {
|
|
my $TocIsApplyingAutomaticRulesOld = $TocIsApplyingAutomaticRules;
|
|
$TocIsApplyingAutomaticRules = '' if InElement('div', '^class="crossbar"$');
|
|
|
|
my $html = RunMyRulesCrossbarOld(@_);
|
|
|
|
$TocIsApplyingAutomaticRules = $TocIsApplyingAutomaticRulesOld;
|
|
return $html;
|
|
}
|
|
|
|
# ....................{ BROWSING }....................
|
|
|
|
=head2 PrintPageContentCrossbar
|
|
|
|
Redefines the default C<PrintPageContent> function so as to extract the
|
|
crossbar "<div...>" outside the content "<div...>", when so desired.
|
|
|
|
=cut
|
|
sub PrintPageContentCrossbar {
|
|
my $html = '';
|
|
my $crossbar_pattern = '(<div class="crossbar">.*?</div>)<!-- crossbar/-->';
|
|
|
|
{ local *STDOUT;
|
|
open( STDOUT, '>', \$html) or die "Can't open memory file: $!";
|
|
PrintPageContentCrossbarOld(@_);
|
|
close STDOUT; }
|
|
|
|
# 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" 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 =~
|
|
s~$crossbar_pattern(.*?<div class="wrapper close"></div></div>)~$2$1~;
|
|
}
|
|
|
|
print $html;
|
|
}
|
|
|
|
# ....................{ EDITING }....................
|
|
*GetEditFormCrossbarOld = \&GetEditForm;
|
|
*GetEditForm = \&GetEditFormCrossbar;
|
|
|
|
sub GetEditFormCrossbar {
|
|
my ($page_name) = @_;
|
|
return
|
|
($page_name eq $CrossbarPageName ?
|
|
$q->p({-class=> 'crossbar_edit_message'},
|
|
T(UserIsAdmin()
|
|
? '<strong>You are currently logged in as an administrator.</strong> '
|
|
.'Saving this page propagates your crossbar changes to '
|
|
.'<em>all</em> other pages by forcefully clearing this Wiki\'s page cache.'
|
|
: '<strong>You are not currently logged in as an administrator.</strong> '
|
|
.'Saving this page only propagates your crossbar changes to <em>newly '
|
|
.'created </em> or <em>edited</em> pages — but don\'t let that deter you!')) : '')
|
|
.GetEditFormCrossbarOld(@_);
|
|
}
|
|
|
|
# ....................{ SAVING }....................
|
|
|
|
=head2 SaveCrossbar
|
|
|
|
Clears the page cache whenever a user saves the crossbar page. Why?
|
|
Because the the contents of the crossbar page is injected into every
|
|
other page. Consequently, when the crossbar page changes, the contents
|
|
of other pages are also changed; and must have their caches forcefully
|
|
cleared, to ensure they are changed.
|
|
|
|
=cut
|
|
sub SaveCrossbar {
|
|
my ($page_name) = @_;
|
|
SaveCrossbarOld(@_);
|
|
if ($page_name eq $CrossbarPageName) {
|
|
# Prevent the RequestLockOrError() and ReleaseLock() functions from doing
|
|
# anything while in the DoClearCache() method, since the default Save()
|
|
# function already obtains the lock. (We can't obtain it twice!)
|
|
*RequestLockOrErrorCrossbarOld = \&RequestLockOrError;
|
|
*RequestLockOrError = \&RequestLockOrErrorCrossbarNoop;
|
|
*ReleaseLockCrossbarOld = \&ReleaseLock;
|
|
*ReleaseLock = \&ReleaseLockCrossbarNoop;
|
|
|
|
# Clear the page cache, now. Go! (Note: this prints a heap of HTML.)
|
|
DoClearCache();
|
|
|
|
# Restore locking functionality.
|
|
*RequestLockOrError = \&RequestLockOrErrorCrossbarOld;
|
|
*ReleaseLock = \&ReleaseLockCrossbarOld;
|
|
}
|
|
}
|
|
|
|
sub RequestLockOrErrorCrossbarNoop { }
|
|
sub ReleaseLockCrossbarNoop { }
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
The information below applies to everything in this distribution,
|
|
except where noted.
|
|
|
|
Copyleft 2008 by B.w.Curry <http://www.raiazome.com>.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see L<http://www.gnu.org/licenses/>.
|
|
|
|
=cut
|