# Copyright 2013-2014 Stefan Goebel.
#
# This file is part of Newcomen.
#
# Newcomen 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.
#
# Newcomen 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 Newcomen. If not, see
# .
package Newcomen::Plugin::Blog::Menu;
our $VERSION = 2014052501;
use namespace::autoclean;
use Moose '-meta_name' => '_moose_meta';
use MooseX::StrictConstructor;
use Newcomen::Plugin::Blog::Menu::Item;
use Newcomen::Util::String;
extends 'Newcomen::Plugin::Base';
with 'Newcomen::Role::Attribute::Config';
override '_build_default_config' => sub {{
'blog' => {
'menu' => {},
}
}};
has 'menu' => (
'is' => 'ro',
'isa' => 'Newcomen::Plugin::Blog::Menu::Item',
'init_arg' => undef,
'default' => sub { Newcomen::Plugin::Blog::Menu::Item -> new ('id' => 'root') },
);
sub _process_path {
my $self = shift;
my $path = shift;
return $path =~ /^\d+_(.+)/a ? ($path, $1) : (undef, $path);
}
sub _process_item {
my $self = shift;
my $path = shift;
my $data = shift;
my @parts = Newcomen::Util::String::split_ssv ($path);
my $last = pop @parts;
my $root = $self -> menu ();
for my $part (@parts) {
my ($order, $id) = $self -> _process_path ($part);
my $child = $root -> child ($id);
unless ($child) {
$child = Newcomen::Plugin::Blog::Menu::Item -> new (
'id' => $id,
'order' => $order,
'parent' => $root,
);
$child -> set (['text' ], $id);
$child -> set (['title'], $id);
$child -> set (['auto' ], 1 );
}
$root = $child;
}
my ($order, $id) = $self -> _process_path ($last);
my $child = $root -> child ($data -> {'id'} // $id);
if (not $child or $child -> get (['auto'])) {
$child = Newcomen::Plugin::Blog::Menu::Item -> new (
'id' => $data -> {'id'} // $id,
'parent' => $root,
) unless $child;
$child -> order ($data -> {'order'} // $order);
$child -> url ($data -> {'url' } // undef );
$child -> set (['text' ], $data -> {'text' } // $child -> id ());
$child -> set (['title'], $data -> {'title'} // $child -> id ());
$child -> set (['anchor'], $data -> {'anchor'}) if $data -> {'anchor'};
$child -> set (['source'], $data -> {'source'}) if $data -> {'source'};
$child -> delete (['auto']);
while (my ($key, $value) = each %$data) {
next if $key =~ /^(?:id|order|url|text|title|anchor|source)$/;
$child -> set ($key, $value);
}
}
}
sub hook_init {
my $self = shift;
my $src = shift;
my $menu = $self -> _config () -> get (['blog', 'menu']) // {};
$self -> _core () -> set (['blog', 'menu'], $self -> menu ());
while (my ($path, $data) = each %$menu) {
$self -> _process_item ($path, $data);
}
}
sub hook_process_source {
my $self = shift;
my $src = shift;
my $menu = $src -> get (['menu']);
return 1 unless $menu;
$menu = [$menu] if ref $menu eq '';
if (ref $menu eq 'HASH') {
while (my ($path, $data) = each %$menu) {
$self -> _process_item ($path, { %$data, 'source' => $src });
}
}
elsif (ref $menu eq 'ARRAY') {
for my $item (@$menu) {
my ($anchor, $path) = $item =~ /^\+(\w+)\s+(.+)$/a ? ('#' . $1, $2) : (undef, $item);
$self -> _process_item ($path, { 'anchor' => $anchor, 'source' => $src });
}
}
return 1;
}
sub hook_post_build_pages {
my $self = shift;
$self -> _set_urls ($self -> menu ());
}
sub _set_urls {
my $self = shift;
my $root = shift;
unless ($root -> url ()) {
if (my $src = $root -> get (['source'])) {
if (my $url = $src -> get (['single_url'])) {
$root -> url ($url);
}
}
}
for my $child ($root -> children ()) {
$self -> _set_urls ($child);
}
}
sub hook_renderer {
my $self = shift;
my $page = shift;
$self -> menu () -> activate ($page -> target ());
}
__PACKAGE__ -> _moose_meta () -> make_immutable ();
1;
__END__
####################################################################################################
=head1 NAME
Newcomen::Plugin::Blog::Menu - Manages a site menu.
=head1 DESCRIPTION
This plugin allows the user to define a menu structure, which can then be used in the templates to
display a menu.
Menu items can be defined in the configuration file, as well as in the article sources' meta data.
See L and L for details. The menu items defined in the
user configuration will be processed first, menu items defined in article sources will be processed
after that.
The menu structure is stored using L instances, please see the
documentation of this module for more details on the instance attributes, methods etc.
Before a page is rendered, the L method of
the root menu item will be called with the page target as parameter, so for a page that is included
in the menu the I attributes of the items will be set appropriately.
=head1 OPTIONS
{
'blog' => {
'menu' => {},
},
}
These are the default options set by this plugin. They may be overridden by user configuration.
The I option must be a hashref. The keys specify menu item paths, the values must
themselves be hashrefs, containing the data for the menu item to be created.
=head2 Path Processing
The path of a menu item specifies the item's position in the menu hierarchy. The path consists of
one or more menu IDs, separated by slashes (C<'/'>). The menu IDs uniquely identify a menu item at
a specific level in the menu hierarchy. Intermediate items in the hierarchy will automatically be
created if required. The properties of these intermediate items will be derived from the path. The
path is split at the separator, resulting in one or more path components, each one relating to one
menu item in the menu hierarchy.
In general, a path component specifies the menu ID for the item. If this string starts with at least
one digit, followed by an underscore, followed by at least one other character, the original value
will be used as the I property of the menu item, and the ID will be the original value with
the leading digit(s) and underscore removed. If a path component does not start with the
aforementioned pattern, the order value of the item will be set to C. In any case, the menu
item's meta data I and I will be set to the same value as the ID for intermediate items
(see below on details how to override these for the leafs). For automatically created intermediate
items, the meta data will also include the key I, with its value set to C<1>.
=head2 Menu Item Data
The final element in the path (the leaf) specifies the actual menu item for which the hashref
contains the menu data. The hashref may include any of the following keys:
=over
=item *
I, to specify the ID. If this is not set, the value from the path (as described above) will be
used.
=item *
I, to specify an order value. If this is not set, the value will be derived from the path as
described above.
=item *
I and I will be copied to the item's meta data (under the same keys). If these are not
set, they will be set to the same value as the item's ID.
=item *
I and I may be used to specify the URL and an anchor on the target page. If the anchor
is set, it should include the leading C<'#'> character. Note that the anchor is stored in the item's
meta data, while the URL is stored in its own attribute of the L
instance. Both are optional.
=item *
Any other key not mentioned above will simply be copied to the menu item's meta data.
=back
=head2 Overriding Menu Items
Once a menu item has been created, it can not be overridden, with the exception of automatically
created (intermediate) items. More precisely, any item with the I meta data set to a true
value can be overridden. Any such item will only be overridden by explicitly creating it, i.e. it
will not be changed if it appears again as an intermediate item. If any such item is overridden, the
I flag will be deleted from the meta data (before copying the new meta data, so if this new
meta data should contain another I value, this new value will be preserved).
=head2 Example
The following is an example of a valid menu configuration. Note that not all data provided in the
example is required, and some data may be derived from the path as described above.
{
'blog' => {
'menu' => {
'999_Meta/About' => {
'id' => 'about',
'url' => '/about.html',
'text' => 'About This Site',
'title' => 'About',
'anchor' => '#top',
'order' => '500_About',
'color' => 'red',
}
},
},
}
In this example the path is set to C<'999_Meta/About'>.
One intermediate menu item is created for the C<'999_Meta'> path automatically as described above,
unless it already exists. Its ID (as well as its I and I meta data) will be set to
C<'Meta'>, and its I attribute to C<'999_Meta'>.
The actual leaf menu item will be created as a child of the C<'Meta'> item. Its properties would be
derived from the path, if they were not specified in the item's hashref. See the section
L