Skip to content
Browse files
Merge branch 'master' into fix-wordpress-plugin-dep-writing
  • Loading branch information
felixfontein committed Nov 20, 2016
2 parents df8b296 + 56d7165 commit 0fb39e26213fbd68097ad873b60468104f58566c
@@ -39,6 +39,7 @@ def set_site(self, site):
directives.register_directive('book_figure', BookFigure)
return super(Plugin, self).set_site(site)

CODE_IMAGE = (u"""<div class="book-figure-media">
<a class="book-figure-image" href="{url}" target="_blank">
<img src="{image_url}" alt="{title}" />
@@ -255,6 +255,7 @@ def import_link(self, post):
'hidetitle': not post['title']

<div class="link"><a href="${url}">${title}</a></div>
<div class="description">
@@ -39,6 +39,7 @@ def set_site(self, site):
directives.register_directive('link_figure', LinkFigure)
return super(Plugin, self).set_site(site)

CODE_URL_BASIC = (u"""<a class="{classes}"
@@ -1,3 +1,50 @@
This very simple plugin throws all the stories to the navigation bar.

Proof-of-concept of a ConfigPlugin. By request on the mailing list.
Started as a Proof-of-concept of a ConfigPlugin. By request on the mailing list.

Changed to map the stories/pages to a hierachical structure in the menu
based on the permalink structure (defaults to the directory structure
of the posts).

This plugin generates menus and one level of submenus (further sub-levels are mapped to first level submenus).
WARNING: Support for submenus is theme-dependent.

The menu entries inserted by navstories are inserted after entries from `NAVIGATION_LINKS`.
Entries listed in `NAVIGATION_LINKS_POST_NAVSTORIES` are inserted after navstories entries.


To include stories in the menu the permalink for the story must start with one of the strings listed in
`NAVSTORIES_PATHS`, other stories should be ignored by this pluging.

Sorting and display names in menu can be controlled for top-level entries via `NAVSTORIES_MAPPING`.

# Paths (permalink) that should be processed by navstories plugin (path starting with /<variable>/, <variable> can contain /, e.g.: stories/b
# Mapping "Toplevel in permalink" to "Visible text"
# The order is as listed here, entries not listed here are included in the end, with the top level of the permalink as text
# example (remove initial #):
#("b", "Boo"),
#("f", "Foo"),
# Indention for each level deeper in a submenu, than the highest level in that submenu, the submenu is flat, so it is only the menu text there are indented
# Static menu after dynamic navstories menu entries
# Format just as NAVIGATION_LINKS, but content included after navstories entries

To exclude a single story from the navstories meny add the following
metadata `.. hidefromnav: yes` to the story.

@@ -7,7 +7,7 @@ plugincategory = ConfigPlugin
MinVersion = 7.1.0+

Author = Chris Warrick
Version = 0.1.0
Author = Chris Warrick, Benny Simonsen
Version = 1.0.1
Website =
Description = Add all stories to the navigation bar.
Description = Add all stories in specified locations to the navigation bar.
@@ -26,6 +26,10 @@

from __future__ import unicode_literals
from nikola.plugin_categories import ConfigPlugin
from nikola import utils
from nikola.utils import LOGGER

import re

class NavStories(ConfigPlugin):
@@ -34,16 +38,149 @@ class NavStories(ConfigPlugin):
name = 'navstories'
dates = {}


# Indention for each level deeper in a submenu, than the highest level in that submenu
# overwriiten by site.config['NAVSTORIES_SUBMENU_INDENTION'] if defined
navstories_submenu_indention = '* '

class NavNode():
Class containing parameters for a menu entry

navpath = []
permalink = ''
title = ''

def __init__(self, navpath, permalink, title):
self.navpath = navpath
self.permalink = permalink
self.title = title

def map_to_menu(self, entries):
Map form list of pages going into menu to tuple of format as NAVIGATION_LINKS and NAVIGATION_LINKS_POST_NAVSTORIES
List format:
- List of "top level entry"
- List
- Top level Menu text, or None if auto-mapped, i.e. not in NAVSTORIES_MAPPING
- List of pages in the top menu entry
- List of:
- navpath: List containing navigation hierarchy (permalink without langinfo (initial /en/) and without the NAVSTORIES_PATHS (e.g. /pages/))
- Permalink
- Page title
[ [ 'Menu text for nav',
[ NavNode instance, # E.g.: .navpath=['nav'], .permalink='/pages/nav/', .title='nav/'
NavNode instance, # E.g.: .navpath=['nav', 'p2', 'a'], .permalink='/pages/nav/p2/a/', .title='Page 2a'
NavNode instance, # E.g.: .navpath=['nav', 'P2', 'b'], .permalink='/pages/nav/P2/b/', .title='Page 2b'
NavNode instance, # E.g.: .navpath=['nav', 'P2', 'a'], .permalink='/pages/nav/P2/a/', .title='Page 2a'
NavNode instance, # E.g.: .navpath=['nav', 'p2'], .permalink='/pages/nav/p2/', .title='Page 2',
NavNode instance, # E.g.: .navpath=['nav', 'p1'], .permalink='/pages/nav/p1/', .title='Side 1'
[ 'Menu text for A',
[ NavNode instance, # E.g.: .navpath=['A', 'last'], .permalink='/pages/A/last/', .title='Page title for A/last'
[ None,
[ NavNode instance, # E.g.: .navpath=['B', 'cde'], .permalink='/pages/B/cde/', .title='Page title for B/cde'
ret = []
for title, navnodes in entries:
# Determine toplevel menu name
# - If not None, use the name
# - If None, use title of page if page with navpath length=1 exist, else navpath[0]
if not title:
# Default is 1th level of navpath
title = navnodes[0].navpath[0]
# Search for toplevel page (navpath length = 1)
for n in navnodes:
if len(n.navpath) == 1:
title = n.title # Page Title
if len(navnodes) == 1 and len(navnodes[0].navpath) == 1:
# Only one menu item and it is not a subpage, let the item go direct to top level menu
ret.append(tuple([navnodes[0].permalink, title]))
sub = []
# Find min depth in actual submenu
min_depth = min(len(n.navpath) for n in navnodes)
# Map pages to submenu
for n in sorted(navnodes, key=lambda n: n.permalink):
# Sort by permalink in page list
prefix = self.navstories_submenu_indention * (len(n.navpath) - min_depth)
sub.append(tuple([n.permalink, prefix + n.title]))
ret.append(tuple([tuple(sub), title]))
return tuple(ret)

def set_site(self, site):
Map navstories config to nav_config[*] as TranslatableSettings

# Read NAVSTORIES_SUBMENU_INDENTION and store in self.navstories_submenu_indention
self.navstories_submenu_indention = site.config['NAVSTORIES_SUBMENU_INDENTION']

nav_config = {}
for i in self.conf_vars:
# Read config variables in a try...except in case a variable is missing
nav_config[i] = utils.TranslatableSetting(i, site.config[i], site.config['TRANSLATIONS'])
except KeyError:
# Initialize to "empty" in case config variable i is missing
nav_config[i] = utils.TranslatableSetting(i, {}, site.config['TRANSLATIONS'])

# NAVIGATION_LINKS is a TranslatableSetting, values is an actual dict
for lang in site.config['NAVIGATION_LINKS'].values:
# navstories config for lang
nav_conf_lang = {}
for i in self.conf_vars:
nav_conf_lang[i] = nav_config[i](lang)

# Which paths are navstories active for current lang? - Must start and end with /
paths = tuple(('/' + s.strip('/') + '/') for s in nav_conf_lang['NAVSTORIES_PATHS'])

# Unsorted (raw) new entries, deleted as mapped to new
new_raw = {}
# Sorted entries as a list of top-level menu entries, later
new = []
# Map site pages to new_raw structure
for p in site.pages:
# Generate navpath (menu) based on permalink without language prefix
# If TRANSLATION[DEFAULT_LANG] = '', then "permalink_nolang = p.permalink()" is ok
permalink_nolang = re.sub(r'^/' + nav_conf_lang['TRANSLATIONS'].lstrip('./') + '/?', '/', p.permalink(lang))
s_candidates = [s for s in paths if permalink_nolang.startswith(s)]
if not s_candidates:
# get longest path
s = max(s_candidates, key=len)
# Strip off the longest path in paths
navpath = permalink_nolang[len(s):].strip('/').split('/')
if len(navpath) == 0:
# Should not happen that navpath is empty, but to prevent errors, and inform via a warning
LOGGER.warn("Page with permalink: '%s', title: '%s', not added to menu by navstories." % (p.permalink(lang), p.title(lang)))
if lang in p.translated_to and not p.meta('hidefromnav'):
new_entries = tuple(sorted([(p.permalink(lang), p.title(lang)) for p in new]))
# Add entry
if not navpath[0] in new_raw:
new_raw[navpath[0]] = []
new_raw[navpath[0]].append(self.NavNode(navpath, p.permalink(lang), p.title(lang)))

# Map from new_raw to new, sorting by NAVSTORIES_MAPPING
for map_key, map_txt in nav_conf_lang['NAVSTORIES_MAPPING']:
# Loop over all new_raw entries, checking if it matches map_key; if match: add it and delete from new_raw
if map_key in new_raw:
new.append([map_txt, new_raw[map_key]])
# Add remaing new_raw entries which didn't match any map_key
new.extend([[None, new_raw[_]] for _ in sorted(new_raw)])

old_entries = site.config['NAVIGATION_LINKS'].values[lang]
site.config['NAVIGATION_LINKS'].values[lang] = old_entries + new_entries
# Map to tuple
new_entries = self.map_to_menu(new)
old_entries = site.config['NAVIGATION_LINKS'](lang)
# Update NAVIGATION_LINKS with navstories dynamically generated entries and NAVIGATION_LINKS_POST_NAVSTORIES entries
site.config['NAVIGATION_LINKS'].values[lang] = old_entries + new_entries + nav_conf_lang['NAVIGATION_LINKS_POST_NAVSTORIES']
super(NavStories, self).set_site(site)
@@ -56,6 +56,7 @@ def set_site(self, site):
PyPlot.out_dir = os.path.join(site.config['OUTPUT_FOLDER'], 'pyplots')
return super(Plugin, self).set_site(site)

pyplot_spec = images.Image.option_spec
pyplot_spec['include-source'] = directives.flag

@@ -69,7 +69,6 @@ def gen_tasks(self):
@@ -122,6 +122,7 @@ def pep_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
rn += sn
return [rn], []

explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)

@@ -180,6 +181,7 @@ def rfc_role(name, rawtext, text, lineno, inliner, options={}, content=[]):

_litvar_re = re.compile('{([^}]+)}')

@@ -198,6 +200,7 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner,
retnode += nodes.Text(text[pos:], text[pos:])
return [retnode], []

_amp_re = re.compile(r'(?<!&)&(?![&\s])')

@@ -253,6 +256,7 @@ def set_source_info(directive, node):
node.source, node.line = \

# FIXME: needs translations
versionlabels = {
'versionadded': 'New in version %s',
@@ -494,6 +498,7 @@ def unknown_visit(self, node):
pnode['classes'] = ['reference']
return [pnode], msg_list

_abbr_re = re.compile('\((.*)\)$', re.S)

0 comments on commit 0fb39e2

Please sign in to comment.