aboutsummaryrefslogtreecommitdiffstats
path: root/nikola
diff options
context:
space:
mode:
Diffstat (limited to 'nikola')
-rw-r--r--nikola/__init__.py2
-rw-r--r--nikola/__main__.py5
-rw-r--r--nikola/conf.py.in39
-rw-r--r--nikola/data/samplesite/files/images/biohazard.pngbin179 -> 0 bytes
-rw-r--r--nikola/data/samplesite/files/images/nikola.pngbin0 -> 1750 bytes
-rw-r--r--nikola/data/samplesite/stories/quickref.rst15
-rw-r--r--nikola/data/samplesite/stories/quickstart.rst8
-rw-r--r--nikola/data/themes/base-jinja/templates/base.tmpl1
-rw-r--r--nikola/data/themes/base-jinja/templates/base_header.tmpl2
-rw-r--r--nikola/data/themes/base-jinja/templates/base_helper.tmpl9
-rw-r--r--nikola/data/themes/base-jinja/templates/post.tmpl11
-rw-r--r--nikola/data/themes/base-jinja/templates/post_helper.tmpl44
-rw-r--r--nikola/data/themes/base-jinja/templates/story.tmpl17
-rw-r--r--nikola/data/themes/base/assets/css/rst.css8
-rw-r--r--nikola/data/themes/base/assets/css/theme.css30
-rw-r--r--nikola/data/themes/base/messages/messages_ar.py34
-rw-r--r--nikola/data/themes/base/messages/messages_bg.py1
-rw-r--r--nikola/data/themes/base/messages/messages_ca.py1
-rw-r--r--nikola/data/themes/base/messages/messages_cs.py15
-rw-r--r--nikola/data/themes/base/messages/messages_da.py34
-rw-r--r--nikola/data/themes/base/messages/messages_de.py5
-rw-r--r--nikola/data/themes/base/messages/messages_el.py1
-rw-r--r--nikola/data/themes/base/messages/messages_en.py1
-rw-r--r--nikola/data/themes/base/messages/messages_eo.py1
-rw-r--r--nikola/data/themes/base/messages/messages_es.py1
-rw-r--r--nikola/data/themes/base/messages/messages_et.py1
-rw-r--r--nikola/data/themes/base/messages/messages_eu.py1
-rw-r--r--nikola/data/themes/base/messages/messages_fa.py1
-rw-r--r--nikola/data/themes/base/messages/messages_fi.py1
-rw-r--r--nikola/data/themes/base/messages/messages_fr.py3
-rw-r--r--nikola/data/themes/base/messages/messages_gl.py34
-rw-r--r--nikola/data/themes/base/messages/messages_hi.py1
-rw-r--r--nikola/data/themes/base/messages/messages_hr.py1
-rw-r--r--nikola/data/themes/base/messages/messages_it.py1
-rw-r--r--nikola/data/themes/base/messages/messages_ja.py1
-rw-r--r--nikola/data/themes/base/messages/messages_nb.py1
-rw-r--r--nikola/data/themes/base/messages/messages_nl.py1
-rw-r--r--nikola/data/themes/base/messages/messages_pl.py5
-rw-r--r--nikola/data/themes/base/messages/messages_pt.py34
-rw-r--r--nikola/data/themes/base/messages/messages_pt_br.py1
-rw-r--r--nikola/data/themes/base/messages/messages_ru.py1
-rw-r--r--nikola/data/themes/base/messages/messages_si_lk.py34
-rw-r--r--nikola/data/themes/base/messages/messages_sk.py3
-rw-r--r--nikola/data/themes/base/messages/messages_sl.py3
-rw-r--r--nikola/data/themes/base/messages/messages_tr.py1
-rw-r--r--nikola/data/themes/base/messages/messages_ur.py1
-rw-r--r--nikola/data/themes/base/messages/messages_zh_cn.py1
-rw-r--r--nikola/data/themes/base/templates/base.tmpl1
-rw-r--r--nikola/data/themes/base/templates/base_helper.tmpl9
-rw-r--r--nikola/data/themes/base/templates/post.tmpl11
-rw-r--r--nikola/data/themes/base/templates/post_helper.tmpl44
-rw-r--r--nikola/data/themes/base/templates/story.tmpl17
-rw-r--r--nikola/data/themes/bootstrap-jinja/templates/base.tmpl3
-rw-r--r--nikola/data/themes/bootstrap-jinja/templates/base_helper.tmpl11
-rw-r--r--nikola/data/themes/bootstrap-jinja/templates/post.tmpl11
-rw-r--r--nikola/data/themes/bootstrap/assets/css/theme.css21
-rw-r--r--nikola/data/themes/bootstrap/templates/base.tmpl3
-rw-r--r--nikola/data/themes/bootstrap/templates/base_helper.tmpl9
-rw-r--r--nikola/data/themes/bootstrap/templates/post.tmpl11
-rw-r--r--nikola/data/themes/bootstrap3-jinja/AUTHORS.txt1
-rw-r--r--nikola/data/themes/bootstrap3-jinja/README.md8
l---------nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.css.map1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.css.map1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/docs.css1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomCenter.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomLeft.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomRight.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleLeft.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleRight.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopCenter.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopLeft.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopRight.png1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/rst.css1
l---------nikola/data/themes/bootstrap3-jinja/assets/css/theme.css1
l---------nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.eot1
l---------nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.svg1
l---------nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.ttf1
l---------nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.woff1
l---------nikola/data/themes/bootstrap3-jinja/bundles1
-rw-r--r--nikola/data/themes/bootstrap3-jinja/engine1
-rw-r--r--nikola/data/themes/bootstrap3-jinja/parent1
-rw-r--r--nikola/data/themes/bootstrap3-jinja/templates/base.tmpl88
-rw-r--r--nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl165
-rw-r--r--nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl94
-rw-r--r--nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl28
-rw-r--r--nikola/data/themes/bootstrap3-jinja/templates/slides.tmpl24
-rw-r--r--nikola/data/themes/bootstrap3/README.md8
l---------nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.css.map1
l---------nikola/data/themes/bootstrap3/assets/css/bootstrap.css.map1
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/docs.css160
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomCenter.pngbin0 -> 111 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomLeft.pngbin0 -> 215 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomRight.pngbin0 -> 217 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleLeft.pngbin0 -> 108 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleRight.pngbin0 -> 108 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopCenter.pngbin0 -> 111 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopLeft.pngbin0 -> 216 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopRight.pngbin0 -> 214 bytes
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/rst.css318
-rw-r--r--nikola/data/themes/bootstrap3/assets/css/theme.css183
l---------nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.eot1
l---------nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.svg1
l---------nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.ttf1
l---------nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.woff1
-rw-r--r--nikola/data/themes/bootstrap3/bundles4
-rw-r--r--nikola/data/themes/bootstrap3/engine1
-rw-r--r--nikola/data/themes/bootstrap3/parent1
-rw-r--r--nikola/data/themes/bootstrap3/templates/base.tmpl88
-rw-r--r--nikola/data/themes/bootstrap3/templates/base_helper.tmpl165
-rw-r--r--nikola/data/themes/bootstrap3/templates/gallery.tmpl94
-rw-r--r--nikola/data/themes/bootstrap3/templates/listing.tmpl28
-rw-r--r--nikola/data/themes/bootstrap3/templates/slides.tmpl24
-rw-r--r--nikola/filters.py32
-rw-r--r--nikola/nikola.py53
-rw-r--r--nikola/plugin_categories.py2
-rw-r--r--nikola/plugins/basic_import.py8
-rw-r--r--nikola/plugins/command/auto.py9
-rw-r--r--nikola/plugins/command/bootswatch_theme.py4
-rw-r--r--nikola/plugins/command/check.py38
-rw-r--r--nikola/plugins/command/deploy.py16
-rw-r--r--nikola/plugins/command/github_deploy.py3
-rw-r--r--nikola/plugins/command/import_wordpress.py50
-rw-r--r--nikola/plugins/command/init.py4
-rw-r--r--nikola/plugins/command/install_theme.py7
-rw-r--r--nikola/plugins/command/new_post.py8
-rw-r--r--nikola/plugins/command/plugin.py11
-rw-r--r--nikola/plugins/command/serve.py13
-rw-r--r--nikola/plugins/compile/html.py11
-rw-r--r--nikola/plugins/compile/ipynb/__init__.py9
-rw-r--r--nikola/plugins/compile/markdown/__init__.py8
-rw-r--r--nikola/plugins/compile/markdown/mdx_gist.py87
-rw-r--r--nikola/plugins/compile/pandoc.py4
-rw-r--r--nikola/plugins/compile/php.py25
-rw-r--r--nikola/plugins/compile/rest/__init__.py19
-rw-r--r--nikola/plugins/compile/rest/gist.py5
-rw-r--r--nikola/plugins/compile/rest/listing.py4
-rw-r--r--nikola/plugins/compile/rest/post_list.py9
-rw-r--r--nikola/plugins/task/bundles.py10
-rw-r--r--nikola/plugins/task/copy_assets.py8
-rw-r--r--nikola/plugins/task/copy_files.py2
-rw-r--r--nikola/plugins/task/galleries.py16
-rw-r--r--nikola/plugins/task/indexes.py35
-rw-r--r--nikola/plugins/task/listings.py5
-rw-r--r--nikola/plugins/task/redirect.py5
-rw-r--r--nikola/plugins/task/robots.py10
-rw-r--r--nikola/plugins/task/sitemap/__init__.py11
-rw-r--r--nikola/plugins/task/sources.py8
-rw-r--r--nikola/plugins/task/tags.py11
-rw-r--r--nikola/plugins/template/jinja.py2
-rw-r--r--nikola/post.py73
-rw-r--r--nikola/utils.py42
151 files changed, 2411 insertions, 341 deletions
diff --git a/nikola/__init__.py b/nikola/__init__.py
index cf4d2e5..4cb5427 100644
--- a/nikola/__init__.py
+++ b/nikola/__init__.py
@@ -27,7 +27,7 @@
from __future__ import absolute_import
import os
-__version__ = "7.0.1"
+__version__ = "7.1.0"
DEBUG = bool(os.getenv('NIKOLA_DEBUG'))
from .nikola import Nikola # NOQA
diff --git a/nikola/__main__.py b/nikola/__main__.py
index 455926d..f492800 100644
--- a/nikola/__main__.py
+++ b/nikola/__main__.py
@@ -78,7 +78,6 @@ def main(args=None):
# the output of that command (the new site) in an unknown directory that is
# not the current working directory. (does not apply to `version`)
argname = args[0] if len(args) > 0 else None
- # FIXME there are import plugins in the repo, so how do we handle this?
if argname and argname not in ['init', 'version'] and not argname.startswith('import_'):
root = get_root_dir()
if root:
@@ -91,8 +90,8 @@ def main(args=None):
config = conf.__dict__
except Exception:
if os.path.exists('conf.py'):
- msg = traceback.format_exc(0).splitlines()[1]
- LOGGER.error('In conf.py line {0}: {1}'.format(sys.exc_info()[2].tb_lineno, msg))
+ msg = traceback.format_exc(0)
+ LOGGER.error('conf.py cannot be parsed.\n{0}'.format(msg))
sys.exit(1)
config = {}
diff --git a/nikola/conf.py.in b/nikola/conf.py.in
index 2f40b59..04c2098 100644
--- a/nikola/conf.py.in
+++ b/nikola/conf.py.in
@@ -59,9 +59,24 @@ TRANSLATIONS = ${TRANSLATIONS}
TRANSLATIONS_PATTERN = ${TRANSLATIONS_PATTERN}
-# Links for the sidebar / navigation bar.
-# You should provide a key-value pair for each used language.
-# (the same way you would do with a (translatable) setting.)
+# Links for the sidebar / navigation bar. (translatable)
+# This is a dict. The keys are languages, and values are tuples.
+#
+# For regular links:
+# ('http://example.com/', 'Text')
+#
+# For submenus:
+# ((
+# ('Sub 1', 'http://example.com/'),
+# ('Sub 2', 'http://example.org/'),
+# ), 'Top')
+#
+# WARNING: Support for submenus is theme-dependent.
+# Only one level of submenus is supported.
+# WARNING: Some themes, including the default Bootstrap 3 theme,
+# may present issues if the menu is too large.
+# (in bootstrap3, the navbar can grow too large and cover contents.)
+
NAVIGATION_LINKS = ${NAVIGATION_LINKS}
# Name of the theme to use.
@@ -259,11 +274,16 @@ REDIRECTIONS = ${REDIRECTIONS}
# A python callable, which will be called with the filename as
# argument.
#
-# By default, there are no filters.
+# By default, only .php files uses filters to inject PHP into
+# Nikola’s templates. All other filters must be enabled through FILTERS.
#
-# Many filters are shipped with Nikola. A list is available in the manual:
+# Many filters are shipped with Nikola. A list is available in the manual:
# <http://getnikola.com/handbook.html#post-processing-filters>
+#
+# from nikola import filters
# FILTERS = {
+# ".html": [filters.typogrify],
+# ".js": [filters.closure_compiler],
# ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
# }
@@ -424,6 +444,9 @@ COMMENT_SYSTEM_ID = ${COMMENT_SYSTEM_ID}
# ANNOTATIONS = False
# Create index.html for story folders?
+# WARNING: if a story would conflict with the index file (usually
+# caused by setting slug to `index`), the STORY_INDEX
+# will not be generated for that directory.
# STORY_INDEX = False
# Enable comments on story pages?
# COMMENTS_IN_STORIES = False
@@ -683,6 +706,12 @@ UNSLUGIFY_TITLES = True
# (defaults to 1.)
# DEMOTE_HEADERS = 1
+# If you don’t like slugified file names ([a-z0-9] and a literal dash),
+# and would prefer to use all the characters your file system allows.
+# USE WITH CARE! This is also not guaranteed to be perfect, and may
+# sometimes crash Nikola, your web server, or eat your cat.
+# USE_SLUGIFY = True
+
# You can configure the logging handlers installed as plugins or change the
# log level of the default stderr handler.
# WARNING: The stderr handler allows only the loglevels of 'INFO' and 'DEBUG'.
diff --git a/nikola/data/samplesite/files/images/biohazard.png b/nikola/data/samplesite/files/images/biohazard.png
deleted file mode 100644
index ae4629d..0000000
--- a/nikola/data/samplesite/files/images/biohazard.png
+++ /dev/null
Binary files differ
diff --git a/nikola/data/samplesite/files/images/nikola.png b/nikola/data/samplesite/files/images/nikola.png
new file mode 100644
index 0000000..71a491f
--- /dev/null
+++ b/nikola/data/samplesite/files/images/nikola.png
Binary files differ
diff --git a/nikola/data/samplesite/stories/quickref.rst b/nikola/data/samplesite/stories/quickref.rst
index 7886cd1..bf5324f 100644
--- a/nikola/data/samplesite/stories/quickref.rst
+++ b/nikola/data/samplesite/stories/quickref.rst
@@ -1242,11 +1242,11 @@
<tr valign="top">
<td><samp>For&nbsp;instance:</samp>
- <p><samp>..&nbsp;image::&nbsp;images/biohazard.png</samp>
+ <p><samp>..&nbsp;image::&nbsp;images/nikola.png</samp>
<td>
For instance:
- <p><img src="/images/biohazard.png" alt="ball1">
+ <p><img src="/images/nikola.png" alt="ball1">
</table>
<h3><a href="#contents" name="substitution-references-and-definitions"
@@ -1266,17 +1266,16 @@
<tbody>
<tr valign="top">
<td><samp>
- The&nbsp;|biohazard|&nbsp;symbol&nbsp;must&nbsp;be
- used&nbsp;on&nbsp;containers&nbsp;used&nbsp;to
- dispose&nbsp;of&nbsp;medical&nbsp;waste.</samp>
+ The&nbsp;|Nikola|&nbsp;static&nbsp;site&nbsp;generator
+ is&nbsp;named&nbsp;after&nbsp;Nikola&nbsp;Tesla.</samp>
<p><samp>
- ..&nbsp;|biohazard|&nbsp;image::&nbsp;biohazard.png</samp>
+ ..&nbsp;|Nikola|&nbsp;image::&nbsp;nikola.png</samp>
<td>
- <p>The <img src="/images/biohazard.png" align="bottom" alt="biohazard"> symbol
- must be used on containers used to dispose of medical waste.
+ <p>The <img src="/images/nikola.png" align="bottom" alt="Nikola"> static
+ site generator is named after Nikola Tesla.
</table>
diff --git a/nikola/data/samplesite/stories/quickstart.rst b/nikola/data/samplesite/stories/quickstart.rst
index 5b78807..d08b295 100644
--- a/nikola/data/samplesite/stories/quickstart.rst
+++ b/nikola/data/samplesite/stories/quickstart.rst
@@ -369,18 +369,18 @@ __ quickref.html#directives
To include an image in your document, you use the the ``image`` directive__.
For example::
- .. image:: /images/biohazard.png
+ .. image:: /images/nikola.png
results in:
-.. image:: /images/biohazard.png
+.. image:: /images/nikola.png
-The ``/images/biohazard.png`` part indicates the filename of the image
+The ``/images/nikola.png`` part indicates the filename of the image
you wish to appear in the document. There's no restriction placed on
the image (format, size etc). If the image is to appear in HTML and
you wish to supply additional information, you may::
- .. image:: /images/biohazard.png
+ .. image:: /images/nikola.png
:height: 100
:width: 200
:scale: 50
diff --git a/nikola/data/themes/base-jinja/templates/base.tmpl b/nikola/data/themes/base-jinja/templates/base.tmpl
index 2b15177..3768b9e 100644
--- a/nikola/data/themes/base-jinja/templates/base.tmpl
+++ b/nikola/data/themes/base-jinja/templates/base.tmpl
@@ -11,6 +11,7 @@
{{ template_hooks['extra_head']() }}
</head>
<body>
+<a href="#content" class="sr-only sr-only-focusable">{{ messages("Skip to main content") }}</a>
<div id="container">
{{ header.html_header() }}
<main id="content">
diff --git a/nikola/data/themes/base-jinja/templates/base_header.tmpl b/nikola/data/themes/base-jinja/templates/base_header.tmpl
index 1001db3..7947f68 100644
--- a/nikola/data/themes/base-jinja/templates/base_header.tmpl
+++ b/nikola/data/themes/base-jinja/templates/base_header.tmpl
@@ -31,7 +31,7 @@
<nav id="menu" role="navigation">
<ul>
{% for url, text in navigation_links[lang] %}
- {% if url is mapping %}
+ {% if isinstance(url, tuple) %}
<li> {{ text }}
<ul>
{% for suburl, text in url %}
diff --git a/nikola/data/themes/base-jinja/templates/base_helper.tmpl b/nikola/data/themes/base-jinja/templates/base_helper.tmpl
index 2dda87b..bd5d025 100644
--- a/nikola/data/themes/base-jinja/templates/base_helper.tmpl
+++ b/nikola/data/themes/base-jinja/templates/base_helper.tmpl
@@ -47,11 +47,18 @@ lang="{{ lang }}">
<meta property="fb:app_id" content="{{ comment_system_id }}">
{% endif %}
+ {% if prevlink %}
+ <link rel="prev" href="{{ prevlink }}" type="text/html">
+ {% endif %}
+ {% if nextlink %}
+ <link rel="next" href="{{ nextlink }}" type="text/html">
+ {% endif %}
+
{{ mathjax_config }}
{% if use_cdn %}
<!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
{% else %}
- <!--[if lt IE 9]><script src="/assets/js/html5.js"></script><![endif]-->
+ <!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
{% endif %}
{{ extra_head_data }}
diff --git a/nikola/data/themes/base-jinja/templates/post.tmpl b/nikola/data/themes/base-jinja/templates/post.tmpl
index 75c7690..e6dc97b 100644
--- a/nikola/data/themes/base-jinja/templates/post.tmpl
+++ b/nikola/data/themes/base-jinja/templates/post.tmpl
@@ -7,9 +7,18 @@
{% block extra_head %}
{{ super() }}
{% if post.meta('keywords') %}
- <meta name="keywords" content="{{ post.meta('keywords')|e }}">
+ <meta name="keywords" content="{{ post.meta('keywords')|e }}">
+ {% endif %}
+ {% if post.description() %}
+ <meta name="description" itemprop="description" content="{{ post.description() }}">
{% endif %}
<meta name="author" content="{{ post.author() }}">
+ {% if post.prev_post %}
+ <link rel="prev" href="{{ post.prev_post.permalink() }}" title="{{ post.prev_post.title() }}" type="text/html">
+ {% endif %}
+ {% if post.next_post %}
+ <link rel="next" href="{{ post.next_post.permalink() }}" title="{{ post.next_post.title() }}" type="text/html">
+ {% endif %}
{{ helper.open_graph_metadata(post) }}
{{ helper.twitter_card_information(post) }}
{{ helper.meta_translations(post) }}
diff --git a/nikola/data/themes/base-jinja/templates/post_helper.tmpl b/nikola/data/themes/base-jinja/templates/post_helper.tmpl
index c695e57..541cd31 100644
--- a/nikola/data/themes/base-jinja/templates/post_helper.tmpl
+++ b/nikola/data/themes/base-jinja/templates/post_helper.tmpl
@@ -38,33 +38,33 @@
{% endmacro %}
{% macro open_graph_metadata(post) %}
- {% if use_open_graph %}
- <meta name="og:title" content="{{ post.title()[:70]|e }}">
- <meta name="og:url" content="{{ abs_link(permalink) }}">
- {% if post.description() %}
- <meta name="og:description" content="{{ post.description()[:200]|e }}">
- {% else %}
- <meta name="og:description" content="{{ post.text(strip_html=True)[:200]|e }}">
- {% endif %}
- <meta name="og:site_name" content="{{ blog_title|e }}">
- <meta name="og:type" content="article">
+{% if use_open_graph %}
+ <meta name="og:title" content="{{ post.title()[:70]|e }}">
+ <meta name="og:url" content="{{ abs_link(permalink) }}">
+ {% if post.description() %}
+ <meta name="og:description" content="{{ post.description()[:200]|e }}">
+ {% else %}
+ <meta name="og:description" content="{{ post.text(strip_html=True)[:200]|e }}">
{% endif %}
+ <meta name="og:site_name" content="{{ blog_title|e }}">
+ <meta name="og:type" content="article">
+{% endif %}
{% endmacro %}
{% macro twitter_card_information(post) %}
- {% if twitter_card and twitter_card['use_twitter_cards'] %}
- <meta name="twitter:card" content="{{ twitter_card.get('card', 'summary')|e }}">
- {% if 'site:id' in twitter_card %}
- <meta name="twitter:site:id" content="{{ twitter_card['site:id'] }}">
- {% elif 'site' in twitter_card %}
- <meta name="twitter:site" content="{{ twitter_card['site'] }}">
- {% endif %}
- {% if 'creator:id' in twitter_card %}
- <meta name="twitter:creator:id" content="{{ twitter_card['creator:id'] }}">
- {% elif 'creator' in twitter_card %}
- <meta name="twitter:creator" content="{{ twitter_card['creator'] }}">
- {% endif %}
+{% if twitter_card and twitter_card['use_twitter_cards'] %}
+ <meta name="twitter:card" content="{{ twitter_card.get('card', 'summary')|e }}">
+ {% if 'site:id' in twitter_card %}
+ <meta name="twitter:site:id" content="{{ twitter_card['site:id'] }}">
+ {% elif 'site' in twitter_card %}
+ <meta name="twitter:site" content="{{ twitter_card['site'] }}">
+ {% endif %}
+ {% if 'creator:id' in twitter_card %}
+ <meta name="twitter:creator:id" content="{{ twitter_card['creator:id'] }}">
+ {% elif 'creator' in twitter_card %}
+ <meta name="twitter:creator" content="{{ twitter_card['creator'] }}">
{% endif %}
+{% endif %}
{% endmacro %}
{% macro mathjax_script(post) %}
diff --git a/nikola/data/themes/base-jinja/templates/story.tmpl b/nikola/data/themes/base-jinja/templates/story.tmpl
index 99caaee..11973f1 100644
--- a/nikola/data/themes/base-jinja/templates/story.tmpl
+++ b/nikola/data/themes/base-jinja/templates/story.tmpl
@@ -4,27 +4,13 @@
{% import 'comments_helper.tmpl' as comments with context %}
{% extends 'post.tmpl' %}
-{% block extra_head %}
- {{ super() }}
- {% if post.meta('keywords') %}
- <meta name="keywords" content="{{ post.meta('keywords')|e }}">
- {% endif %}
- <meta name="author" content="{{ post.author() }}">
- {{ helper.open_graph_metadata(post) }}
- {{ helper.twitter_card_information(post) }}
- {{ helper.meta_translations(post) }}
- {% if post.description() %}
- <meta name="description" itemprop="description" content="{{ post.description() }}">
- {% endif %}
-{% endblock %}
-
{% block content %}
<article class="storypage" itemscope="itemscope" itemtype="http://schema.org/Article">
<header>
{{ pheader.html_title() }}
{{ pheader.html_translations(post) }}
</header>
- <div itemprop="articleBody text">
+ <div class="e-content entry-content" itemprop="articleBody text">
{{ post.text() }}
</div>
{% if site_has_comments and enable_comments and not post.meta('nocomments') %}
@@ -33,5 +19,6 @@
{{ comments.comment_form(post.permalink(absolute=True), post.title(), post.base_path) }}
</section>
{% endif %}
+ {{ helper.mathjax_script(post) }}
</article>
{% endblock %}
diff --git a/nikola/data/themes/base/assets/css/rst.css b/nikola/data/themes/base/assets/css/rst.css
index 855b9fb..784308b 100644
--- a/nikola/data/themes/base/assets/css/rst.css
+++ b/nikola/data/themes/base/assets/css/rst.css
@@ -109,6 +109,14 @@ div.line-block div.line-block {
margin-bottom: 0 ;
margin-left: 1.5em }
+
+html[dir="rtl"] div.line-block div.line-block {
+ margin-top: 0 ;
+ margin-bottom: 0 ;
+ margin-right: 1.5em ;
+ margin-left: 0 ;
+}
+
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
diff --git a/nikola/data/themes/base/assets/css/theme.css b/nikola/data/themes/base/assets/css/theme.css
index 6fd1072..18b93db 100644
--- a/nikola/data/themes/base/assets/css/theme.css
+++ b/nikola/data/themes/base/assets/css/theme.css
@@ -157,6 +157,7 @@ body {
content: "";
}
.postpromonav .pager {
+ clear: both;
height: 1em;
}
.postpromonav .tags li,
@@ -169,6 +170,14 @@ body {
.postpromonav .pager .next:dir(rtl) {
float: left;
}
+
+.postpromonav .pager .previous {
+ float: left;
+}
+.postpromonav .pager .previous:dir(rtl) {
+ float: right;
+}
+
.metadata p {
display: inline;
}
@@ -253,3 +262,24 @@ img {
.codetable .linenos {
padding-right: 10px;
}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ clip: auto;
+}
diff --git a/nikola/data/themes/base/messages/messages_ar.py b/nikola/data/themes/base/messages/messages_ar.py
new file mode 100644
index 0000000..f7ba16a
--- /dev/null
+++ b/nikola/data/themes/base/messages/messages_ar.py
@@ -0,0 +1,34 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "%d min remaining to read": "",
+ "Also available in:": "",
+ "Archive": "",
+ "Categories": "الأصناف",
+ "Comments": "التّعليقات",
+ "LANGUAGE": "العربيّة",
+ "Languages:": "",
+ "More posts about %s": "المزيد من المقالات حول %s",
+ "Newer posts": "",
+ "Next post": "",
+ "No posts found.": "",
+ "Nothing found.": "",
+ "Older posts": "",
+ "Original site": "",
+ "Posted:": "",
+ "Posts about %s": "",
+ "Posts for year %s": "",
+ "Posts for {month} {year}": "",
+ "Previous post": "",
+ "Publication date": "",
+ "RSS feed": "",
+ "Read in English": "",
+ "Read more": "قراءة المزيد",
+ "Skip to main content": "",
+ "Source": "",
+ "Tags and Categories": "",
+ "Tags": "",
+ "old posts, page %d": "",
+ "page %d": "",
+}
diff --git a/nikola/data/themes/base/messages/messages_bg.py b/nikola/data/themes/base/messages/messages_bg.py
index 4158ac8..28adb77 100644
--- a/nikola/data/themes/base/messages/messages_bg.py
+++ b/nikola/data/themes/base/messages/messages_bg.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Прочетете на български",
"Read more": "Прочети още",
+ "Skip to main content": "",
"Source": "Source",
"Tags and Categories": "Тагове и Категории",
"Tags": "Тагове",
diff --git a/nikola/data/themes/base/messages/messages_ca.py b/nikola/data/themes/base/messages/messages_ca.py
index 7723f3e..58c8577 100644
--- a/nikola/data/themes/base/messages/messages_ca.py
+++ b/nikola/data/themes/base/messages/messages_ca.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Llegeix-ho en català",
"Read more": "Llegeix-ne més",
+ "Skip to main content": "",
"Source": "Codi",
"Tags and Categories": "",
"Tags": "Etiquetes",
diff --git a/nikola/data/themes/base/messages/messages_cs.py b/nikola/data/themes/base/messages/messages_cs.py
index f80a79f..548c6d7 100644
--- a/nikola/data/themes/base/messages/messages_cs.py
+++ b/nikola/data/themes/base/messages/messages_cs.py
@@ -2,18 +2,18 @@
from __future__ import unicode_literals
MESSAGES = {
- "%d min remaining to read": "",
+ "%d min remaining to read": "%d min zbývajících",
"Also available in:": "Dostupné také v",
"Archive": "Archiv",
"Categories": "Kategorie",
- "Comments": "",
+ "Comments": "Komentáře",
"LANGUAGE": "Čeština",
- "Languages:": "",
+ "Languages:": "Jazyky:",
"More posts about %s": "Další příspěvky o %s",
"Newer posts": "Novější příspěvky",
"Next post": "Další příspěvek",
- "No posts found.": "",
- "Nothing found.": "",
+ "No posts found.": "Nebyly nalezeny žádné příspěvky.",
+ "Nothing found.": "Nic nebylo nalezeno.",
"Older posts": "Starší příspěvky",
"Original site": "Původní stránka",
"Posted:": "Zveřejněno:",
@@ -21,10 +21,11 @@ MESSAGES = {
"Posts for year %s": "Příspěvky v roce %s",
"Posts for {month} {year}": "Příspěvky v {month} {year}",
"Previous post": "Předchozí příspěvek",
- "Publication date": "",
- "RSS feed": "",
+ "Publication date": "Datum zveřejnění",
+ "RSS feed": "RSS zdroj",
"Read in English": "Číst v češtině",
"Read more": "Číst dál",
+ "Skip to main content": "",
"Source": "Zdroj",
"Tags and Categories": "Štítky a kategorie",
"Tags": "Štítky",
diff --git a/nikola/data/themes/base/messages/messages_da.py b/nikola/data/themes/base/messages/messages_da.py
new file mode 100644
index 0000000..c5c82ee
--- /dev/null
+++ b/nikola/data/themes/base/messages/messages_da.py
@@ -0,0 +1,34 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "%d min remaining to read": "",
+ "Also available in:": "",
+ "Archive": "",
+ "Categories": "",
+ "Comments": "",
+ "LANGUAGE": "",
+ "Languages:": "",
+ "More posts about %s": "",
+ "Newer posts": "",
+ "Next post": "",
+ "No posts found.": "",
+ "Nothing found.": "",
+ "Older posts": "",
+ "Original site": "",
+ "Posted:": "",
+ "Posts about %s": "",
+ "Posts for year %s": "",
+ "Posts for {month} {year}": "",
+ "Previous post": "",
+ "Publication date": "",
+ "RSS feed": "",
+ "Read in English": "",
+ "Read more": "",
+ "Skip to main content": "",
+ "Source": "",
+ "Tags and Categories": "",
+ "Tags": "",
+ "old posts, page %d": "",
+ "page %d": "",
+}
diff --git a/nikola/data/themes/base/messages/messages_de.py b/nikola/data/themes/base/messages/messages_de.py
index 737e63b..6be6ad5 100644
--- a/nikola/data/themes/base/messages/messages_de.py
+++ b/nikola/data/themes/base/messages/messages_de.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
MESSAGES = {
- "%d min remaining to read": "",
+ "%d min remaining to read": "%d min verbleiben zum Lesen",
"Also available in:": "Auch verfügbar in:",
"Archive": "Archiv",
"Categories": "Kategorien",
@@ -25,9 +25,10 @@ MESSAGES = {
"RSS feed": "RSS-Feed",
"Read in English": "Auf Deutsch lesen",
"Read more": "Weiterlesen",
+ "Skip to main content": "",
"Source": "Source",
"Tags and Categories": "Tags und Kategorien",
"Tags": "Tags",
- "old posts, page %d": "Vorherige Einträge, Seite %d",
+ "old posts, page %d": "Ältere Einträge, Seite %d",
"page %d": "Seite %d",
}
diff --git a/nikola/data/themes/base/messages/messages_el.py b/nikola/data/themes/base/messages/messages_el.py
index aeca302..ce2fd89 100644
--- a/nikola/data/themes/base/messages/messages_el.py
+++ b/nikola/data/themes/base/messages/messages_el.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Διαβάστε στα Ελληνικά",
"Read more": "Διαβάστε περισσότερα",
+ "Skip to main content": "",
"Source": "Πηγαίος κώδικας",
"Tags and Categories": "Ετικέτες και κατηγορίες",
"Tags": "Ετικέτες",
diff --git a/nikola/data/themes/base/messages/messages_en.py b/nikola/data/themes/base/messages/messages_en.py
index bdf2d42..df04974 100644
--- a/nikola/data/themes/base/messages/messages_en.py
+++ b/nikola/data/themes/base/messages/messages_en.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS feed",
"Read in English": "Read in English",
"Read more": "Read more",
+ "Skip to main content": "Skip to main content",
"Source": "Source",
"Tags and Categories": "Tags and Categories",
"Tags": "Tags",
diff --git a/nikola/data/themes/base/messages/messages_eo.py b/nikola/data/themes/base/messages/messages_eo.py
index e439e6b..38b54e2 100644
--- a/nikola/data/themes/base/messages/messages_eo.py
+++ b/nikola/data/themes/base/messages/messages_eo.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Legu ĝin en Esperanto",
"Read more": "Legu plu",
+ "Skip to main content": "",
"Source": "Fonto",
"Tags and Categories": "Etikedoj kaj Kategorioj",
"Tags": "Etikedoj",
diff --git a/nikola/data/themes/base/messages/messages_es.py b/nikola/data/themes/base/messages/messages_es.py
index 0905f00..67de5aa 100644
--- a/nikola/data/themes/base/messages/messages_es.py
+++ b/nikola/data/themes/base/messages/messages_es.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "feed RSS",
"Read in English": "Leer en español",
"Read more": "Leer más",
+ "Skip to main content": "",
"Source": "Código",
"Tags and Categories": "Tags y Categorías",
"Tags": "Tags",
diff --git a/nikola/data/themes/base/messages/messages_et.py b/nikola/data/themes/base/messages/messages_et.py
index f473985..3a53c2f 100644
--- a/nikola/data/themes/base/messages/messages_et.py
+++ b/nikola/data/themes/base/messages/messages_et.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Loe eesti keeles",
"Read more": "Loe veel",
+ "Skip to main content": "",
"Source": "Lähtekood",
"Tags and Categories": "Sildid ja kategooriad",
"Tags": "Märksõnad",
diff --git a/nikola/data/themes/base/messages/messages_eu.py b/nikola/data/themes/base/messages/messages_eu.py
index 8958d42..6920552 100644
--- a/nikola/data/themes/base/messages/messages_eu.py
+++ b/nikola/data/themes/base/messages/messages_eu.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Euskaraz irakurri",
"Read more": "Irakurri gehiago",
+ "Skip to main content": "",
"Source": "Iturria",
"Tags and Categories": "Etiketak eta Kategoriak",
"Tags": "Etiketak",
diff --git a/nikola/data/themes/base/messages/messages_fa.py b/nikola/data/themes/base/messages/messages_fa.py
index 49cfda5..5899ec5 100644
--- a/nikola/data/themes/base/messages/messages_fa.py
+++ b/nikola/data/themes/base/messages/messages_fa.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "خوراک",
"Read in English": "به فارسی بخوانید",
"Read more": "بیشتر بخوانید",
+ "Skip to main content": "",
"Source": "منبع",
"Tags and Categories": "برچسب‌ها و دسته‌ها",
"Tags": "برچسب‌ها",
diff --git a/nikola/data/themes/base/messages/messages_fi.py b/nikola/data/themes/base/messages/messages_fi.py
index b621459..1988e3f 100644
--- a/nikola/data/themes/base/messages/messages_fi.py
+++ b/nikola/data/themes/base/messages/messages_fi.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS syöte",
"Read in English": "Lue suomeksi",
"Read more": "Lue lisää",
+ "Skip to main content": "",
"Source": "Lähde",
"Tags and Categories": "Tagit ja kategoriat",
"Tags": "Tagit",
diff --git a/nikola/data/themes/base/messages/messages_fr.py b/nikola/data/themes/base/messages/messages_fr.py
index 316ba20..a30aa1a 100644
--- a/nikola/data/themes/base/messages/messages_fr.py
+++ b/nikola/data/themes/base/messages/messages_fr.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
MESSAGES = {
- "%d min remaining to read": "",
+ "%d min remaining to read": "Il reste encore %d min. de lecture",
"Also available in:": "Egalement disponible en:",
"Archive": "Archives",
"Categories": "Catégories",
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "Flux RSS",
"Read in English": "Lire en français",
"Read more": "Lire la suite",
+ "Skip to main content": "Aller au contenu principal",
"Source": "Source",
"Tags and Categories": "Étiquettes et catégories",
"Tags": "Étiquettes",
diff --git a/nikola/data/themes/base/messages/messages_gl.py b/nikola/data/themes/base/messages/messages_gl.py
new file mode 100644
index 0000000..c5c82ee
--- /dev/null
+++ b/nikola/data/themes/base/messages/messages_gl.py
@@ -0,0 +1,34 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "%d min remaining to read": "",
+ "Also available in:": "",
+ "Archive": "",
+ "Categories": "",
+ "Comments": "",
+ "LANGUAGE": "",
+ "Languages:": "",
+ "More posts about %s": "",
+ "Newer posts": "",
+ "Next post": "",
+ "No posts found.": "",
+ "Nothing found.": "",
+ "Older posts": "",
+ "Original site": "",
+ "Posted:": "",
+ "Posts about %s": "",
+ "Posts for year %s": "",
+ "Posts for {month} {year}": "",
+ "Previous post": "",
+ "Publication date": "",
+ "RSS feed": "",
+ "Read in English": "",
+ "Read more": "",
+ "Skip to main content": "",
+ "Source": "",
+ "Tags and Categories": "",
+ "Tags": "",
+ "old posts, page %d": "",
+ "page %d": "",
+}
diff --git a/nikola/data/themes/base/messages/messages_hi.py b/nikola/data/themes/base/messages/messages_hi.py
index 6b53e01..3d69697 100644
--- a/nikola/data/themes/base/messages/messages_hi.py
+++ b/nikola/data/themes/base/messages/messages_hi.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "हिन्दी में पढ़िए",
"Read more": "और पढ़िए",
+ "Skip to main content": "",
"Source": "सोर्स",
"Tags and Categories": "टैग्स और श्रेणियाँ",
"Tags": "टैग्स",
diff --git a/nikola/data/themes/base/messages/messages_hr.py b/nikola/data/themes/base/messages/messages_hr.py
index c3343c9..f5f0886 100644
--- a/nikola/data/themes/base/messages/messages_hr.py
+++ b/nikola/data/themes/base/messages/messages_hr.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS kanal",
"Read in English": "Čitaj na hrvatskom",
"Read more": "Čitaj dalje",
+ "Skip to main content": "",
"Source": "Izvor",
"Tags and Categories": "Tagovi i kategorije",
"Tags": "Tagovi",
diff --git a/nikola/data/themes/base/messages/messages_it.py b/nikola/data/themes/base/messages/messages_it.py
index b248d34..62442d4 100644
--- a/nikola/data/themes/base/messages/messages_it.py
+++ b/nikola/data/themes/base/messages/messages_it.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "Flusso RSS",
"Read in English": "Leggi in italiano",
"Read more": "Continua la lettura",
+ "Skip to main content": "",
"Source": "Sorgente",
"Tags and Categories": "Tags e Categorie",
"Tags": "Tags",
diff --git a/nikola/data/themes/base/messages/messages_ja.py b/nikola/data/themes/base/messages/messages_ja.py
index 4b0fd54..cba5ee9 100644
--- a/nikola/data/themes/base/messages/messages_ja.py
+++ b/nikola/data/themes/base/messages/messages_ja.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS フィード",
"Read in English": "日本語で読む",
"Read more": "続きを読む",
+ "Skip to main content": "",
"Source": "ソース",
"Tags and Categories": "タグとカテゴリー",
"Tags": "タグ",
diff --git a/nikola/data/themes/base/messages/messages_nb.py b/nikola/data/themes/base/messages/messages_nb.py
index f6232df..f4d6062 100644
--- a/nikola/data/themes/base/messages/messages_nb.py
+++ b/nikola/data/themes/base/messages/messages_nb.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "Les på norsk",
"Read more": "Les mer",
+ "Skip to main content": "",
"Source": "Kilde",
"Tags and Categories": "Merker og kategorier",
"Tags": "Merker",
diff --git a/nikola/data/themes/base/messages/messages_nl.py b/nikola/data/themes/base/messages/messages_nl.py
index 7cba96b..4aa9147 100644
--- a/nikola/data/themes/base/messages/messages_nl.py
+++ b/nikola/data/themes/base/messages/messages_nl.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS-feed",
"Read in English": "Lees in het Nederlands",
"Read more": "Lees verder",
+ "Skip to main content": "Ga door naar de hoofdinhoud",
"Source": "Bron",
"Tags and Categories": "Tags en Categorieën",
"Tags": "Tags",
diff --git a/nikola/data/themes/base/messages/messages_pl.py b/nikola/data/themes/base/messages/messages_pl.py
index 6b6e48d..b1d4e82 100644
--- a/nikola/data/themes/base/messages/messages_pl.py
+++ b/nikola/data/themes/base/messages/messages_pl.py
@@ -7,7 +7,7 @@ MESSAGES = {
"Archive": "Archiwum",
"Categories": "Kategorie",
"Comments": "Komentarze",
- "LANGUAGE": "polski",
+ "LANGUAGE": "Polski",
"Languages:": "Języki:",
"More posts about %s": "Więcej postów o %s",
"Newer posts": "Nowsze posty",
@@ -25,9 +25,10 @@ MESSAGES = {
"RSS feed": "Kanał RSS",
"Read in English": "Czytaj po polsku",
"Read more": "Czytaj więcej",
+ "Skip to main content": "Przejdź do treści",
"Source": "Źródło",
"Tags and Categories": "Tagi i Kategorie",
- "Tags": "Tags",
+ "Tags": "Tagi",
"old posts, page %d": "stare posty, strona %d",
"page %d": "strona %d",
}
diff --git a/nikola/data/themes/base/messages/messages_pt.py b/nikola/data/themes/base/messages/messages_pt.py
new file mode 100644
index 0000000..c5c82ee
--- /dev/null
+++ b/nikola/data/themes/base/messages/messages_pt.py
@@ -0,0 +1,34 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "%d min remaining to read": "",
+ "Also available in:": "",
+ "Archive": "",
+ "Categories": "",
+ "Comments": "",
+ "LANGUAGE": "",
+ "Languages:": "",
+ "More posts about %s": "",
+ "Newer posts": "",
+ "Next post": "",
+ "No posts found.": "",
+ "Nothing found.": "",
+ "Older posts": "",
+ "Original site": "",
+ "Posted:": "",
+ "Posts about %s": "",
+ "Posts for year %s": "",
+ "Posts for {month} {year}": "",
+ "Previous post": "",
+ "Publication date": "",
+ "RSS feed": "",
+ "Read in English": "",
+ "Read more": "",
+ "Skip to main content": "",
+ "Source": "",
+ "Tags and Categories": "",
+ "Tags": "",
+ "old posts, page %d": "",
+ "page %d": "",
+}
diff --git a/nikola/data/themes/base/messages/messages_pt_br.py b/nikola/data/themes/base/messages/messages_pt_br.py
index c86b2f8..0805f8e 100644
--- a/nikola/data/themes/base/messages/messages_pt_br.py
+++ b/nikola/data/themes/base/messages/messages_pt_br.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "Feed RSS",
"Read in English": "Ler em português",
"Read more": "Leia mais",
+ "Skip to main content": "",
"Source": "Código",
"Tags and Categories": "Tags e Categorias",
"Tags": "Tags",
diff --git a/nikola/data/themes/base/messages/messages_ru.py b/nikola/data/themes/base/messages/messages_ru.py
index 7c038cc..7205906 100644
--- a/nikola/data/themes/base/messages/messages_ru.py
+++ b/nikola/data/themes/base/messages/messages_ru.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS лента",
"Read in English": "Прочесть по-русски",
"Read more": "Читать далее",
+ "Skip to main content": "",
"Source": "Источник",
"Tags and Categories": "Тэги и категории",
"Tags": "Тэги",
diff --git a/nikola/data/themes/base/messages/messages_si_lk.py b/nikola/data/themes/base/messages/messages_si_lk.py
new file mode 100644
index 0000000..c5c82ee
--- /dev/null
+++ b/nikola/data/themes/base/messages/messages_si_lk.py
@@ -0,0 +1,34 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "%d min remaining to read": "",
+ "Also available in:": "",
+ "Archive": "",
+ "Categories": "",
+ "Comments": "",
+ "LANGUAGE": "",
+ "Languages:": "",
+ "More posts about %s": "",
+ "Newer posts": "",
+ "Next post": "",
+ "No posts found.": "",
+ "Nothing found.": "",
+ "Older posts": "",
+ "Original site": "",
+ "Posted:": "",
+ "Posts about %s": "",
+ "Posts for year %s": "",
+ "Posts for {month} {year}": "",
+ "Previous post": "",
+ "Publication date": "",
+ "RSS feed": "",
+ "Read in English": "",
+ "Read more": "",
+ "Skip to main content": "",
+ "Source": "",
+ "Tags and Categories": "",
+ "Tags": "",
+ "old posts, page %d": "",
+ "page %d": "",
+}
diff --git a/nikola/data/themes/base/messages/messages_sk.py b/nikola/data/themes/base/messages/messages_sk.py
index 3b56a58..e3618f3 100644
--- a/nikola/data/themes/base/messages/messages_sk.py
+++ b/nikola/data/themes/base/messages/messages_sk.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
MESSAGES = {
- "%d min remaining to read": "",
+ "%d min remaining to read": "zostáva %d minút na čítanie",
"Also available in:": "Tiež dostupné v:",
"Archive": "Archív",
"Categories": "Kategórie",
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS kanál",
"Read in English": "Čítať v slovenčine",
"Read more": "Čítať ďalej",
+ "Skip to main content": "",
"Source": "Zdroj",
"Tags and Categories": "Štítky a kategórie",
"Tags": "Štítky",
diff --git a/nikola/data/themes/base/messages/messages_sl.py b/nikola/data/themes/base/messages/messages_sl.py
index 53045e3..f9f1d13 100644
--- a/nikola/data/themes/base/messages/messages_sl.py
+++ b/nikola/data/themes/base/messages/messages_sl.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
MESSAGES = {
- "%d min remaining to read": "za prebrati preostalo še %d min",
+ "%d min remaining to read": "še %d min za branje preostanka",
"Also available in:": "Na voljo tudi v:",
"Archive": "Arhiv",
"Categories": "Kategorije",
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "vir RSS",
"Read in English": "Beri v slovenščini",
"Read more": "Več o tem",
+ "Skip to main content": "Preskoči na glavno vsebino",
"Source": "Izvor",
"Tags and Categories": "Značke in kategorije",
"Tags": "Značke",
diff --git a/nikola/data/themes/base/messages/messages_tr.py b/nikola/data/themes/base/messages/messages_tr.py
index df9c4eb..3ba8217 100644
--- a/nikola/data/themes/base/messages/messages_tr.py
+++ b/nikola/data/themes/base/messages/messages_tr.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "RSS kaynağı",
"Read in English": "Türkçe olarak oku",
"Read more": "Devamını oku",
+ "Skip to main content": "",
"Source": "Kaynak",
"Tags and Categories": "Etiketler ve Kategoriler",
"Tags": "Etiketler",
diff --git a/nikola/data/themes/base/messages/messages_ur.py b/nikola/data/themes/base/messages/messages_ur.py
index 204d95f..fac2a3e 100644
--- a/nikola/data/themes/base/messages/messages_ur.py
+++ b/nikola/data/themes/base/messages/messages_ur.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "آر ایس ایس فیڈ",
"Read in English": "اردو میں پڑھیے",
"Read more": "مزید پڑھیے",
+ "Skip to main content": "",
"Source": "سورس",
"Tags and Categories": "ٹیگز اور زمرے",
"Tags": "ٹیگز",
diff --git a/nikola/data/themes/base/messages/messages_zh_cn.py b/nikola/data/themes/base/messages/messages_zh_cn.py
index 525cb45..9d36505 100644
--- a/nikola/data/themes/base/messages/messages_zh_cn.py
+++ b/nikola/data/themes/base/messages/messages_zh_cn.py
@@ -25,6 +25,7 @@ MESSAGES = {
"RSS feed": "",
"Read in English": "中文版",
"Read more": "更多",
+ "Skip to main content": "",
"Source": "源代码",
"Tags and Categories": "标签和分类",
"Tags": "标签",
diff --git a/nikola/data/themes/base/templates/base.tmpl b/nikola/data/themes/base/templates/base.tmpl
index f587593..21f5ad5 100644
--- a/nikola/data/themes/base/templates/base.tmpl
+++ b/nikola/data/themes/base/templates/base.tmpl
@@ -11,6 +11,7 @@ ${base.html_headstart()}
${template_hooks['extra_head']()}
</head>
<body>
+<a href="#content" class="sr-only sr-only-focusable">${messages("Skip to main content")}</a>
<div id="container">
${header.html_header()}
<main id="content">
diff --git a/nikola/data/themes/base/templates/base_helper.tmpl b/nikola/data/themes/base/templates/base_helper.tmpl
index beeff99..491b6da 100644
--- a/nikola/data/themes/base/templates/base_helper.tmpl
+++ b/nikola/data/themes/base/templates/base_helper.tmpl
@@ -47,11 +47,18 @@ lang="${lang}">
<meta property="fb:app_id" content="${comment_system_id}">
% endif
+ %if prevlink:
+ <link rel="prev" href="${prevlink}" type="text/html">
+ %endif
+ %if nextlink:
+ <link rel="next" href="${nextlink}" type="text/html">
+ %endif
+
${mathjax_config}
%if use_cdn:
<!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
%else:
- <!--[if lt IE 9]><script src="/assets/js/html5.js"></script><![endif]-->
+ <!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
%endif
${extra_head_data}
diff --git a/nikola/data/themes/base/templates/post.tmpl b/nikola/data/themes/base/templates/post.tmpl
index 0babb2b..fc0afba 100644
--- a/nikola/data/themes/base/templates/post.tmpl
+++ b/nikola/data/themes/base/templates/post.tmpl
@@ -7,9 +7,18 @@
<%block name="extra_head">
${parent.extra_head()}
% if post.meta('keywords'):
- <meta name="keywords" content="${post.meta('keywords')|h}">
+ <meta name="keywords" content="${post.meta('keywords')|h}">
% endif
+ %if post.description():
+ <meta name="description" itemprop="description" content="${post.description()}">
+ %endif
<meta name="author" content="${post.author()}">
+ %if post.prev_post:
+ <link rel="prev" href="${post.prev_post.permalink()}" title="${post.prev_post.title()}" type="text/html">
+ %endif
+ %if post.next_post:
+ <link rel="next" href="${post.next_post.permalink()}" title="${post.next_post.title()}" type="text/html">
+ %endif
${helper.open_graph_metadata(post)}
${helper.twitter_card_information(post)}
${helper.meta_translations(post)}
diff --git a/nikola/data/themes/base/templates/post_helper.tmpl b/nikola/data/themes/base/templates/post_helper.tmpl
index 85ba378..c4e0ed1 100644
--- a/nikola/data/themes/base/templates/post_helper.tmpl
+++ b/nikola/data/themes/base/templates/post_helper.tmpl
@@ -38,33 +38,33 @@
</%def>
<%def name="open_graph_metadata(post)">
- %if use_open_graph:
- <meta name="og:title" content="${post.title()[:70]|h}">
- <meta name="og:url" content="${abs_link(permalink)}">
- %if post.description():
- <meta name="og:description" content="${post.description()[:200]|h}">
- %else:
- <meta name="og:description" content="${post.text(strip_html=True)[:200]|h}">
- %endif
- <meta name="og:site_name" content="${blog_title|striphtml}">
- <meta name="og:type" content="article">
+%if use_open_graph:
+ <meta name="og:title" content="${post.title()[:70]|h}">
+ <meta name="og:url" content="${abs_link(permalink)}">
+ %if post.description():
+ <meta name="og:description" content="${post.description()[:200]|h}">
+ %else:
+ <meta name="og:description" content="${post.text(strip_html=True)[:200]|h}">
%endif
+ <meta name="og:site_name" content="${blog_title|striphtml}">
+ <meta name="og:type" content="article">
+%endif
</%def>
<%def name="twitter_card_information(post)">
- %if twitter_card and twitter_card['use_twitter_cards']:
- <meta name="twitter:card" content="${twitter_card.get('card', 'summary')|h}">
- %if 'site:id' in twitter_card:
- <meta name="twitter:site:id" content="${twitter_card['site:id']}">
- %elif 'site' in twitter_card:
- <meta name="twitter:site" content="${twitter_card['site']}">
- %endif
- %if 'creator:id' in twitter_card:
- <meta name="twitter:creator:id" content="${twitter_card['creator:id']}">
- %elif 'creator' in twitter_card:
- <meta name="twitter:creator" content="${twitter_card['creator']}">
- %endif
+%if twitter_card and twitter_card['use_twitter_cards']:
+ <meta name="twitter:card" content="${twitter_card.get('card', 'summary')|h}">
+ %if 'site:id' in twitter_card:
+ <meta name="twitter:site:id" content="${twitter_card['site:id']}">
+ %elif 'site' in twitter_card:
+ <meta name="twitter:site" content="${twitter_card['site']}">
+ %endif
+ %if 'creator:id' in twitter_card:
+ <meta name="twitter:creator:id" content="${twitter_card['creator:id']}">
+ %elif 'creator' in twitter_card:
+ <meta name="twitter:creator" content="${twitter_card['creator']}">
%endif
+%endif
</%def>
<%def name="mathjax_script(post)">
diff --git a/nikola/data/themes/base/templates/story.tmpl b/nikola/data/themes/base/templates/story.tmpl
index e3e3054..2737c4d 100644
--- a/nikola/data/themes/base/templates/story.tmpl
+++ b/nikola/data/themes/base/templates/story.tmpl
@@ -4,27 +4,13 @@
<%namespace name="comments" file="comments_helper.tmpl"/>
<%inherit file="post.tmpl"/>
-<%block name="extra_head">
- ${parent.extra_head()}
- % if post.meta('keywords'):
- <meta name="keywords" content="${post.meta('keywords')|h}">
- % endif
- <meta name="author" content="${post.author()}">
- ${helper.open_graph_metadata(post)}
- ${helper.twitter_card_information(post)}
- ${helper.meta_translations(post)}
- %if post.description():
- <meta name="description" itemprop="description" content="${post.description()}">
- %endif
-</%block>
-
<%block name="content">
<article class="storypage" itemscope="itemscope" itemtype="http://schema.org/Article">
<header>
${pheader.html_title()}
${pheader.html_translations(post)}
</header>
- <div itemprop="articleBody text">
+ <div class="e-content entry-content" itemprop="articleBody text">
${post.text()}
</div>
%if site_has_comments and enable_comments and not post.meta('nocomments'):
@@ -33,5 +19,6 @@
${comments.comment_form(post.permalink(absolute=True), post.title(), post.base_path)}
</section>
%endif
+ ${helper.mathjax_script(post)}
</article>
</%block>
diff --git a/nikola/data/themes/bootstrap-jinja/templates/base.tmpl b/nikola/data/themes/bootstrap-jinja/templates/base.tmpl
index a433721..e9bed3c 100644
--- a/nikola/data/themes/bootstrap-jinja/templates/base.tmpl
+++ b/nikola/data/themes/bootstrap-jinja/templates/base.tmpl
@@ -9,6 +9,7 @@
{{ template_hooks['extra_head']() }}
</head>
<body>
+<a href="#content" class="sr-only sr-only-focusable">{{ messages("Skip to main content") }}</a>
<!-- Menubar -->
@@ -57,7 +58,7 @@
</div>
</div>
<!-- End of Menubar -->
-<div class="container-fluid" id="container-fluid">
+<div class="container-fluid" id="content">
<!--Body content-->
<div class="row-fluid">
<div class="span2"></div>
diff --git a/nikola/data/themes/bootstrap-jinja/templates/base_helper.tmpl b/nikola/data/themes/bootstrap-jinja/templates/base_helper.tmpl
index d8398b8..e44b3a7 100644
--- a/nikola/data/themes/bootstrap-jinja/templates/base_helper.tmpl
+++ b/nikola/data/themes/bootstrap-jinja/templates/base_helper.tmpl
@@ -47,11 +47,18 @@ lang="{{ lang }}">
<meta property="fb:app_id" content="{{ comment_system_id }}">
{% endif %}
+ {% if prevlink %}
+ <link rel="prev" href="{{ prevlink }}" type="text/html">
+ {% endif %}
+ {% if nextlink %}
+ <link rel="next" href="{{ nextlink }}" type="text/html">
+ {% endif %}
+
{{ mathjax_config }}
{% if use_cdn %}
<!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
{% else %}
- <!--[if lt IE 9]><script src="/assets/js/html5.js"></script><![endif]-->
+ <!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
{% endif %}
{{ extra_head_data }}
@@ -117,7 +124,7 @@ lang="{{ lang }}">
{% macro html_navigation_links() %}
{% for url, text in navigation_links[lang] %}
- {% if url is mapping %}
+ {% if isinstance(url, tuple) %}
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ text }}<b class="caret"></b></a>
<ul class="dropdown-menu">
{% for suburl, text in url %}
diff --git a/nikola/data/themes/bootstrap-jinja/templates/post.tmpl b/nikola/data/themes/bootstrap-jinja/templates/post.tmpl
index 531ebd5..54646d0 100644
--- a/nikola/data/themes/bootstrap-jinja/templates/post.tmpl
+++ b/nikola/data/themes/bootstrap-jinja/templates/post.tmpl
@@ -7,9 +7,18 @@
{% block extra_head %}
{{ super() }}
{% if post.meta('keywords') %}
- <meta name="keywords" content="{{ post.meta('keywords')|e }}">
+ <meta name="keywords" content="{{ post.meta('keywords')|e }}">
+ {% endif %}
+ {% if post.description() %}
+ <meta name="description" itemprop="description" content="{{ post.description() }}">
{% endif %}
<meta name="author" content="{{ post.author() }}">
+ {% if post.prev_post %}
+ <link rel="prev" href="{{ post.prev_post.permalink() }}" title="{{ post.prev_post.title() }}" type="text/html">
+ {% endif %}
+ {% if post.next_post %}
+ <link rel="next" href="{{ post.next_post.permalink() }}" title="{{ post.next_post.title() }}" type="text/html">
+ {% endif %}
{{ helper.open_graph_metadata(post) }}
{{ helper.twitter_card_information(post) }}
{{ helper.meta_translations(post) }}
diff --git a/nikola/data/themes/bootstrap/assets/css/theme.css b/nikola/data/themes/bootstrap/assets/css/theme.css
index ccdfda2..761dbb6 100644
--- a/nikola/data/themes/bootstrap/assets/css/theme.css
+++ b/nikola/data/themes/bootstrap/assets/css/theme.css
@@ -172,3 +172,24 @@ h4, h5, h6 {
padding: 10px 0;
display: inline-block;
}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ clip: auto;
+}
diff --git a/nikola/data/themes/bootstrap/templates/base.tmpl b/nikola/data/themes/bootstrap/templates/base.tmpl
index a469098..9f2bb61 100644
--- a/nikola/data/themes/bootstrap/templates/base.tmpl
+++ b/nikola/data/themes/bootstrap/templates/base.tmpl
@@ -9,6 +9,7 @@ ${base.html_headstart()}
${template_hooks['extra_head']()}
</head>
<body>
+<a href="#content" class="sr-only sr-only-focusable">${messages("Skip to main content")}</a>
<!-- Menubar -->
@@ -57,7 +58,7 @@ ${template_hooks['extra_head']()}
</div>
</div>
<!-- End of Menubar -->
-<div class="container-fluid" id="container-fluid">
+<div class="container-fluid" id="content">
<!--Body content-->
<div class="row-fluid">
<div class="span2"></div>
diff --git a/nikola/data/themes/bootstrap/templates/base_helper.tmpl b/nikola/data/themes/bootstrap/templates/base_helper.tmpl
index 2dcc138..40cce39 100644
--- a/nikola/data/themes/bootstrap/templates/base_helper.tmpl
+++ b/nikola/data/themes/bootstrap/templates/base_helper.tmpl
@@ -47,11 +47,18 @@ lang="${lang}">
<meta property="fb:app_id" content="${comment_system_id}">
% endif
+ %if prevlink:
+ <link rel="prev" href="${prevlink}" type="text/html">
+ %endif
+ %if nextlink:
+ <link rel="next" href="${nextlink}" type="text/html">
+ %endif
+
${mathjax_config}
%if use_cdn:
<!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
%else:
- <!--[if lt IE 9]><script src="/assets/js/html5.js"></script><![endif]-->
+ <!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
%endif
${extra_head_data}
diff --git a/nikola/data/themes/bootstrap/templates/post.tmpl b/nikola/data/themes/bootstrap/templates/post.tmpl
index 29a5b75..e55fcd5 100644
--- a/nikola/data/themes/bootstrap/templates/post.tmpl
+++ b/nikola/data/themes/bootstrap/templates/post.tmpl
@@ -7,9 +7,18 @@
<%block name="extra_head">
${parent.extra_head()}
% if post.meta('keywords'):
- <meta name="keywords" content="${post.meta('keywords')|h}">
+ <meta name="keywords" content="${post.meta('keywords')|h}">
% endif
+ %if post.description():
+ <meta name="description" itemprop="description" content="${post.description()}">
+ %endif
<meta name="author" content="${post.author()}">
+ %if post.prev_post:
+ <link rel="prev" href="${post.prev_post.permalink()}" title="${post.prev_post.title()}" type="text/html">
+ %endif
+ %if post.next_post:
+ <link rel="next" href="${post.next_post.permalink()}" title="${post.next_post.title()}" type="text/html">
+ %endif
${helper.open_graph_metadata(post)}
${helper.twitter_card_information(post)}
${helper.meta_translations(post)}
diff --git a/nikola/data/themes/bootstrap3-jinja/AUTHORS.txt b/nikola/data/themes/bootstrap3-jinja/AUTHORS.txt
new file mode 100644
index 0000000..043d497
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/AUTHORS.txt
@@ -0,0 +1 @@
+Roberto Alsina <https://github.com/ralsina>
diff --git a/nikola/data/themes/bootstrap3-jinja/README.md b/nikola/data/themes/bootstrap3-jinja/README.md
new file mode 100644
index 0000000..f008daf
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/README.md
@@ -0,0 +1,8 @@
+A bootstrap3 version of the bootstrap theme.
+
+There is a variant called bootstrap3-gradients which uses an extra CSS
+file for a *visually enhanced experience* (according to Bootstrap
+developers at least). This one uses the default bootstrap3 flat look.
+
+This theme supports Bootswtach font/color schemes (unlike
+bootstrap3-gradients) through the `nikola bootswatch_theme` command.
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.css.map b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.css.map
new file mode 120000
index 0000000..639bdc1
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.css.map
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/css/bootstrap-theme.css.map \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.css.map b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.css.map
new file mode 120000
index 0000000..8448a3d
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.css.map
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/css/bootstrap.css.map \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/docs.css b/nikola/data/themes/bootstrap3-jinja/assets/css/docs.css
new file mode 120000
index 0000000..b9cce36
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/docs.css
@@ -0,0 +1 @@
+../../../bootstrap3/assets/css/docs.css \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomCenter.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomCenter.png
new file mode 120000
index 0000000..2a6267e
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomCenter.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderBottomCenter.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomLeft.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomLeft.png
new file mode 120000
index 0000000..6cd025a
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomLeft.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderBottomLeft.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomRight.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomRight.png
new file mode 120000
index 0000000..9596518
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderBottomRight.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderBottomRight.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleLeft.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleLeft.png
new file mode 120000
index 0000000..b5403bf
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleLeft.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderMiddleLeft.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleRight.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleRight.png
new file mode 120000
index 0000000..27c023f
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderMiddleRight.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderMiddleRight.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopCenter.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopCenter.png
new file mode 120000
index 0000000..e272a45
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopCenter.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderTopCenter.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopLeft.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopLeft.png
new file mode 120000
index 0000000..e8ceae5
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopLeft.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderTopLeft.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopRight.png b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopRight.png
new file mode 120000
index 0000000..9a84403
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/images/ie6/borderTopRight.png
@@ -0,0 +1 @@
+../../../../../bootstrap3/assets/css/images/ie6/borderTopRight.png \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/rst.css b/nikola/data/themes/bootstrap3-jinja/assets/css/rst.css
new file mode 120000
index 0000000..d78763e
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/rst.css
@@ -0,0 +1 @@
+../../../bootstrap3/assets/css/rst.css \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css b/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css
new file mode 120000
index 0000000..a2774ff
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css
@@ -0,0 +1 @@
+../../../bootstrap3/assets/css/theme.css \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.eot b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.eot
new file mode 120000
index 0000000..c2dfd17
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.eot
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.svg b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.svg
new file mode 120000
index 0000000..30abe9d
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.ttf b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.ttf
new file mode 120000
index 0000000..93e3bf3
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.ttf
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.woff b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.woff
new file mode 120000
index 0000000..f7595ae
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/assets/fonts/glyphicons-halflings-regular.woff
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/bundles b/nikola/data/themes/bootstrap3-jinja/bundles
new file mode 120000
index 0000000..8cb3e06
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/bundles
@@ -0,0 +1 @@
+../bootstrap3/bundles \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3-jinja/engine b/nikola/data/themes/bootstrap3-jinja/engine
new file mode 100644
index 0000000..6f04b30
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/engine
@@ -0,0 +1 @@
+jinja
diff --git a/nikola/data/themes/bootstrap3-jinja/parent b/nikola/data/themes/bootstrap3-jinja/parent
new file mode 100644
index 0000000..e89c4ee
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/parent
@@ -0,0 +1 @@
+bootstrap-jinja
diff --git a/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl
new file mode 100644
index 0000000..c1ac838
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl
@@ -0,0 +1,88 @@
+{# -*- coding: utf-8 -*- #}
+{% import 'base_helper.tmpl' as base with context %}
+{% import 'annotation_helper.tmpl' as notes with context %}
+{{ set_locale(lang) }}
+{{ base.html_headstart() }}
+{% block extra_head %}
+{# Leave this block alone. #}
+{% endblock %}
+{{ template_hooks['extra_head']() }}
+</head>
+<body>
+<a href="#content" class="sr-only sr-only-focusable">{{ messages("Skip to main content") }}</a>
+
+<!-- Menubar -->
+
+<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+ <div class="container"><!-- This keeps the margins nice -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="{{ abs_link('/') }}">
+ {% if logo_url %}
+ <img src="{{ logo_url }}" alt="{{ blog_title }}" id="logo">
+ {% endif %}
+
+ {% if show_blog_title %}
+ <span id="blog-title">{{ blog_title }}</span>
+ {% endif %}
+ </a>
+ </div><!-- /.navbar-header -->
+ <div class="collapse navbar-collapse navbar-ex1-collapse">
+ <ul class="nav navbar-nav">
+ {{ base.html_navigation_links() }}
+ {{ template_hooks['menu']() }}
+ </ul>
+ {% if search_form %}
+ {{ search_form }}
+ {% endif %}
+
+ <ul class="nav navbar-nav navbar-right">
+ {% block belowtitle %}
+ {% if translations|length > 1 %}
+ <li>{{ base.html_translations() }}</li>
+ {% endif %}
+ {% endblock %}
+ {% if show_sourcelink %}
+ {% block sourcelink %}{% endblock %}
+ {% endif %}
+ {{ template_hooks['menu_alt']() }}
+ </ul>
+ </div><!-- /.navbar-collapse -->
+ </div><!-- /.container -->
+</nav>
+
+<!-- End of Menubar -->
+
+<div class="container" id="content">
+ <div class="body-content">
+ <!--Body content-->
+ <div class="row">
+ {{ template_hooks['page_header']() }}
+ {% block content %}{% endblock %}
+ </div>
+ <!--End of body content-->
+
+ <footer>
+ {{ content_footer }}
+ {{ template_hooks['page_footer']() }}
+ </footer>
+ </div>
+</div>
+
+{{ base.late_load_js() }}
+ <script>jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"100%",maxHeight:"100%",scalePhotos:true});</script>
+ {% block extra_js %}{% endblock %}
+ {% if annotations and post and not post.meta('noannotations') %}
+ {{ notes.code() }}
+ {% elif not annotations and post and post.meta('annotations') %}
+ {{ notes.code() }}
+ {% endif %}
+{{ body_end }}
+{{ template_hooks['body_end']() }}
+</body>
+</html>
diff --git a/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl
new file mode 100644
index 0000000..38a73c4
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl
@@ -0,0 +1,165 @@
+{# -*- coding: utf-8 -*- #}
+
+{% macro html_headstart() %}
+<!DOCTYPE html>
+<html
+
+{% if use_open_graph or (twitter_card and twitter_card['use_twitter_cards']) or (comment_system == 'facebook') %}
+prefix='
+{% if use_open_graph or (twitter_card and twitter_card['use_twitter_cards']) %}
+og: http://ogp.me/ns#
+{% endif %}
+{% if use_open_graph %}
+article: http://ogp.me/ns/article#
+{% endif %}
+{% if comment_system == 'facebook' %}
+fb: http://ogp.me/ns/fb#
+{% endif %}
+'
+{% endif %}
+
+{% if is_rtl %}
+dir="rtl"
+{% endif %}
+
+lang="{{ lang }}">
+ <head>
+ <meta charset="utf-8">
+ {% if description %}
+ <meta name="description" content="{{ description }}">
+ {% endif %}
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>{{ title|e }} | {{ blog_title|e }}</title>
+
+ {{ html_stylesheets() }}
+ {{ html_feedlinks() }}
+ {% if permalink %}
+ <link rel="canonical" href="{{ abs_link(permalink) }}">
+ {% endif %}
+
+ {% if favicons %}
+ {% for name, file, size in favicons %}
+ <link rel="{{ name }}" href="{{ file }}" sizes="{{ size }}"/>
+ {% endfor %}
+ {% endif %}
+
+ {% if comment_system == 'facebook' %}
+ <meta property="fb:app_id" content="{{ comment_system_id }}">
+ {% endif %}
+
+ {% if prevlink %}
+ <link rel="prev" href="{{ prevlink }}" type="text/html">
+ {% endif %}
+ {% if nextlink %}
+ <link rel="next" href="{{ nextlink }}" type="text/html">
+ {% endif %}
+
+ {{ mathjax_config }}
+ {% if use_cdn %}
+ <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
+ {% else %}
+ <!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
+ {% endif %}
+
+ {{ extra_head_data }}
+{% endmacro %}
+
+{% macro late_load_js() %}
+ {% if use_bundles %}
+ {% if use_cdn %}
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
+ <script src="/assets/js/all.js"></script>
+ {% else %}
+ <script src="/assets/js/all-nocdn.js"></script>
+ {% endif %}
+ {% else %}
+ {% if use_cdn %}
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
+ {% else %}
+ <script src="/assets/js/jquery.min.js"></script>
+ <script src="/assets/js/bootstrap.min.js"></script>
+ {% endif %}
+ <script src="/assets/js/jquery.colorbox-min.js"></script>
+ {% endif %}
+ {% if colorbox_locales[lang] %}
+ <script src="/assets/js/colorbox-i18n/jquery.colorbox-{{ colorbox_locales[lang] }}.js"></script>
+ {% endif %}
+ {{ social_buttons_code }}
+{% endmacro %}
+
+
+{% macro html_stylesheets() %}
+ {% if use_bundles %}
+ {% if use_cdn %}
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
+ {% else %}
+ <link href="/assets/css/all-nocdn.css" rel="stylesheet" type="text/css">
+ {% endif %}
+ {% else %}
+ {% if use_cdn %}
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
+ {% else %}
+ <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css">
+ {% endif %}
+ <link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/code.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/theme.css" rel="stylesheet" type="text/css">
+ {% if has_custom_css %}
+ <link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
+ {% endif %}
+ {% endif %}
+ {% if annotations and post and not post.meta('noannotations') %}
+ {{ notes.css() }}
+ {% elif not annotations and post and post.meta('annotations') %}
+ {{ notes.css() }}
+ {% endif %}
+{% endmacro %}
+
+{% macro html_navigation_links() %}
+ {% for url, text in navigation_links[lang] %}
+ {% if isinstance(url, tuple) %}
+ <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ text }}<b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ {% for suburl, text in url %}
+ {% if rel_link(permalink, suburl) == "#" %}
+ <li class="active"><a href="{{ permalink }}">{{ text }}</a>
+ {% else %}
+ <li><a href="{{ suburl }}">{{ text }}</a>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ {% else %}
+ {% if rel_link(permalink, url) == "#" %}
+ <li class="active"><a href="{{ permalink }}">{{ text }}</a>
+ {% else %}
+ <li><a href="{{ url }}">{{ text }}</a>
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+{% endmacro %}
+
+{% macro html_feedlinks() %}
+ {% if rss_link %}
+ {{ rss_link }}
+ {% elif generate_rss %}
+ {% if translations|length > 1 %}
+ {% for language in translations %}
+ <link rel="alternate" type="application/rss+xml" title="RSS ({{ language }})" href="{{ _link('rss', None, language) }}">
+ {% endfor %}
+ {% else %}
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="{{ _link('rss', None) }}">
+ {% endif %}
+ {% endif %}
+{% endmacro %}
+
+{% macro html_translations() %}
+ {% for langname in translations.keys() %}
+ {% if langname != lang %}
+ <li><a href="{{ _link("index", None, langname) }}" rel="alternate" hreflang="{{ langname }}">{{ messages("LANGUAGE", langname) }}</a></li>
+ {% endif %}
+ {% endfor %}
+{% endmacro %}
diff --git a/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl
new file mode 100644
index 0000000..11382c3
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl
@@ -0,0 +1,94 @@
+{# -*- coding: utf-8 -*- #}
+{% extends 'base.tmpl' %}
+{% import 'comments_helper.tmpl' as comments with context %}
+{% import 'crumbs.tmpl' as ui with context %}
+{% block sourcelink %}{% endblock %}
+
+{% block content %}
+ {{ ui.bar(crumbs) }}
+ {% if title %}
+ <h1>{{ title }}</h1>
+ {% endif %}
+ {% if post %}
+ <p>
+ {{ post.text() }}
+ </p>
+ {% endif %}
+ {% if folders %}
+ <ul>
+ {% for folder, ftitle in folders %}
+ <li><a href="{{ folder }}"><i class="glyphicon
+ glyphicon-folder-open"></i>&nbsp;{{ ftitle }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+<div id="gallery_container"></div>
+{% if photo_array %}
+<noscript>
+<ul class="thumbnails">
+ {% for image in photo_array %}
+ <li><a href="{{ image['url'] }}" class="thumbnail image-reference" title="{{ image['title'] }}">
+ <img src="{{ image['url_thumb'] }}" alt="{{ image['title'] }}" /></a>
+ {% endfor %}
+</ul>
+</noscript>
+{% endif %}
+{% if site_has_comments and enable_comments %}
+{{ comments.comment_form(None, permalink, title) }}
+{% endif %}
+{% endblock %}
+
+{% block extra_head %}
+{{ super() }}
+<style type="text/css">
+ .image-block {
+ display: inline-block;
+ }
+ .flowr_row {
+ width: 100%;
+ }
+ </style>
+{% endblock %}
+
+
+{% block extra_js %}
+<script src="/assets/js/flowr.plugin.js"></script>
+<script>
+jsonContent = {{ photo_array_json }};
+$("#gallery_container").flowr({
+ data : jsonContent,
+ height : {{ thumbnail_size }}*.6,
+ padding: 5,
+ rows: -1,
+ render : function(params) {
+ // Just return a div, string or a dom object, anything works fine
+ img = $("<img />").attr({
+ 'src': params.itemData.url_thumb,
+ 'width' : params.width,
+ 'height' : params.height
+ }).css('max-width', '100%');
+ link = $( "<a></a>").attr({
+ 'href': params.itemData.url,
+ 'class': 'image-reference'
+ });
+ div = $("<div />").addClass('image-block').attr({
+ 'title': params.itemData.title,
+ 'data-toggle': "tooltip",
+ });
+ link.append(img);
+ div.append(link);
+ div.hover(div.tooltip());
+ return div;
+ },
+ itemWidth : function(data) { return data.size.w; },
+ itemHeight : function(data) { return data.size.h; },
+ complete : function(params) {
+ if( jsonContent.length > params.renderedItems ) {
+ nextRenderList = jsonContent.slice( params.renderedItems );
+ }
+ }
+ });
+$("a.image-reference").colorbox({rel:"gal", maxWidth:"100%",maxHeight:"100%",scalePhotos:true});
+</script>
+{% endblock %}
diff --git a/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl
new file mode 100644
index 0000000..634c482
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl
@@ -0,0 +1,28 @@
+{# -*- coding: utf-8 -*- #}
+{% extends 'base.tmpl' %}
+{% import 'crumbs.tmpl' as ui with context %}
+
+{% block content %}
+{{ ui.bar(crumbs) }}
+{% if folders or files %}
+<ul class="list-unstyled">
+{% for name in folders %}
+ <li><a href="{{ name }}"><i class="glyphicon glyphicon-folder-open"></i> {{ name }}</a>
+{% endfor %}
+{% for name in files %}
+ <li><a href="{{ name }}.html"><i class="glyphicon glyphicon-file"></i> {{ name }}</a>
+{% endfor %}
+</ul>
+{% endif %}
+{% if code %}
+ {{ code }}
+{% endif %}
+{% endblock %}
+
+{% block sourcelink %}
+{% if source_link %}
+ <li>
+ <a href="{{ source_link }}" id="sourcelink">{{ messages("Source") }}</a>
+ </li>
+{% endif %}
+{% endblock %}
diff --git a/nikola/data/themes/bootstrap3-jinja/templates/slides.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/slides.tmpl
new file mode 100644
index 0000000..342ed27
--- /dev/null
+++ b/nikola/data/themes/bootstrap3-jinja/templates/slides.tmpl
@@ -0,0 +1,24 @@
+{% block content %}
+<div id="{{ carousel_id }}" class="carousel slide">
+ <ol class="carousel-indicators">
+ {% for i in range(slides_content|length) %}
+ {% if i == 0 %}
+ <li data-target="#{{ carousel_id }}" data-slide-to="{{ i }}" class="active"></li>
+ {% else %}
+ <li data-target="#{{ carousel_id }}" data-slide-to="{{ i }}"></li>
+ {% endif %}
+ {% endfor %}
+ </ol>
+ <div class="carousel-inner">
+ {% for i, image in enumerate(slides_content) %}
+ {% if i == 0 %}
+ <div class="item active"><img src="{{ image }}" alt="" style="margin: 0 auto 0 auto;"></div>
+ {% else %}
+ <div class="item"><img src="{{ image }}" alt="" style="margin: 0 auto 0 auto;"></div>
+ {% endif %}
+ {% endfor %}
+ </div>
+ <a class="left carousel-control" href="#{{ carousel_id }}" data-slide="prev"><span class="icon-prev"></span></a>
+ <a class="right carousel-control" href="#{{ carousel_id }}" data-slide="next"><span class="icon-next"></span></a>
+</div>
+{% endblock %}
diff --git a/nikola/data/themes/bootstrap3/README.md b/nikola/data/themes/bootstrap3/README.md
new file mode 100644
index 0000000..f008daf
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/README.md
@@ -0,0 +1,8 @@
+A bootstrap3 version of the bootstrap theme.
+
+There is a variant called bootstrap3-gradients which uses an extra CSS
+file for a *visually enhanced experience* (according to Bootstrap
+developers at least). This one uses the default bootstrap3 flat look.
+
+This theme supports Bootswtach font/color schemes (unlike
+bootstrap3-gradients) through the `nikola bootswatch_theme` command.
diff --git a/nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.css.map b/nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.css.map
new file mode 120000
index 0000000..639bdc1
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.css.map
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/css/bootstrap-theme.css.map \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3/assets/css/bootstrap.css.map b/nikola/data/themes/bootstrap3/assets/css/bootstrap.css.map
new file mode 120000
index 0000000..8448a3d
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/bootstrap.css.map
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/css/bootstrap.css.map \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3/assets/css/docs.css b/nikola/data/themes/bootstrap3/assets/css/docs.css
new file mode 100644
index 0000000..189ea89
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/docs.css
@@ -0,0 +1,160 @@
+body {
+ font-weight: 300;
+}
+
+a:hover,
+a:focus {
+ text-decoration: none;
+}
+
+.container {
+ max-width: 700px;
+}
+
+h2 {
+ text-align: center;
+ font-weight: 300;
+}
+
+
+/* Header
+-------------------------------------------------- */
+
+.jumbotron {
+ position: relative;
+ font-size: 16px;
+ color: #fff;
+ color: rgba(255,255,255,.75);
+ text-align: center;
+ background-color: #b94a48;
+ border-radius: 0;
+}
+.jumbotron h1,
+.jumbotron .glyphicon-ok {
+ margin-bottom: 15px;
+ font-weight: 300;
+ letter-spacing: -1px;
+ color: #fff;
+}
+.jumbotron .glyphicon-ok {
+ font-size: 40px;
+ line-height: 1;
+}
+.btn-outline {
+ margin-top: 15px;
+ margin-bottom: 15px;
+ padding: 18px 24px;
+ font-size: inherit;
+ font-weight: 500;
+ color: #fff; /* redeclare to override the `.jumbotron a` */
+ background-color: transparent;
+ border-color: #fff;
+ border-color: rgba(255,255,255,.5);
+ transition: all .1s ease-in-out;
+}
+.btn-outline:hover,
+.btn-outline:active {
+ color: #b94a48;
+ background-color: #fff;
+ border-color: #fff;
+}
+
+.jumbotron:after {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 10;
+ display: block;
+ content: "";
+ height: 30px;
+ background-image: -moz-linear-gradient(rgba(0, 0, 0, 0), rgba(0,0,0,.1));
+ background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0,0,0,.1));
+}
+
+.jumbotron p a,
+.jumbotron-links a {
+ font-weight: 500;
+ color: #fff;
+ transition: all .1s ease-in-out;
+}
+.jumbotron p a:hover,
+.jumbotron-links a:hover {
+ text-shadow: 0 0 10px rgba(255,255,255,.55);
+}
+
+/* Textual links */
+.jumbotron-links {
+ margin-top: 15px;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+ font-size: 14px;
+}
+.jumbotron-links li {
+ display: inline;
+}
+.jumbotron-links li + li {
+ margin-left: 20px;
+}
+
+@media (min-width: 768px) {
+ .jumbotron {
+ padding-top: 100px;
+ padding-bottom: 100px;
+ font-size: 21px;
+ }
+ .jumbotron h1,
+ .jumbotron .glyphicon-ok {
+ font-size: 50px;
+ }
+}
+
+/* Steps for setup
+-------------------------------------------------- */
+
+.how-to {
+ padding: 50px 20px;
+ border-top: 1px solid #eee;
+}
+.how-to li {
+ font-size: 21px;
+ line-height: 1.5;
+ margin-top: 20px;
+}
+.how-to li p {
+ font-size: 16px;
+ color: #555;
+}
+.how-to code {
+ font-size: 85%;
+ color: #b94a48;
+ background-color: #fcf3f2;
+ word-wrap: break-word;
+ white-space: normal;
+}
+
+/* Icons
+-------------------------------------------------- */
+
+.the-icons {
+ padding: 40px 10px;
+ font-size: 20px;
+ line-height: 2;
+ color: #333;
+ text-align: center;
+}
+.the-icons .glyphicon {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+/* Footer
+-------------------------------------------------- */
+
+.footer {
+ padding: 50px 30px;
+ color: #777;
+ text-align: center;
+ border-top: 1px solid #eee;
+}
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomCenter.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomCenter.png
new file mode 100644
index 0000000..0d4475e
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomCenter.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomLeft.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomLeft.png
new file mode 100644
index 0000000..2775eba
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomLeft.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomRight.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomRight.png
new file mode 100644
index 0000000..f7f5137
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderBottomRight.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleLeft.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleLeft.png
new file mode 100644
index 0000000..a2d63d1
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleLeft.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleRight.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleRight.png
new file mode 100644
index 0000000..fd7c3e8
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderMiddleRight.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopCenter.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopCenter.png
new file mode 100644
index 0000000..2937a9c
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopCenter.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopLeft.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopLeft.png
new file mode 100644
index 0000000..f9d458b
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopLeft.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopRight.png b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopRight.png
new file mode 100644
index 0000000..74b8583
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/images/ie6/borderTopRight.png
Binary files differ
diff --git a/nikola/data/themes/bootstrap3/assets/css/rst.css b/nikola/data/themes/bootstrap3/assets/css/rst.css
new file mode 100644
index 0000000..489ceaa
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/rst.css
@@ -0,0 +1,318 @@
+/*
+:Author: David Goodger (goodger@python.org)
+:Id: $Id: html4css1.css 7514 2012-09-14 14:27:12Z milde $
+:Copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+
+See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
+customize this style sheet.
+*/
+
+/* used to remove borders from tables and images */
+.borderless, table.borderless td, table.borderless th {
+ border: 0 }
+
+table.borderless td, table.borderless th {
+ /* Override padding for "table.docutils td" with "! important".
+ The right padding separates the table cells. */
+ padding: 0 0.5em 0 0 ! important }
+
+.first {
+ /* Override more specific margin styles with "! important". */
+ margin-top: 0 ! important }
+
+.last, .with-subtitle {
+ margin-bottom: 0 ! important }
+
+.hidden {
+ display: none }
+
+a.toc-backref {
+ text-decoration: none ;
+ color: black }
+
+blockquote.epigraph {
+ margin: 2em 5em ; }
+
+dl.docutils dd {
+ margin-bottom: 0.5em }
+
+object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
+ overflow: hidden;
+}
+
+/* Uncomment (and remove this text!) to get bold-faced definition list terms
+dl.docutils dt {
+ font-weight: bold }
+*/
+
+div.abstract {
+ margin: 2em 5em }
+
+div.abstract p.topic-title {
+ font-weight: bold ;
+ text-align: center }
+
+div.admonition, div.attention, div.caution, div.danger, div.error,
+div.hint, div.important, div.note, div.tip, div.warning {
+ margin: 2em ;
+ border: medium outset ;
+ padding: 1em }
+
+div.admonition p.admonition-title, div.hint p.admonition-title,
+div.important p.admonition-title, div.note p.admonition-title,
+div.tip p.admonition-title {
+ font-weight: bold ;
+ font-family: sans-serif }
+
+div.attention p.admonition-title, div.caution p.admonition-title,
+div.danger p.admonition-title, div.error p.admonition-title,
+div.warning p.admonition-title, .code .error {
+ color: red ;
+ font-weight: bold ;
+ font-family: sans-serif }
+
+/* Uncomment (and remove this text!) to get reduced vertical space in
+ compound paragraphs.
+div.compound .compound-first, div.compound .compound-middle {
+ margin-bottom: 0.5em }
+
+div.compound .compound-last, div.compound .compound-middle {
+ margin-top: 0.5em }
+*/
+
+div.dedication {
+ margin: 2em 5em ;
+ text-align: center ;
+ font-style: italic }
+
+div.dedication p.topic-title {
+ font-weight: bold ;
+ font-style: normal }
+
+div.figure {
+ margin-left: 2em ;
+ margin-right: 2em }
+
+div.footer, div.header {
+ clear: both;
+ font-size: smaller }
+
+div.line-block {
+ display: block ;
+ margin-top: 1em ;
+ margin-bottom: 1em }
+
+div.line-block div.line-block {
+ margin-top: 0 ;
+ margin-bottom: 0 ;
+ margin-left: 1.5em }
+
+html[dir="rtl"] div.line-block div.line-block {
+ margin-top: 0 ;
+ margin-bottom: 0 ;
+ margin-right: 1.5em ;
+ margin-left: 0 }
+
+div.sidebar {
+ margin: 0 0 0.5em 1em ;
+ border: medium outset ;
+ padding: 1em ;
+ background-color: #ffffee ;
+ width: 40% ;
+ float: right ;
+ clear: right }
+
+div.sidebar p.rubric {
+ font-family: sans-serif ;
+ font-size: medium }
+
+div.system-messages {
+ margin: 5em }
+
+div.system-messages h1 {
+ color: red }
+
+div.system-message {
+ border: medium outset ;
+ padding: 1em }
+
+div.system-message p.system-message-title {
+ color: red ;
+ font-weight: bold }
+
+div.topic {
+ margin: 2em }
+
+h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
+h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
+ margin-top: 0.4em }
+
+h1.title {
+ text-align: center }
+
+h2.subtitle {
+ text-align: center }
+
+hr.docutils {
+ width: 75% }
+
+img.align-left, .figure.align-left, object.align-left {
+ clear: left ;
+ float: left ;
+ margin-right: 1em }
+
+img.align-right, .figure.align-right, object.align-right {
+ clear: right ;
+ float: right ;
+ margin-left: 1em }
+
+img.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left }
+
+.align-center {
+ clear: both ;
+ text-align: center }
+
+.align-right {
+ text-align: right }
+
+/* reset inner alignment in figures */
+div.align-right {
+ text-align: inherit }
+
+/* div.align-center * { */
+/* text-align: left } */
+
+ol.simple, ul.simple {
+ margin-bottom: 1em }
+
+ol.arabic {
+ list-style: decimal }
+
+ol.loweralpha {
+ list-style: lower-alpha }
+
+ol.upperalpha {
+ list-style: upper-alpha }
+
+ol.lowerroman {
+ list-style: lower-roman }
+
+ol.upperroman {
+ list-style: upper-roman }
+
+p.attribution {
+ text-align: right ;
+ margin-left: 50% }
+
+p.caption {
+ font-style: italic }
+
+p.credits {
+ font-style: italic ;
+ font-size: smaller }
+
+p.label {
+ white-space: nowrap }
+
+p.rubric {
+ font-weight: bold ;
+ font-size: larger ;
+ color: maroon ;
+ text-align: center }
+
+p.sidebar-title {
+ font-family: sans-serif ;
+ font-weight: bold ;
+ font-size: larger }
+
+p.sidebar-subtitle {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+p.topic-title {
+ font-weight: bold }
+
+pre.address {
+ margin-bottom: 0 ;
+ margin-top: 0 ;
+ font: inherit }
+
+pre.literal-block, pre.doctest-block, pre.math, pre.code {
+ margin-left: 2em ;
+ margin-right: 2em }
+
+pre.code .ln { color: grey; } /* line numbers */
+pre.code, code { background-color: #eeeeee }
+pre.code .comment, code .comment { color: #5C6576 }
+pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
+pre.code .literal.string, code .literal.string { color: #0C5404 }
+pre.code .name.builtin, code .name.builtin { color: #352B84 }
+pre.code .deleted, code .deleted { background-color: #DEB0A1}
+pre.code .inserted, code .inserted { background-color: #A3D289}
+
+span.classifier {
+ font-family: sans-serif ;
+ font-style: oblique }
+
+span.classifier-delimiter {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+span.interpreted {
+ font-family: sans-serif }
+
+span.option {
+ white-space: nowrap }
+
+span.pre {
+ white-space: pre }
+
+span.problematic {
+ color: red }
+
+span.section-subtitle {
+ /* font-size relative to parent (h1..h6 element) */
+ font-size: 80% }
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px }
+
+table.docinfo {
+ margin: 2em 4em }
+
+table.docutils {
+ margin-top: 0.5em ;
+ margin-bottom: 0.5em }
+
+table.footnote {
+ border-left: solid 1px black;
+ margin-left: 1px }
+
+table.docutils td, table.docutils th,
+table.docinfo td, table.docinfo th {
+ padding-left: 0.5em ;
+ padding-right: 0.5em ;
+ vertical-align: top }
+
+table.docutils th.field-name, table.docinfo th.docinfo-name {
+ font-weight: bold ;
+ text-align: left ;
+ white-space: nowrap ;
+ padding-left: 0 }
+
+h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
+h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
+ font-size: 100% }
+
+ul.auto-toc {
+ list-style-type: none }
diff --git a/nikola/data/themes/bootstrap3/assets/css/theme.css b/nikola/data/themes/bootstrap3/assets/css/theme.css
new file mode 100644
index 0000000..5e3775a
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/css/theme.css
@@ -0,0 +1,183 @@
+body {
+ margin-top: 60px;
+}
+
+#container {
+ width: 960px;
+ margin: 0 auto;
+}
+
+#contentcolumn {
+ max-width: 760px;
+}
+#q {
+ width: 150px;
+}
+
+img {
+ max-width: 90%;
+}
+
+.postbox {
+ border-bottom: 2px solid darkgrey;
+ margin-bottom: 12px;
+}
+
+.titlebox {
+ text-align: right;
+}
+
+#addthisbox {margin-bottom: 12px;}
+
+td.label {
+ /* Issue #290 */
+ background-color: inherit;
+}
+
+.footnote-reference {
+ /* Issue 290 */
+ vertical-align: super;
+ font-size: xx-small;
+}
+
+
+.caption {
+ /* Issue 292 */
+ text-align: center;
+ padding-top: 1em;
+}
+
+div.figure > img,
+div.figure > a > img {
+ /* Issue 292 */
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+div.sidebar, div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning {
+ /* Issue 277 */
+ border: 1px solid #aaa;
+ border-radius: 5px;
+}
+
+blockquote p, blockquote {
+ font-size: 17.5px;
+ font-weight: 300;
+ line-height: 1.25;
+}
+
+ul.bricks > li {
+ display: inline;
+ background-color: lightblue;
+ padding: 8px;
+ border-radius: 5px;
+ line-height: 3;
+ white-space:nowrap;
+ margin: 3px;
+}
+
+.at300b, .stMainServices, .stButton, .stButton_gradient {
+ box-sizing: content-box;
+}
+
+pre, pre code {
+ white-space: pre;
+ word-wrap: normal;
+ overflow: auto;
+}
+
+article.post-micro {
+ font-family: Georgia, 'Times New Roman', Times, serif;
+ font-size: 1.5em;
+}
+
+/* fix anchors for headers */
+h1, h2, h3 {
+ margin-top: -40px;
+ padding-top: 60px;
+}
+
+h4, h5, h6 {
+ margin-top: -50px;
+ padding-top: 60px;
+}
+
+.image-block {
+ display: inline-block;
+}
+
+.flowr_row {
+ width: 100%;
+}
+
+.tags {
+ padding-left: 0;
+ margin-left: -5px;
+ list-style: none;
+ text-align: center;
+
+}
+
+.tags > li {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999;
+ border-radius: 10px;
+}
+
+.tags > li a {
+ color: #fff;
+}
+
+.metadata p:before,
+.postlist .listdate:before {
+ content: " — ";
+}
+
+.metadata p:first-of-type:before {
+ content: "";
+}
+
+.metadata p {
+ display: inline;
+}
+
+.posttranslations h3 {
+ display: inline;
+ font-size: 1em;
+ font-weight: bold;
+}
+
+.posttranslations h3:last-child {
+ display: none;
+}
+
+.entry-content {
+ margin-top: 1em;
+}
+
+.navbar-brand {
+ padding: 0 15px;
+}
+
+.navbar-brand #blog-title {
+ padding: 15px 0;
+ display: inline-block;
+}
+
+.navbar-brand #logo {
+ max-width: 100%;
+}
+
+.row {
+ margin: 0;
+}
diff --git a/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.eot b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.eot
new file mode 120000
index 0000000..c2dfd17
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.eot
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.svg b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.svg
new file mode 120000
index 0000000..30abe9d
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.ttf b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.ttf
new file mode 120000
index 0000000..93e3bf3
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.ttf
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.woff b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.woff
new file mode 120000
index 0000000..f7595ae
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/assets/fonts/glyphicons-halflings-regular.woff
@@ -0,0 +1 @@
+../../../../../../bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff \ No newline at end of file
diff --git a/nikola/data/themes/bootstrap3/bundles b/nikola/data/themes/bootstrap3/bundles
new file mode 100644
index 0000000..0a96b4f
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/bundles
@@ -0,0 +1,4 @@
+assets/css/all-nocdn.css=bootstrap.css,rst.css,code.css,colorbox.css,theme.css,custom.css
+assets/css/all.css=rst.css,code.css,colorbox.css,theme.css,custom.css
+assets/js/all-nocdn.js=jquery.min.js,bootstrap.min.js,jquery.colorbox-min.js
+assets/js/all.js=jquery.colorbox-min.js
diff --git a/nikola/data/themes/bootstrap3/engine b/nikola/data/themes/bootstrap3/engine
new file mode 100644
index 0000000..2951cdd
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/engine
@@ -0,0 +1 @@
+mako
diff --git a/nikola/data/themes/bootstrap3/parent b/nikola/data/themes/bootstrap3/parent
new file mode 100644
index 0000000..b7c200a
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/parent
@@ -0,0 +1 @@
+bootstrap
diff --git a/nikola/data/themes/bootstrap3/templates/base.tmpl b/nikola/data/themes/bootstrap3/templates/base.tmpl
new file mode 100644
index 0000000..c463873
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/templates/base.tmpl
@@ -0,0 +1,88 @@
+## -*- coding: utf-8 -*-
+<%namespace name="base" file="base_helper.tmpl" import="*" />
+<%namespace name="notes" file="annotation_helper.tmpl" import="*" />
+${set_locale(lang)}
+${base.html_headstart()}
+<%block name="extra_head">
+### Leave this block alone.
+</%block>
+${template_hooks['extra_head']()}
+</head>
+<body>
+<a href="#content" class="sr-only sr-only-focusable">${messages("Skip to main content")}</a>
+
+<!-- Menubar -->
+
+<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+ <div class="container"><!-- This keeps the margins nice -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="${abs_link('/')}">
+ %if logo_url:
+ <img src="${logo_url}" alt="${blog_title}" id="logo">
+ %endif
+
+ % if show_blog_title:
+ <span id="blog-title">${blog_title}</span>
+ % endif
+ </a>
+ </div><!-- /.navbar-header -->
+ <div class="collapse navbar-collapse navbar-ex1-collapse">
+ <ul class="nav navbar-nav">
+ ${base.html_navigation_links()}
+ ${template_hooks['menu']()}
+ </ul>
+ %if search_form:
+ ${search_form}
+ %endif
+
+ <ul class="nav navbar-nav navbar-right">
+ <%block name="belowtitle">
+ %if len(translations) > 1:
+ <li>${base.html_translations()}</li>
+ %endif
+ </%block>
+ % if show_sourcelink:
+ <%block name="sourcelink"></%block>
+ %endif
+ ${template_hooks['menu_alt']()}
+ </ul>
+ </div><!-- /.navbar-collapse -->
+ </div><!-- /.container -->
+</nav>
+
+<!-- End of Menubar -->
+
+<div class="container" id="content">
+ <div class="body-content">
+ <!--Body content-->
+ <div class="row">
+ ${template_hooks['page_header']()}
+ <%block name="content"></%block>
+ </div>
+ <!--End of body content-->
+
+ <footer>
+ ${content_footer}
+ ${template_hooks['page_footer']()}
+ </footer>
+ </div>
+</div>
+
+${base.late_load_js()}
+ <script>jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"100%",maxHeight:"100%",scalePhotos:true});</script>
+ <%block name="extra_js"></%block>
+ % if annotations and post and not post.meta('noannotations'):
+ ${notes.code()}
+ % elif not annotations and post and post.meta('annotations'):
+ ${notes.code()}
+ % endif
+${body_end}
+${template_hooks['body_end']()}
+</body>
+</html>
diff --git a/nikola/data/themes/bootstrap3/templates/base_helper.tmpl b/nikola/data/themes/bootstrap3/templates/base_helper.tmpl
new file mode 100644
index 0000000..096c3c2
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/templates/base_helper.tmpl
@@ -0,0 +1,165 @@
+## -*- coding: utf-8 -*-
+
+<%def name="html_headstart()">
+<!DOCTYPE html>
+<html
+\
+% if use_open_graph or (twitter_card and twitter_card['use_twitter_cards']) or (comment_system == 'facebook'):
+prefix='\
+%if use_open_graph or (twitter_card and twitter_card['use_twitter_cards']):
+og: http://ogp.me/ns# \
+%endif
+%if use_open_graph:
+article: http://ogp.me/ns/article# \
+%endif
+%if comment_system == 'facebook':
+fb: http://ogp.me/ns/fb# \
+%endif
+'\
+%endif
+\
+% if is_rtl:
+dir="rtl" \
+% endif
+\
+lang="${lang}">
+ <head>
+ <meta charset="utf-8">
+ %if description:
+ <meta name="description" content="${description}">
+ %endif
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>${title|striphtml} | ${blog_title|striphtml}</title>
+
+ ${html_stylesheets()}
+ ${html_feedlinks()}
+ %if permalink:
+ <link rel="canonical" href="${abs_link(permalink)}">
+ %endif
+
+ %if favicons:
+ %for name, file, size in favicons:
+ <link rel="${name}" href="${file}" sizes="${size}"/>
+ %endfor
+ %endif
+
+ % if comment_system == 'facebook':
+ <meta property="fb:app_id" content="${comment_system_id}">
+ % endif
+
+ %if prevlink:
+ <link rel="prev" href="${prevlink}" type="text/html">
+ %endif
+ %if nextlink:
+ <link rel="next" href="${nextlink}" type="text/html">
+ %endif
+
+ ${mathjax_config}
+ %if use_cdn:
+ <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
+ %else:
+ <!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
+ %endif
+
+ ${extra_head_data}
+</%def>
+
+<%def name="late_load_js()">
+ %if use_bundles:
+ %if use_cdn:
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
+ <script src="/assets/js/all.js"></script>
+ %else:
+ <script src="/assets/js/all-nocdn.js"></script>
+ %endif
+ %else:
+ %if use_cdn:
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
+ %else:
+ <script src="/assets/js/jquery.min.js"></script>
+ <script src="/assets/js/bootstrap.min.js"></script>
+ %endif
+ <script src="/assets/js/jquery.colorbox-min.js"></script>
+ %endif
+ %if colorbox_locales[lang]:
+ <script src="/assets/js/colorbox-i18n/jquery.colorbox-${colorbox_locales[lang]}.js"></script>
+ %endif
+ ${social_buttons_code}
+</%def>
+
+
+<%def name="html_stylesheets()">
+ %if use_bundles:
+ %if use_cdn:
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
+ %else:
+ <link href="/assets/css/all-nocdn.css" rel="stylesheet" type="text/css">
+ %endif
+ %else:
+ %if use_cdn:
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
+ %else:
+ <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css">
+ %endif
+ <link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/code.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/theme.css" rel="stylesheet" type="text/css">
+ %if has_custom_css:
+ <link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
+ %endif
+ %endif
+ % if annotations and post and not post.meta('noannotations'):
+ ${notes.css()}
+ % elif not annotations and post and post.meta('annotations'):
+ ${notes.css()}
+ % endif
+</%def>
+
+<%def name="html_navigation_links()">
+ %for url, text in navigation_links[lang]:
+ % if isinstance(url, tuple):
+ <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">${text}<b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ %for suburl, text in url:
+ % if rel_link(permalink, suburl) == "#":
+ <li class="active"><a href="${permalink}">${text}</a>
+ %else:
+ <li><a href="${suburl}">${text}</a>
+ %endif
+ %endfor
+ </ul>
+ % else:
+ % if rel_link(permalink, url) == "#":
+ <li class="active"><a href="${permalink}">${text}</a>
+ %else:
+ <li><a href="${url}">${text}</a>
+ %endif
+ % endif
+ %endfor
+</%def>
+
+<%def name="html_feedlinks()">
+ %if rss_link:
+ ${rss_link}
+ %elif generate_rss:
+ %if len(translations) > 1:
+ %for language in translations:
+ <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, language)}">
+ %endfor
+ %else:
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="${_link('rss', None)}">
+ %endif
+ %endif
+</%def>
+
+<%def name="html_translations()">
+ %for langname in translations.keys():
+ %if langname != lang:
+ <li><a href="${_link("index", None, langname)}" rel="alternate" hreflang="${langname}">${messages("LANGUAGE", langname)}</a></li>
+ %endif
+ %endfor
+</%def>
diff --git a/nikola/data/themes/bootstrap3/templates/gallery.tmpl b/nikola/data/themes/bootstrap3/templates/gallery.tmpl
new file mode 100644
index 0000000..26fe80d
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/templates/gallery.tmpl
@@ -0,0 +1,94 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%namespace name="comments" file="comments_helper.tmpl"/>
+<%namespace name="ui" file="crumbs.tmpl" import="bar"/>
+<%block name="sourcelink"></%block>
+
+<%block name="content">
+ ${ui.bar(crumbs)}
+ %if title:
+ <h1>${title}</h1>
+ %endif
+ %if post:
+ <p>
+ ${post.text()}
+ </p>
+ %endif
+ %if folders:
+ <ul>
+ % for folder, ftitle in folders:
+ <li><a href="${folder}"><i class="glyphicon
+ glyphicon-folder-open"></i>&nbsp;${ftitle}</a></li>
+ % endfor
+ </ul>
+ %endif
+
+<div id="gallery_container"></div>
+%if photo_array:
+<noscript>
+<ul class="thumbnails">
+ %for image in photo_array:
+ <li><a href="${image['url']}" class="thumbnail image-reference" title="${image['title']}">
+ <img src="${image['url_thumb']}" alt="${image['title']}" /></a>
+ %endfor
+</ul>
+</noscript>
+%endif
+%if site_has_comments and enable_comments:
+${comments.comment_form(None, permalink, title)}
+%endif
+</%block>
+
+<%block name="extra_head">
+${parent.extra_head()}
+<style type="text/css">
+ .image-block {
+ display: inline-block;
+ }
+ .flowr_row {
+ width: 100%;
+ }
+ </style>
+</%block>
+
+
+<%block name="extra_js">
+<script src="/assets/js/flowr.plugin.js"></script>
+<script>
+jsonContent = ${photo_array_json};
+$("#gallery_container").flowr({
+ data : jsonContent,
+ height : ${thumbnail_size}*.6,
+ padding: 5,
+ rows: -1,
+ render : function(params) {
+ // Just return a div, string or a dom object, anything works fine
+ img = $("<img />").attr({
+ 'src': params.itemData.url_thumb,
+ 'width' : params.width,
+ 'height' : params.height
+ }).css('max-width', '100%');
+ link = $( "<a></a>").attr({
+ 'href': params.itemData.url,
+ 'class': 'image-reference'
+ });
+ div = $("<div />").addClass('image-block').attr({
+ 'title': params.itemData.title,
+ 'data-toggle': "tooltip",
+ });
+ link.append(img);
+ div.append(link);
+ div.hover(div.tooltip());
+ return div;
+ },
+ itemWidth : function(data) { return data.size.w; },
+ itemHeight : function(data) { return data.size.h; },
+ complete : function(params) {
+ if( jsonContent.length > params.renderedItems ) {
+ nextRenderList = jsonContent.slice( params.renderedItems );
+ }
+ }
+ });
+$("a.image-reference").colorbox({rel:"gal", maxWidth:"100%",maxHeight:"100%",scalePhotos:true});
+</script>
+</%block>
diff --git a/nikola/data/themes/bootstrap3/templates/listing.tmpl b/nikola/data/themes/bootstrap3/templates/listing.tmpl
new file mode 100644
index 0000000..7b09e3e
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/templates/listing.tmpl
@@ -0,0 +1,28 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%namespace name="ui" file="crumbs.tmpl" import="bar"/>
+
+<%block name="content">
+${ui.bar(crumbs)}
+%if folders or files:
+<ul class="list-unstyled">
+% for name in folders:
+ <li><a href="${name}"><i class="glyphicon glyphicon-folder-open"></i> ${name}</a>
+% endfor
+% for name in files:
+ <li><a href="${name}.html"><i class="glyphicon glyphicon-file"></i> ${name}</a>
+% endfor
+</ul>
+%endif
+% if code:
+ ${code}
+% endif
+</%block>
+
+<%block name="sourcelink">
+% if source_link:
+ <li>
+ <a href="${source_link}" id="sourcelink">${messages("Source")}</a>
+ </li>
+% endif
+</%block>
diff --git a/nikola/data/themes/bootstrap3/templates/slides.tmpl b/nikola/data/themes/bootstrap3/templates/slides.tmpl
new file mode 100644
index 0000000..a73848a
--- /dev/null
+++ b/nikola/data/themes/bootstrap3/templates/slides.tmpl
@@ -0,0 +1,24 @@
+<%block name="content">
+<div id="${carousel_id}" class="carousel slide">
+ <ol class="carousel-indicators">
+ % for i in range(len(slides_content)):
+ % if i == 0:
+ <li data-target="#${carousel_id}" data-slide-to="${i}" class="active"></li>
+ % else:
+ <li data-target="#${carousel_id}" data-slide-to="${i}"></li>
+ % endif
+ % endfor
+ </ol>
+ <div class="carousel-inner">
+ % for i, image in enumerate(slides_content):
+ % if i == 0:
+ <div class="item active"><img src="${image}" alt="" style="margin: 0 auto 0 auto;"></div>
+ % else:
+ <div class="item"><img src="${image}" alt="" style="margin: 0 auto 0 auto;"></div>
+ % endif
+ % endfor
+ </div>
+ <a class="left carousel-control" href="#${carousel_id}" data-slide="prev"><span class="icon-prev"></span></a>
+ <a class="right carousel-control" href="#${carousel_id}" data-slide="next"><span class="icon-next"></span></a>
+</div>
+</%block>
diff --git a/nikola/filters.py b/nikola/filters.py
index 78d624b..0037004 100644
--- a/nikola/filters.py
+++ b/nikola/filters.py
@@ -29,7 +29,7 @@
from .utils import req_missing
from functools import wraps
import os
-import codecs
+import io
import shutil
import subprocess
import tempfile
@@ -62,10 +62,10 @@ def apply_to_text_file(f):
in place. Reads files in UTF-8."""
@wraps(f)
def f_in_file(fname):
- with codecs.open(fname, 'r', 'utf-8') as inf:
+ with io.open(fname, 'r', encoding='utf-8') as inf:
data = inf.read()
data = f(data)
- with codecs.open(fname, 'w+', 'utf-8') as outf:
+ with io.open(fname, 'w+', encoding='utf-8') as outf:
outf.write(data)
return f_in_file
@@ -135,6 +135,10 @@ def yui_compressor(infile):
return runinplace(r'{} --nomunge %1 -o %2'.format(yuicompressor), infile)
+def closure_compiler(infile):
+ return runinplace(r'closure-compiler --warning_level QUIET --js %1 --js_output_file %2', infile)
+
+
def optipng(infile):
return runinplace(r"optipng -preserve -o2 -quiet %1", infile)
@@ -144,6 +148,13 @@ def jpegoptim(infile):
@apply_to_text_file
+def minify_lines(data):
+ datalines = data.splitlines()
+ datalines = [line.lstrip() for line in datalines if not (line.strip() == "")]
+ return "\n".join(datalines)
+
+
+@apply_to_text_file
def typogrify(data):
if typo is None:
req_missing(['typogrify'], 'use the typogrify filter')
@@ -155,3 +166,18 @@ def typogrify(data):
# data = typo.caps(data)
data = typo.initial_quotes(data)
return data
+
+
+@apply_to_text_file
+def php_template_injection(data):
+ import re
+ template = re.search('<\!-- __NIKOLA_PHP_TEMPLATE_INJECTION source\:(.*) checksum\:(.*)__ -->', data)
+ if template:
+ source = template.group(1)
+ with io.open(source, "r", encoding="utf-8") as in_file:
+ phpdata = in_file.read()
+ _META_SEPARATOR = '(' + os.linesep * 2 + '|' + ('\n' * 2) + '|' + ("\r\n" * 2) + ')'
+ phpdata = re.split(_META_SEPARATOR, phpdata, maxsplit=1)[-1]
+ phpdata = re.sub(template.group(0), phpdata, data)
+
+ return phpdata
diff --git a/nikola/nikola.py b/nikola/nikola.py
index 59e1b97..6a3fc0d 100644
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function, unicode_literals
-import codecs
+import io
from collections import defaultdict
from copy import copy
from pkg_resources import resource_filename
@@ -34,6 +34,7 @@ import glob
import locale
import os
import sys
+import mimetypes
try:
from urlparse import urlparse, urlsplit, urljoin
except ImportError:
@@ -133,7 +134,7 @@ LEGAL_VALUES = {
'pt': 'pt_br',
'zh': 'zh_cn'
},
- 'RTL_LANGUAGES': ('fa', 'ur'),
+ 'RTL_LANGUAGES': ('ar', 'fa', 'ur'),
'COLORBOX_LOCALES': defaultdict(
str,
bg='bg',
@@ -163,6 +164,16 @@ LEGAL_VALUES = {
}
+def _enclosure(post, lang):
+ '''Default implementation of enclosures'''
+ enclosure = post.meta('enclosure', lang)
+ if enclosure:
+ length = 0
+ url = enclosure
+ mime = mimetypes.guess_type(url)[0]
+ return url, length, mime
+
+
class Nikola(object):
"""Class that handles site generation.
@@ -319,6 +330,7 @@ class Nikola(object):
'USE_CDN': False,
'USE_FILENAME_AS_TITLE': True,
'USE_OPEN_GRAPH': True,
+ 'USE_SLUGIFY': True,
'TIMEZONE': 'UTC',
'DEPLOY_DRAFTS': True,
'DEPLOY_FUTURE': False,
@@ -389,6 +401,9 @@ class Nikola(object):
# We provide the arguments to format in CONTENT_FOOTER_FORMATS.
self.config['CONTENT_FOOTER'].langformat(self.config['CONTENT_FOOTER_FORMATS'])
+ # propagate USE_SLUGIFY
+ utils.USE_SLUGIFY = self.config['USE_SLUGIFY']
+
# Make sure we have pyphen installed if we are using it
if self.config.get('HYPHENATE') and pyphen is None:
utils.LOGGER.warn('To use the hyphenation, you have to install '
@@ -635,6 +650,7 @@ class Nikola(object):
'SHOW_SOURCELINK')
self._GLOBAL_CONTEXT['extra_head_data'] = self.config.get('EXTRA_HEAD_DATA')
self._GLOBAL_CONTEXT['colorbox_locales'] = LEGAL_VALUES['COLORBOX_LOCALES']
+ self._GLOBAL_CONTEXT['url_replacer'] = self.url_replacer
self._GLOBAL_CONTEXT.update(self.config.get('GLOBAL_CONTEXT', {}))
@@ -866,6 +882,10 @@ class Nikola(object):
if not result:
result = "."
+ # Don't forget the query part of the link
+ if parsed_dst.query:
+ result += "?" + parsed_dst.query
+
# Don't forget the fragment (anchor) part of the link
if parsed_dst.fragment:
result += "#" + parsed_dst.fragment
@@ -875,7 +895,7 @@ class Nikola(object):
return result
def generic_rss_renderer(self, lang, title, link, description, timeline, output_path,
- rss_teasers, rss_plain, feed_length=10, feed_url=None, enclosure=None):
+ rss_teasers, rss_plain, feed_length=10, feed_url=None, enclosure=_enclosure):
"""Takes all necessary data, and renders a RSS feed in output_path."""
rss_obj = rss.RSS2(
@@ -929,14 +949,11 @@ class Nikola(object):
rss_obj.rss_attrs["xmlns:dc"] = "http://purl.org/dc/elements/1.1/"
""" Enclosure callback must returns tuple """
- if enclosure:
- download_link, download_size, download_type = enclosure(post=post, lang=lang)
-
- args['enclosure'] = rss.Enclosure(
- download_link,
- download_size,
- download_type,
- )
+ # enclosure callback returns None if post has no enclosure, or a
+ # 3-tuple of (url, length (0 is valid), mimetype)
+ enclosure_details = enclosure(post=post, lang=lang)
+ if enclosure_details is not None:
+ args['enclosure'] = rss.Enclosure(*enclosure_details)
items.append(utils.ExtendedItem(**args))
@@ -944,7 +961,7 @@ class Nikola(object):
dst_dir = os.path.dirname(output_path)
utils.makedirs(dst_dir)
- with codecs.open(output_path, "wb+", "utf-8") as rss_file:
+ with io.open(output_path, "w+", encoding="utf-8") as rss_file:
data = rss_obj.to_xml(encoding='utf-8')
if isinstance(data, utils.bytes_str):
data = data.decode('utf-8')
@@ -1199,16 +1216,17 @@ class Nikola(object):
self.posts_per_month[
'{0}/{1:02d}'.format(post.date.year, post.date.month)].append(post)
for tag in post.alltags:
- if utils.slugify(tag) in slugged_tags:
+ _tag_slugified = utils.slugify(tag)
+ if _tag_slugified in slugged_tags:
if tag not in self.posts_per_tag:
# Tags that differ only in case
- other_tag = [k for k in self.posts_per_tag.keys() if k.lower() == tag.lower()][0]
+ other_tag = [existing for existing in self.posts_per_tag.keys() if utils.slugify(existing) == _tag_slugified][0]
utils.LOGGER.error('You have tags that are too similar: {0} and {1}'.format(tag, other_tag))
utils.LOGGER.error('Tag {0} is used in: {1}'.format(tag, post.source_path))
utils.LOGGER.error('Tag {0} is used in: {1}'.format(other_tag, ', '.join([p.source_path for p in self.posts_per_tag[other_tag]])))
quit = True
else:
- slugged_tags.add(utils.slugify(tag))
+ slugged_tags.add(utils.slugify(tag, force=True))
self.posts_per_tag[tag].append(post)
self.posts_per_category[post.meta('category')].append(post)
else:
@@ -1249,7 +1267,6 @@ class Nikola(object):
context['title'] = post.title(lang)
context['description'] = post.description(lang)
context['permalink'] = post.permalink(lang)
- context['page_list'] = self.pages
if post.use_in_feeds:
context['enable_comments'] = True
else:
@@ -1274,6 +1291,8 @@ class Nikola(object):
for k in self._GLOBAL_CONTEXT_TRANSLATABLE:
deps_dict[k] = deps_dict['global'][k](lang)
+ deps_dict['navigation_links'] = deps_dict['global']['navigation_links'](lang)
+
if post:
deps_dict['post_translations'] = post.translated_to
@@ -1315,6 +1334,8 @@ class Nikola(object):
for k in self._GLOBAL_CONTEXT_TRANSLATABLE:
deps_context[k] = deps_context['global'][k](lang)
+ deps_context['navigation_links'] = deps_context['global']['navigation_links'](lang)
+
task = {
'name': os.path.normpath(output_name),
'targets': [output_name],
diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py
index 4109f2d..5eab806 100644
--- a/nikola/plugin_categories.py
+++ b/nikola/plugin_categories.py
@@ -84,7 +84,7 @@ class Command(BasePlugin, DoitCommand):
doc_purpose = "A short explanation."
doc_usage = ""
- doc_description = None # None value will completely ommit line from doc
+ doc_description = None # None value will completely omit line from doc
# see http://python-doit.sourceforge.net/cmd_run.html#parameters
cmd_options = ()
needs_config = True
diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py
index 7b23f9c..764968a 100644
--- a/nikola/plugins/basic_import.py
+++ b/nikola/plugins/basic_import.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import unicode_literals, print_function
-import codecs
+import io
import csv
import datetime
import os
@@ -128,7 +128,7 @@ class ImportMixin(object):
description = ""
utils.makedirs(os.path.dirname(filename))
- with codecs.open(filename, "w+", "utf8") as fd:
+ with io.open(filename, "w+", encoding="utf8") as fd:
fd.write('{0}\n'.format(title))
fd.write('{0}\n'.format(slug))
fd.write('{0}\n'.format(post_date))
@@ -139,7 +139,7 @@ class ImportMixin(object):
@staticmethod
def write_urlmap_csv(output_file, url_map):
utils.makedirs(os.path.dirname(output_file))
- with codecs.open(output_file, 'w+', 'utf8') as fd:
+ with io.open(output_file, 'w+', encoding='utf8') as fd:
csv_writer = csv.writer(fd)
for item in url_map.items():
csv_writer.writerow(item)
@@ -159,7 +159,7 @@ class ImportMixin(object):
@staticmethod
def write_configuration(filename, rendered_template):
utils.makedirs(os.path.dirname(filename))
- with codecs.open(filename, 'w+', 'utf8') as fd:
+ with io.open(filename, 'w+', encoding='utf8') as fd:
fd.write(rendered_template)
diff --git a/nikola/plugins/command/auto.py b/nikola/plugins/command/auto.py
index c46e0a3..7f3f66f 100644
--- a/nikola/plugins/command/auto.py
+++ b/nikola/plugins/command/auto.py
@@ -28,7 +28,6 @@ from __future__ import print_function, unicode_literals
import os
import subprocess
-import webbrowser
from nikola.plugin_categories import Command
from nikola.utils import req_missing
@@ -61,7 +60,7 @@ class CommandAuto(Command):
try:
from livereload import Server
except ImportError:
- req_missing(['livereload==2.1.0'], 'use the "auto" command')
+ req_missing(['livereload'], 'use the "auto" command')
return
# Run an initial build so we are up-to-date
@@ -81,6 +80,8 @@ class CommandAuto(Command):
out_folder = self.site.config['OUTPUT_FOLDER']
if options and options.get('browser'):
- webbrowser.open('http://localhost:{0}'.format(port))
+ browser = True
+ else:
+ browser = False
- server.serve(port, None, out_folder)
+ server.serve(port, None, out_folder, True, browser)
diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py
index 871a5ce..e65413b 100644
--- a/nikola/plugins/command/bootswatch_theme.py
+++ b/nikola/plugins/command/bootswatch_theme.py
@@ -82,9 +82,9 @@ class CommandBootswatchTheme(Command):
# See if we need bootswatch for bootstrap v2 or v3
themes = utils.get_theme_chain(parent)
- if 'bootstrap3' not in themes:
+ if 'bootstrap3' not in themes or 'bootstrap3-jinja' not in themes:
version = '2'
- elif 'bootstrap' not in themes:
+ elif 'bootstrap' not in themes or 'bootstrap-jinja' not in themes:
LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap')
elif 'bootstrap3-gradients' in themes or 'bootstrap3-gradients-jinja' in themes:
LOGGER.warn('"bootswatch_theme" doesn\'t work well with the bootstrap3-gradients family')
diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py
index 76571a0..bd254f4 100644
--- a/nikola/plugins/command/check.py
+++ b/nikola/plugins/command/check.py
@@ -30,9 +30,9 @@ import re
import sys
try:
from urllib import unquote
- from urlparse import urlparse
+ from urlparse import urlparse, urljoin, urldefrag
except ImportError:
- from urllib.parse import unquote, urlparse # NOQA
+ from urllib.parse import unquote, urlparse, urljoin, urldefrag # NOQA
import lxml.html
@@ -63,6 +63,15 @@ def real_scan_files(site):
return (only_on_output, only_on_input)
+def fs_relpath_from_url_path(url_path):
+ """Expects as input an urlparse(s).path"""
+ url_path = unquote(url_path)
+ # in windows relative paths don't begin with os.sep
+ if sys.platform == 'win32' and len(url_path):
+ url_path = url_path[1:].replace('/', '\\')
+ return url_path
+
+
class CommandCheck(Command):
"""Check the generated site."""
@@ -142,6 +151,8 @@ class CommandCheck(Command):
self.existing_targets.add(self.site.config['SITE_URL'])
self.existing_targets.add(self.site.config['BASE_URL'])
url_type = self.site.config['URL_TYPE']
+ if url_type == 'absolute':
+ url_netloc_to_root = urlparse(self.site.config['SITE_URL']).path
try:
filename = task.split(":")[-1]
d = lxml.html.fromstring(open(filename).read())
@@ -149,6 +160,7 @@ class CommandCheck(Command):
target = l[0].attrib[l[1]]
if target == "#":
continue
+ target, _ = urldefrag(target)
parsed = urlparse(target)
# Absolute links when using only paths, skip.
@@ -159,24 +171,20 @@ class CommandCheck(Command):
if (parsed.scheme or target.startswith('//')) and parsed.netloc != base_url.netloc:
continue
- if parsed.fragment:
- target = target.split('#')[0]
if url_type == 'rel_path':
target_filename = os.path.abspath(
os.path.join(os.path.dirname(filename), unquote(target)))
elif url_type in ('full_path', 'absolute'):
- target_filename = os.path.abspath(
- os.path.join(os.path.dirname(filename), parsed.path))
- if parsed.path in ['', '/']:
- target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.config['INDEX_FILE'])
- elif parsed.path.endswith('/'): # abspath removes trailing slashes
- target_filename += '/{0}'.format(self.site.config['INDEX_FILE'])
- if target_filename.startswith(base_url.path):
- target_filename = target_filename[len(base_url.path):]
- target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], target_filename)
- if parsed.path in ['', '/']:
- target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.config['INDEX_FILE'])
+ if url_type == 'absolute':
+ # convert to 'full_path' case, ie url relative to root
+ url_rel_path = target.path[len(url_netloc_to_root):]
+ else:
+ url_rel_path = target.path
+ if url_rel_path == '' or url_rel_path.endswith('/'):
+ url_rel_path = urljoin(url_rel_path, self.site.config['INDEX_FILE'])
+ fs_rel_path = fs_relpath_from_url_path(url_rel_path)
+ target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], fs_rel_path)
if any(re.match(x, target_filename) for x in self.whitelist):
continue
diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py
index 1bec1d3..fde43fa 100644
--- a/nikola/plugins/command/deploy.py
+++ b/nikola/plugins/command/deploy.py
@@ -25,8 +25,9 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
-import codecs
+import io
from datetime import datetime
+from dateutil.tz import gettz
import os
import sys
import subprocess
@@ -35,7 +36,7 @@ import time
from blinker import signal
from nikola.plugin_categories import Command
-from nikola.utils import remove_file, get_logger
+from nikola.utils import get_logger, remove_file, unicode_str
class CommandDeploy(Command):
@@ -84,7 +85,7 @@ class CommandDeploy(Command):
self.logger.info("Successful deployment")
try:
- with codecs.open(timestamp_path, 'rb', 'utf8') as inf:
+ with io.open(timestamp_path, 'r', encoding='utf8') as inf:
last_deploy = datetime.strptime(inf.read().strip(), "%Y-%m-%dT%H:%M:%S.%f")
clean = False
except (IOError, Exception) as e:
@@ -96,8 +97,8 @@ class CommandDeploy(Command):
self._emit_deploy_event(last_deploy, new_deploy, clean, undeployed_posts)
# Store timestamp of successful deployment
- with codecs.open(timestamp_path, 'wb+', 'utf8') as outf:
- outf.write(new_deploy.isoformat())
+ with io.open(timestamp_path, 'w+', encoding='utf8') as outf:
+ outf.write(unicode_str(new_deploy.isoformat()))
def _emit_deploy_event(self, last_deploy, new_deploy, clean=False, undeployed=None):
""" Emit events for all timeline entries newer than last deploy.
@@ -120,9 +121,12 @@ class CommandDeploy(Command):
'undeployed': undeployed
}
+ if last_deploy.tzinfo is None:
+ last_deploy = last_deploy.replace(tzinfo=gettz('UTC'))
+
deployed = [
entry for entry in self.site.timeline
- if entry.date > last_deploy.replace(tzinfo=self.site.tzinfo) and entry not in undeployed
+ if entry.date > last_deploy and entry not in undeployed
]
event['deployed'] = deployed
diff --git a/nikola/plugins/command/github_deploy.py b/nikola/plugins/command/github_deploy.py
index d4dd8c5..13da48c 100644
--- a/nikola/plugins/command/github_deploy.py
+++ b/nikola/plugins/command/github_deploy.py
@@ -135,9 +135,10 @@ class CommandGitHubDeploy(Command):
)
commands = [
+ ['git', 'pull', remote, '%s:%s' % (deploy, deploy)],
['git', 'add', '-A'],
['git', 'commit', '-m', commit_message],
- ['git', 'push', '-f', remote, '%s:%s' % (deploy, deploy)],
+ ['git', 'push', remote, '%s:%s' % (deploy, deploy)],
['git', 'checkout', source],
]
diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py
index 8ddc8c7..1af4083 100644
--- a/nikola/plugins/command/import_wordpress.py
+++ b/nikola/plugins/command/import_wordpress.py
@@ -158,6 +158,7 @@ class CommandImportWordpress(Command, ImportMixin):
channel = self.get_channel_from_file(self.wordpress_export_file)
self.context = self.populate_context(channel)
+ self.base_dir = urlparse(self.context['BASE_URL']).path
conf_template = self.generate_base_site()
# If user has specified a custom pattern for translation files we
@@ -323,13 +324,15 @@ class CommandImportWordpress(Command, ImportMixin):
# your blogging into another site or system its not.
# Why don't they just use JSON?
if sys.version_info[0] == 2:
- metadata = phpserialize.loads(utils.sys_encode(meta_value.text))
- size_key = 'sizes'
- file_key = 'file'
+ try:
+ metadata = phpserialize.loads(utils.sys_encode(meta_value.text))
+ except ValueError:
+ # local encoding might be wrong sometimes
+ metadata = phpserialize.loads(meta_value.text.encode('utf-8'))
else:
- metadata = phpserialize.loads(meta_value.text.encode('UTF-8'))
- size_key = b'sizes'
- file_key = b'file'
+ metadata = phpserialize.loads(meta_value.text.encode('utf-8'))
+ size_key = b'sizes'
+ file_key = b'file'
if size_key not in metadata:
continue
@@ -385,26 +388,34 @@ class CommandImportWordpress(Command, ImportMixin):
# link is something like http://foo.com/2012/09/01/hello-world/
# So, take the path, utils.slugify it, and that's our slug
link = get_text_tag(item, 'link', None)
- path = unquote(urlparse(link).path.strip('/'))
+ parsed = urlparse(link)
+ path = unquote(parsed.path.strip('/'))
# In python 2, path is a str. slug requires a unicode
# object. According to wikipedia, unquoted strings will
# usually be UTF8
if isinstance(path, utils.bytes_str):
path = path.decode('utf8')
+
+ # Cut out the base directory.
+ if path.startswith(self.base_dir.strip('/')):
+ path = path.replace(self.base_dir.strip('/'), '', 1)
+
pathlist = path.split('/')
- if len(pathlist) > 1:
- out_folder = os.path.join(*([out_folder] + pathlist[:-1]))
- slug = utils.slugify(pathlist[-1])
- if not slug: # it happens if the post has no "nice" URL
+ if parsed.query: # if there are no nice URLs and query strings are used
+ out_folder = os.path.join(*([out_folder] + pathlist))
slug = get_text_tag(
item, '{{{0}}}post_name'.format(wordpress_namespace), None)
- if not slug: # it *may* happen
- slug = get_text_tag(
- item, '{{{0}}}post_id'.format(wordpress_namespace), None)
- if not slug: # should never happen
- LOGGER.error("Error converting post:", title)
- return
+ if not slug: # it *may* happen
+ slug = get_text_tag(
+ item, '{{{0}}}post_id'.format(wordpress_namespace), None)
+ if not slug: # should never happen
+ LOGGER.error("Error converting post:", title)
+ return
+ else:
+ if len(pathlist) > 1:
+ out_folder = os.path.join(*([out_folder] + pathlist[:-1]))
+ slug = utils.slugify(pathlist[-1])
description = get_text_tag(item, 'description', '')
post_date = get_text_tag(
@@ -440,8 +451,9 @@ class CommandImportWordpress(Command, ImportMixin):
LOGGER.notice('Draft "{0}" will not be imported.'.format(title))
elif content.strip():
# If no content is found, no files are written.
- self.url_map[link] = (self.context['SITE_URL'] + out_folder + '/'
- + slug + '.html')
+ self.url_map[link] = (self.context['SITE_URL'] +
+ out_folder.rstrip('/') + '/' + slug +
+ '.html').replace(os.sep, '/')
if hasattr(self, "separate_qtranslate_content") \
and self.separate_qtranslate_content:
content_translations = separate_qtranslate_content(content)
diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py
index 8fb15e0..a8b60db 100644
--- a/nikola/plugins/command/init.py
+++ b/nikola/plugins/command/init.py
@@ -27,7 +27,7 @@
from __future__ import print_function, unicode_literals
import os
import shutil
-import codecs
+import io
import json
import textwrap
import datetime
@@ -242,7 +242,7 @@ class CommandInit(Command):
template_path = resource_filename('nikola', 'conf.py.in')
conf_template = Template(filename=template_path)
conf_path = os.path.join(target, 'conf.py')
- with codecs.open(conf_path, 'w+', 'utf8') as fd:
+ with io.open(conf_path, 'w+', encoding='utf8') as fd:
fd.write(conf_template.render(**prepare_config(SAMPLE_CONF)))
@classmethod
diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py
index 859bd56..5397772 100644
--- a/nikola/plugins/command/install_theme.py
+++ b/nikola/plugins/command/install_theme.py
@@ -26,10 +26,9 @@
from __future__ import print_function
import os
-import codecs
+import io
import json
import shutil
-from io import BytesIO
import pygments
from pygments.lexers import PythonLexer
@@ -137,7 +136,7 @@ class CommandInstallTheme(Command):
if name in data:
utils.makedirs(self.output_dir)
LOGGER.info('Downloading: ' + data[name])
- zip_file = BytesIO()
+ zip_file = io.BytesIO()
zip_file.write(requests.get(data[name]).content)
LOGGER.info('Extracting: {0} into themes'.format(name))
utils.extract_all(zip_file)
@@ -161,7 +160,7 @@ class CommandInstallTheme(Command):
if os.path.exists(confpypath):
LOGGER.notice('This theme has a sample config file. Integrate it with yours in order to make this theme work!')
print('Contents of the conf.py.sample file:\n')
- with codecs.open(confpypath, 'rb', 'utf-8') as fh:
+ with io.open(confpypath, 'r', encoding='utf-8') as fh:
if self.site.colorful:
print(indent(pygments.highlight(
fh.read(), PythonLexer(), TerminalFormatter()),
diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py
index 42f77cc..24c09d0 100644
--- a/nikola/plugins/command/new_post.py
+++ b/nikola/plugins/command/new_post.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import unicode_literals, print_function
-import codecs
+import io
import datetime
import os
import sys
@@ -332,7 +332,7 @@ class CommandNewPost(Command):
event = dict(path=txt_path)
if not onefile: # write metadata file
- with codecs.open(meta_path, "wb+", "utf8") as fd:
+ with io.open(meta_path, "w+", encoding="utf8") as fd:
fd.write(utils.write_metadata(data))
LOGGER.info("Your {0}'s metadata is at: {1}".format(content_type, meta_path))
event['meta_path'] = meta_path
@@ -341,8 +341,8 @@ class CommandNewPost(Command):
signal('new_' + content_type).send(self, **event)
if options['edit']:
- editor = os.getenv('EDITOR')
- to_run = [editor, txt_path]
+ editor = os.getenv('EDITOR', '').split()
+ to_run = editor + [txt_path]
if not onefile:
to_run.append(meta_path)
if editor:
diff --git a/nikola/plugins/command/plugin.py b/nikola/plugins/command/plugin.py
index df0e7a4..71901b8 100644
--- a/nikola/plugins/command/plugin.py
+++ b/nikola/plugins/command/plugin.py
@@ -25,8 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
-import codecs
-from io import BytesIO
+import io
import os
import shutil
import subprocess
@@ -228,7 +227,7 @@ class CommandPlugin(Command):
if name in data:
utils.makedirs(self.output_dir)
LOGGER.info('Downloading: ' + data[name])
- zip_file = BytesIO()
+ zip_file = io.BytesIO()
zip_file.write(requests.get(data[name]).content)
LOGGER.info('Extracting: {0} into {1}/'.format(name, self.output_dir))
utils.extract_all(zip_file, self.output_dir)
@@ -258,7 +257,7 @@ class CommandPlugin(Command):
except subprocess.CalledProcessError:
LOGGER.error('Could not install the dependencies.')
print('Contents of the requirements.txt file:\n')
- with codecs.open(reqpath, 'rb', 'utf-8') as fh:
+ with io.open(reqpath, 'r', encoding='utf-8') as fh:
print(indent(fh.read(), 4 * ' '))
print('You have to install those yourself or through a '
'package manager.')
@@ -270,7 +269,7 @@ class CommandPlugin(Command):
'dependencies you need to install '
'manually.')
print('Contents of the requirements-nonpy.txt file:\n')
- with codecs.open(reqnpypath, 'rb', 'utf-8') as fh:
+ with io.open(reqnpypath, 'r', encoding='utf-8') as fh:
for l in fh.readlines():
i, j = l.split('::')
print(indent(i.strip(), 4 * ' '))
@@ -283,7 +282,7 @@ class CommandPlugin(Command):
if os.path.exists(confpypath):
LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this plugin work!')
print('Contents of the conf.py.sample file:\n')
- with codecs.open(confpypath, 'rb', 'utf-8') as fh:
+ with io.open(confpypath, 'r', encoding='utf-8') as fh:
if self.site.colorful:
print(indent(pygments.highlight(
fh.read(), PythonLexer(), TerminalFormatter()),
diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py
index 623e2db..de4f6e2 100644
--- a/nikola/plugins/command/serve.py
+++ b/nikola/plugins/command/serve.py
@@ -60,8 +60,8 @@ class CommandServe(Command):
'short': 'a',
'long': 'address',
'type': str,
- 'default': '127.0.0.1',
- 'help': 'Address to bind (default: 127.0.0.1)',
+ 'default': '',
+ 'help': 'Address to bind (default: 0.0.0.0 – all local interfaces)',
},
{
'name': 'browser',
@@ -84,10 +84,10 @@ class CommandServe(Command):
httpd = HTTPServer((options['address'], options['port']),
OurHTTPRequestHandler)
sa = httpd.socket.getsockname()
- self.logger.info("Serving HTTP on {0} port {1} ...".format(*sa))
+ self.logger.info("Serving HTTP on {0} port {1}...".format(*sa))
if options['browser']:
- server_url = "http://{0}:{1}/".format(options['address'], options['port'])
- self.logger.info("Opening {0} in the default web browser ...".format(server_url))
+ server_url = "http://{0}:{1}/".format(*sa)
+ self.logger.info("Opening {0} in the default web browser...".format(server_url))
webbrowser.open(server_url)
try:
httpd.serve_forever()
@@ -156,6 +156,9 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
return None
self.send_response(200)
self.send_header("Content-type", ctype)
+ if os.path.splitext(path)[1] == '.svgz':
+ # Special handling for svgz to make it work nice with browsers.
+ self.send_header("Content-Encoding", 'gzip')
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py
index fff7f89..24bf385 100644
--- a/nikola/plugins/compile/html.py
+++ b/nikola/plugins/compile/html.py
@@ -26,14 +26,15 @@
"""Implementation of compile_html for HTML source files."""
+from __future__ import unicode_literals
+
import os
import re
-import codecs
+import io
from nikola.plugin_categories import PageCompiler
from nikola.utils import makedirs, write_metadata
-
_META_SEPARATOR = '(' + os.linesep * 2 + '|' + ('\n' * 2) + '|' + ("\r\n" * 2) + ')'
@@ -43,8 +44,8 @@ class CompileHtml(PageCompiler):
def compile_html(self, source, dest, is_two_file=True):
makedirs(os.path.dirname(dest))
- with codecs.open(dest, "w+", "utf8") as out_file:
- with codecs.open(source, "r", "utf8") as in_file:
+ with io.open(dest, "w+", encoding="utf8") as out_file:
+ with io.open(source, "r", encoding="utf8") as in_file:
data = in_file.read()
if not is_two_file:
data = re.split(_META_SEPARATOR, data, maxsplit=1)[-1]
@@ -62,7 +63,7 @@ class CompileHtml(PageCompiler):
makedirs(os.path.dirname(path))
if not content.endswith('\n'):
content += '\n'
- with codecs.open(path, "wb+", "utf8") as fd:
+ with io.open(path, "w+", encoding="utf8") as fd:
if onefile:
fd.write('<!--\n')
fd.write(write_metadata(metadata))
diff --git a/nikola/plugins/compile/ipynb/__init__.py b/nikola/plugins/compile/ipynb/__init__.py
index f4d554c..7dde279 100644
--- a/nikola/plugins/compile/ipynb/__init__.py
+++ b/nikola/plugins/compile/ipynb/__init__.py
@@ -27,7 +27,7 @@
"""Implementation of compile_html based on nbconvert."""
from __future__ import unicode_literals, print_function
-import codecs
+import io
import os
try:
@@ -47,6 +47,7 @@ class CompileIPynb(PageCompiler):
name = "ipynb"
supports_onefile = False
+ demote_headers = True
def compile_html(self, source, dest, is_two_file=True):
if flag is None:
@@ -55,8 +56,8 @@ class CompileIPynb(PageCompiler):
HTMLExporter.default_template = 'basic'
c = Config(self.site.config['IPYNB_CONFIG'])
exportHtml = HTMLExporter(config=c)
- with codecs.open(dest, "w+", "utf8") as out_file:
- with codecs.open(source, "r", "utf8") as in_file:
+ with io.open(dest, "w+", encoding="utf8") as out_file:
+ with io.open(source, "r", encoding="utf8") as in_file:
nb = in_file.read()
nb_json = nbformat.reads_json(nb)
(body, resources) = exportHtml.from_notebook_node(nb_json)
@@ -71,7 +72,7 @@ class CompileIPynb(PageCompiler):
makedirs(os.path.dirname(path))
if onefile:
raise Exception('The one-file format is not supported by this compiler.')
- with codecs.open(path, "wb+", "utf8") as fd:
+ with io.open(path, "w+", encoding="utf8") as fd:
fd.write("""{
"metadata": {
"name": ""
diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py
index 4182626..47c7c9b 100644
--- a/nikola/plugins/compile/markdown/__init__.py
+++ b/nikola/plugins/compile/markdown/__init__.py
@@ -28,7 +28,7 @@
from __future__ import unicode_literals
-import codecs
+import io
import os
import re
@@ -70,8 +70,8 @@ class CompileMarkdown(PageCompiler):
req_missing(['markdown'], 'build this site (compile Markdown)')
makedirs(os.path.dirname(dest))
self.extensions += self.site.config.get("MARKDOWN_EXTENSIONS")
- with codecs.open(dest, "w+", "utf8") as out_file:
- with codecs.open(source, "r", "utf8") as in_file:
+ with io.open(dest, "w+", encoding="utf8") as out_file:
+ with io.open(source, "r", encoding="utf8") as in_file:
data = in_file.read()
if not is_two_file:
data = re.split('(\n\n|\r\n\r\n)', data, maxsplit=1)[-1]
@@ -90,7 +90,7 @@ class CompileMarkdown(PageCompiler):
makedirs(os.path.dirname(path))
if not content.endswith('\n'):
content += '\n'
- with codecs.open(path, "wb+", "utf8") as fd:
+ with io.open(path, "w+", encoding="utf8") as fd:
if onefile:
fd.write('<!-- \n')
fd.write(write_metadata(metadata))
diff --git a/nikola/plugins/compile/markdown/mdx_gist.py b/nikola/plugins/compile/markdown/mdx_gist.py
index 247478b..4209bdd 100644
--- a/nikola/plugins/compile/markdown/mdx_gist.py
+++ b/nikola/plugins/compile/markdown/mdx_gist.py
@@ -65,6 +65,42 @@ Example with filename:
</div>
</p>
+Basic Example with hexidecimal id:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... [:gist: c4a43d6fdce612284ac0]
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ <p>Text of the gist:
+ <div class="gist">
+ <script src="https://gist.github.com/c4a43d6fdce612284ac0.js"></script>
+ <noscript>
+ <pre>Moo</pre>
+ </noscript>
+ </div>
+ </p>
+
+Example with hexidecimal id filename:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... [:gist: c4a43d6fdce612284ac0 cow.txt]
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ <p>Text of the gist:
+ <div class="gist">
+ <script src="https://gist.github.com/c4a43d6fdce612284ac0.js?file=cow.txt"></script>
+ <noscript>
+ <pre>Moo</pre>
+ </noscript>
+ </div>
+ </p>
+
Example using reStructuredText syntax:
>>> import markdown
@@ -83,6 +119,42 @@ Example using reStructuredText syntax:
</div>
</p>
+Example using hexidecimal ID with reStructuredText syntax:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... .. gist:: c4a43d6fdce612284ac0
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ <p>Text of the gist:
+ <div class="gist">
+ <script src="https://gist.github.com/c4a43d6fdce612284ac0.js"></script>
+ <noscript>
+ <pre>Moo</pre>
+ </noscript>
+ </div>
+ </p>
+
+Example using hexidecimal ID and filename with reStructuredText syntax:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... .. gist:: c4a43d6fdce612284ac0 cow.txt
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ <p>Text of the gist:
+ <div class="gist">
+ <script src="https://gist.github.com/c4a43d6fdce612284ac0.js?file=cow.txt"></script>
+ <noscript>
+ <pre>Moo</pre>
+ </noscript>
+ </div>
+ </p>
+
Error Case: non-existent Gist ID:
>>> import markdown
@@ -95,7 +167,8 @@ Error Case: non-existent Gist ID:
<p>Text of the gist:
<div class="gist">
<script src="https://gist.github.com/0.js"></script>
- <noscript><!-- WARNING: Received a 404 response from Gist URL: https://gist.github.com/raw/0 --></noscript>
+ <noscript><!-- WARNING: Received a 404 response from Gist URL: \
+https://gist.githubusercontent.com/raw/0 --></noscript>
</div>
</p>
@@ -111,7 +184,8 @@ Error Case: non-existent file:
<p>Text of the gist:
<div class="gist">
<script src="https://gist.github.com/4747847.js?file=doesntexist.py"></script>
- <noscript><!-- WARNING: Received a 404 response from Gist URL: https://gist.github.com/raw/4747847/doesntexist.py --></noscript>
+ <noscript><!-- WARNING: Received a 404 response from Gist URL: \
+https://gist.githubusercontent.com/raw/4747847/doesntexist.py --></noscript>
</div>
</p>
@@ -140,11 +214,11 @@ except ImportError:
GIST_JS_URL = "https://gist.github.com/{0}.js"
GIST_FILE_JS_URL = "https://gist.github.com/{0}.js?file={1}"
-GIST_RAW_URL = "https://gist.github.com/raw/{0}"
-GIST_FILE_RAW_URL = "https://gist.github.com/raw/{0}/{1}"
+GIST_RAW_URL = "https://gist.githubusercontent.com/raw/{0}"
+GIST_FILE_RAW_URL = "https://gist.githubusercontent.com/raw/{0}/{1}"
-GIST_MD_RE = r'\[:gist:\s*(?P<gist_id>\d+)(?:\s*(?P<filename>.+?))?\s*\]'
-GIST_RST_RE = r'(?m)^\.\.\s*gist::\s*(?P<gist_id>\d+)(?:\s*(?P<filename>.+))\s*$'
+GIST_MD_RE = r'\[:gist:\s*(?P<gist_id>\S+)(?:\s*(?P<filename>.+?))?\s*\]'
+GIST_RST_RE = r'(?m)^\.\.\s*gist::\s*(?P<gist_id>[^\]\s]+)(?:\s*(?P<filename>.+?))?\s*$'
class GistFetchException(Exception):
@@ -244,6 +318,5 @@ def makeExtension(configs=None):
if __name__ == '__main__':
import doctest
- # Silence user warnings thrown by tests:
doctest.testmod(optionflags=(doctest.NORMALIZE_WHITESPACE +
doctest.REPORT_NDIFF))
diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py
index 6aa737e..ada8035 100644
--- a/nikola/plugins/compile/pandoc.py
+++ b/nikola/plugins/compile/pandoc.py
@@ -30,7 +30,7 @@ You will need, of course, to install pandoc
"""
-import codecs
+import io
import os
import subprocess
@@ -62,7 +62,7 @@ class CompilePandoc(PageCompiler):
makedirs(os.path.dirname(path))
if not content.endswith('\n'):
content += '\n'
- with codecs.open(path, "wb+", "utf8") as fd:
+ with io.open(path, "w+", encoding="utf8") as fd:
if onefile:
fd.write('<!--\n')
fd.write(write_metadata(metadata))
diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py
index 601f098..77344fb 100644
--- a/nikola/plugins/compile/php.py
+++ b/nikola/plugins/compile/php.py
@@ -29,11 +29,11 @@
from __future__ import unicode_literals
import os
-import shutil
-import codecs
+import io
from nikola.plugin_categories import PageCompiler
from nikola.utils import makedirs, write_metadata
+from hashlib import md5
class CompilePhp(PageCompiler):
@@ -43,7 +43,11 @@ class CompilePhp(PageCompiler):
def compile_html(self, source, dest, is_two_file=True):
makedirs(os.path.dirname(dest))
- shutil.copyfile(source, dest)
+ with io.open(dest, "w+", encoding="utf8") as out_file:
+ with open(source, "rb") as in_file:
+ hash = md5(in_file.read()).hexdigest()
+ out_file.write('<!-- __NIKOLA_PHP_TEMPLATE_INJECTION source:{0} checksum:{1}__ -->'.format(source, hash))
+ return True
def create_post(self, path, **kw):
content = kw.pop('content', None)
@@ -53,10 +57,21 @@ class CompilePhp(PageCompiler):
metadata = {}
metadata.update(self.default_metadata)
metadata.update(kw)
- os.makedirs(os.path.dirname(path))
+ if not metadata['description']:
+ # For PHP, a description must be set. Otherwise, Nikola will
+ # take the first 200 characters of the post as the Open Graph
+ # description (og:description meta element)!
+ # If the PHP source leaks there:
+ # (a) The script will be executed multiple times
+ # (b) PHP may encounter a syntax error if it cuts too early,
+ # therefore completely breaking the page
+ # Here, we just use the title. The user should come up with
+ # something better, but just using the title does the job.
+ metadata['description'] = metadata['title']
+ makedirs(os.path.dirname(path))
if not content.endswith('\n'):
content += '\n'
- with codecs.open(path, "wb+", "utf8") as fd:
+ with io.open(path, "w+", encoding="utf8") as fd:
if onefile:
fd.write('<!--\n')
fd.write(write_metadata(metadata))
diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py
index a93199c..98c7151 100644
--- a/nikola/plugins/compile/rest/__init__.py
+++ b/nikola/plugins/compile/rest/__init__.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import unicode_literals
-import codecs
+import io
import os
import re
@@ -58,8 +58,8 @@ class CompileRest(PageCompiler):
req_missing(['docutils'], 'build this site (compile reStructuredText)')
makedirs(os.path.dirname(dest))
error_level = 100
- with codecs.open(dest, "w+", "utf8") as out_file:
- with codecs.open(source, "r", "utf8") as in_file:
+ with io.open(dest, "w+", encoding="utf8") as out_file:
+ with io.open(source, "r", encoding="utf8") as in_file:
data = in_file.read()
add_ln = 0
if not is_two_file:
@@ -83,11 +83,11 @@ class CompileRest(PageCompiler):
'syntax_highlight': 'short',
'math_output': 'mathjax',
'template': default_template_path,
- }, logger=self.logger, l_source=source, l_add_ln=add_ln)
+ }, logger=self.logger, source_path=source, l_add_ln=add_ln)
out_file.write(output)
deps_path = dest + '.dep'
if deps.list:
- with codecs.open(deps_path, "wb+", "utf8") as deps_file:
+ with io.open(deps_path, "w+", encoding="utf8") as deps_file:
deps_file.write('\n'.join(deps.list))
else:
if os.path.isfile(deps_path):
@@ -108,7 +108,7 @@ class CompileRest(PageCompiler):
makedirs(os.path.dirname(path))
if not content.endswith('\n'):
content += '\n'
- with codecs.open(path, "wb+", "utf8") as fd:
+ with io.open(path, "w+", encoding="utf8") as fd:
if onefile:
fd.write(write_metadata(metadata))
fd.write('\n' + content)
@@ -213,7 +213,7 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
parser=None, parser_name='restructuredtext', writer=None,
writer_name='html', settings=None, settings_spec=None,
settings_overrides=None, config_section=None,
- enable_exit_status=None, logger=None, l_source='', l_add_ln=0):
+ enable_exit_status=None, logger=None, l_add_ln=0):
"""
Set up & run a `Publisher`, and return a dictionary of document parts.
Dictionary keys are the names of parts, and values are Unicode strings;
@@ -237,7 +237,7 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
# logger a logger from Nikola
# source source filename (docutils gets a string)
# add_ln amount of metadata lines (see comment in compile_html above)
- reader.l_settings = {'logger': logger, 'source': l_source,
+ reader.l_settings = {'logger': logger, 'source': source_path,
'add_ln': l_add_ln}
pub = docutils.core.Publisher(reader, parser, writer, settings=settings,
@@ -246,7 +246,8 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
pub.set_components(None, parser_name, writer_name)
pub.process_programmatic_settings(
settings_spec, settings_overrides, config_section)
- pub.set_source(source, source_path)
+ pub.set_source(source, None)
+ pub.settings._nikola_source_path = source_path
pub.set_destination(None, destination_path)
pub.publish(enable_exit_status=enable_exit_status)
diff --git a/nikola/plugins/compile/rest/gist.py b/nikola/plugins/compile/rest/gist.py
index e09ed76..65189b5 100644
--- a/nikola/plugins/compile/rest/gist.py
+++ b/nikola/plugins/compile/rest/gist.py
@@ -49,7 +49,10 @@ class GitHubGist(Directive):
def get_raw_gist(self, gistID):
url = "https://gist.github.com/raw/{0}".format(gistID)
- return requests.get(url).text
+ try:
+ return requests.get(url).text
+ except requests.exceptions.RequestException:
+ raise self.error('Cannot get gist for url={0}'.format(url))
def run(self):
if 'https://' in self.arguments[0]:
diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py
index 18a1807..23ec254 100644
--- a/nikola/plugins/compile/rest/listing.py
+++ b/nikola/plugins/compile/rest/listing.py
@@ -29,7 +29,7 @@
from __future__ import unicode_literals
-from codecs import open as codecs_open # for patching purposes
+import io
import os
try:
from urlparse import urlunsplit
@@ -111,7 +111,7 @@ class Listing(Include):
self.options['code'] = lang
if 'linenos' in self.options:
self.options['number-lines'] = self.options['linenos']
- with codecs_open(fpath, 'rb+', 'utf8') as fileobject:
+ with io.open(fpath, 'r+', encoding='utf8') as fileobject:
self.content = fileobject.read().splitlines()
self.state.document.settings.record_dependencies.add(fpath)
target = urlunsplit(("link", 'listing', fname, '', ''))
diff --git a/nikola/plugins/compile/rest/post_list.py b/nikola/plugins/compile/rest/post_list.py
index 456e571..f719e31 100644
--- a/nikola/plugins/compile/rest/post_list.py
+++ b/nikola/plugins/compile/rest/post_list.py
@@ -129,6 +129,7 @@ class PostList(Directive):
else:
post_list_id = self.options.get('id', 'post_list_' + uuid.uuid4().hex)
+ filtered_timeline = []
posts = []
step = -1 if reverse is None else None
if show_all is None:
@@ -136,16 +137,20 @@ class PostList(Directive):
else:
timeline = [p for p in self.site.timeline if p.use_in_feeds]
- for post in timeline[start:stop:step]:
+ for post in timeline:
if tags:
cont = True
+ tags_lower = [t.lower() for t in post.tags]
for tag in tags:
- if tag in [t.lower() for t in post.tags]:
+ if tag in tags_lower:
cont = False
if cont:
continue
+ filtered_timeline.append(post)
+
+ for post in filtered_timeline[start:stop:step]:
if slugs:
cont = True
for slug in slugs:
diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py
index 7437a9d..fca6924 100644
--- a/nikola/plugins/task/bundles.py
+++ b/nikola/plugins/task/bundles.py
@@ -122,8 +122,12 @@ def get_theme_bundles(themes):
if os.path.isfile(bundles_path):
with open(bundles_path) as fd:
for line in fd:
- name, files = line.split('=')
- files = [f.strip() for f in files.split(',')]
- bundles[name.strip().replace('/', os.sep)] = files
+ try:
+ name, files = line.split('=')
+ files = [f.strip() for f in files.split(',')]
+ bundles[name.strip().replace('/', os.sep)] = files
+ except ValueError:
+ # for empty lines
+ pass
break
return bundles
diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py
index 4801347..29aa083 100644
--- a/nikola/plugins/task/copy_assets.py
+++ b/nikola/plugins/task/copy_assets.py
@@ -24,7 +24,9 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-import codecs
+from __future__ import unicode_literals
+
+import io
import os
from nikola.plugin_categories import Task
@@ -82,13 +84,13 @@ class CopyAssets(Task):
from pygments.formatters import get_formatter_by_name
formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
utils.makedirs(os.path.dirname(code_css_path))
- with codecs.open(code_css_path, 'wb+', 'utf8') as outf:
+ with io.open(code_css_path, 'w+', encoding='utf8') as outf:
outf.write(kw["code.css_head"])
outf.write(formatter.get_style_defs(kw["code.css_selectors"]))
outf.write(kw["code.css_close"])
if os.path.exists(code_css_path):
- with codecs.open(code_css_path, 'r', 'utf-8') as fh:
+ with io.open(code_css_path, 'r', encoding='utf-8') as fh:
testcontents = fh.read(len(kw["code.css_head"])) == kw["code.css_head"]
else:
testcontents = False
diff --git a/nikola/plugins/task/copy_files.py b/nikola/plugins/task/copy_files.py
index 9846ca0..1d31756 100644
--- a/nikola/plugins/task/copy_files.py
+++ b/nikola/plugins/task/copy_files.py
@@ -52,4 +52,4 @@ class CopyFiles(Task):
for task in utils.copy_tree(src, real_dst, link_cutoff=dst):
task['basename'] = self.name
task['uptodate'] = [utils.config_changed(kw)]
- yield utils.apply_filters(task, filters)
+ yield utils.apply_filters(task, filters, skip_ext=['.html'])
diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py
index 366374b..f835444 100644
--- a/nikola/plugins/task/galleries.py
+++ b/nikola/plugins/task/galleries.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import unicode_literals
-import codecs
+import io
import datetime
import glob
import json
@@ -55,6 +55,8 @@ from nikola import utils
from nikola.post import Post
from nikola.utils import req_missing
+_image_size_cache = {}
+
class Galleries(Task):
"""Render image galleries."""
@@ -153,6 +155,9 @@ class Galleries(Task):
# Create index.html for each language
for lang in self.kw['translations']:
+ # save navigation links as dependencies
+ self.kw['navigation_links|{0}'.format(lang)] = self.kw['global_context']['navigation_links'](lang)
+
dst = os.path.join(
self.kw['output_folder'],
self.site.path(
@@ -460,8 +465,11 @@ class Galleries(Task):
photo_array = []
for img, thumb, title in zip(img_list, thumbs, img_titles):
- im = Image.open(thumb)
- w, h = im.size
+ w, h = _image_size_cache.get(thumb, (None, None))
+ if w is None:
+ im = Image.open(thumb)
+ w, h = im.size
+ _image_size_cache[thumb] = w, h
# Thumbs are files in output, we need URLs
photo_array.append({
'url': url_from_path(img),
@@ -515,7 +523,7 @@ class Galleries(Task):
rss_obj.rss_attrs["xmlns:dc"] = "http://purl.org/dc/elements/1.1/"
dst_dir = os.path.dirname(output_path)
utils.makedirs(dst_dir)
- with codecs.open(output_path, "wb+", "utf-8") as rss_file:
+ with io.open(output_path, "w+", encoding="utf-8") as rss_file:
data = rss_obj.to_xml(encoding='utf-8')
if isinstance(data, utils.bytes_str):
data = data.decode('utf-8')
diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py
index 386cc18..0a2cd02 100644
--- a/nikola/plugins/task/indexes.py
+++ b/nikola/plugins/task/indexes.py
@@ -147,20 +147,29 @@ class Indexes(Task):
groups[dirname].append(p)
for dirname, post_list in groups.items():
context = {}
- context["items"] = [
- (post.title(lang), post.permalink(lang))
- for post in post_list
- ]
+ context["items"] = []
+ should_render = True
output_name = os.path.join(kw['output_folder'], dirname, kw['index_file'])
- task = self.site.generic_post_list_renderer(lang, post_list,
- output_name,
- template_name,
- kw['filters'],
- context)
- task_cfg = {1: task['uptodate'][0].config, 2: kw}
- task['uptodate'] = [config_changed(task_cfg)]
- task['basename'] = self.name
- yield task
+ short_destination = os.path.join(dirname, kw['index_file'])
+ for post in post_list:
+ # If there is an index.html pending to be created from
+ # a story, do not generate the STORY_INDEX
+ if post.destination_path(lang) == short_destination:
+ should_render = False
+ else:
+ context["items"].append((post.title(lang),
+ post.permalink(lang)))
+
+ if should_render:
+ task = self.site.generic_post_list_renderer(lang, post_list,
+ output_name,
+ template_name,
+ kw['filters'],
+ context)
+ task_cfg = {1: task['uptodate'][0].config, 2: kw}
+ task['uptodate'] = [config_changed(task_cfg)]
+ task['basename'] = self.name
+ yield task
def index_path(self, name, lang):
if name not in [None, 0]:
diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py
index a0fe974..79f6763 100644
--- a/nikola/plugins/task/listings.py
+++ b/nikola/plugins/task/listings.py
@@ -73,7 +73,7 @@ class Listings(Task):
code = highlight(fd.read(), lexer,
HtmlFormatter(cssclass='code',
linenos="table", nowrap=False,
- lineanchors=utils.slugify(in_name),
+ lineanchors=utils.slugify(in_name, force=True),
anchorlinenos=True))
# the pygments highlighter uses <div class="codehilite"><pre>
# for code. We switch it to reST's <pre class="code">.
@@ -124,6 +124,9 @@ class Listings(Task):
for k in self.site._GLOBAL_CONTEXT_TRANSLATABLE:
uptodate[k] = self.site.GLOBAL_CONTEXT[k](kw['default_lang'])
+ # save navigation links as dependencies
+ uptodate['navigation_links'] = uptodate['c']['navigation_links'](kw['default_lang'])
+
uptodate2 = uptodate.copy()
uptodate2['f'] = files
uptodate2['d'] = dirs
diff --git a/nikola/plugins/task/redirect.py b/nikola/plugins/task/redirect.py
index eccc0ab..e1134bf 100644
--- a/nikola/plugins/task/redirect.py
+++ b/nikola/plugins/task/redirect.py
@@ -24,7 +24,7 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-import codecs
+import io
import os
from nikola.plugin_categories import Task
@@ -60,7 +60,8 @@ class Redirect(Task):
def create_redirect(src, dst):
utils.makedirs(os.path.dirname(src))
- with codecs.open(src, "wb+", "utf8") as fd:
+ with io.open(src, "w+", encoding="utf8") as fd:
fd.write('<!DOCTYPE html><head><title>Redirecting...</title>'
+ '<meta name="robots" content="noindex">'
'<meta http-equiv="refresh" content="0; '
'url={0}"></head><body><p>Page moved <a href="{0}">here</a></p></body>'.format(dst))
diff --git a/nikola/plugins/task/robots.py b/nikola/plugins/task/robots.py
index 9944c0d..b229d37 100644
--- a/nikola/plugins/task/robots.py
+++ b/nikola/plugins/task/robots.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function, absolute_import, unicode_literals
-import codecs
+import io
import os
try:
from urlparse import urljoin, urlparse
@@ -51,14 +51,14 @@ class RobotsFile(LateTask):
"robots_exclusions": self.site.config["ROBOTS_EXCLUSIONS"]
}
- if kw["site_url"] != urljoin(kw["site_url"], "/"):
- utils.LOGGER.warn('robots.txt not ending up in server root, will be useless')
-
sitemapindex_url = urljoin(kw["base_url"], "sitemapindex.xml")
robots_path = os.path.join(kw['output_folder'], "robots.txt")
def write_robots():
- with codecs.open(robots_path, 'wb+', 'utf8') as outf:
+ if kw["site_url"] != urljoin(kw["site_url"], "/"):
+ utils.LOGGER.warn('robots.txt not ending up in server root, will be useless')
+
+ with io.open(robots_path, 'w+', encoding='utf8') as outf:
outf.write("Sitemap: {0}\n\n".format(sitemapindex_url))
if kw["robots_exclusions"]:
outf.write("User-Agent: *\n")
diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py
index beac6cb..943e9b2 100644
--- a/nikola/plugins/task/sitemap/__init__.py
+++ b/nikola/plugins/task/sitemap/__init__.py
@@ -25,7 +25,7 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function, absolute_import, unicode_literals
-import codecs
+import io
import datetime
import os
try:
@@ -150,7 +150,7 @@ class Sitemap(LateTask):
continue
if path.endswith('.html') or path.endswith('.htm'):
try:
- if u'<!doctype html' not in codecs.open(real_path, 'r', 'utf8').read(1024).lower():
+ if u'<!doctype html' not in io.open(real_path, 'r', encoding='utf8').read(1024).lower():
# ignores "html" files without doctype
# alexa-verify, google-site-verification, etc.
continue
@@ -160,7 +160,8 @@ class Sitemap(LateTask):
continue
""" put RSS in sitemapindex[] instead of in urlset[], sitemap_path is included after it is generated """
if path.endswith('.xml') or path.endswith('.rss'):
- if u'<rss' in codecs.open(real_path, 'r', 'utf8').read(512) or u'<urlset'and path != sitemap_path:
+ filehead = io.open(real_path, 'r', encoding='utf8').read(512)
+ if u'<rss' in filehead or (u'<urlset' in filehead and path != sitemap_path):
path = path.replace(os.sep, '/')
lastmod = self.get_lastmod(real_path)
loc = urljoin(base_url, base_path + path)
@@ -187,7 +188,7 @@ class Sitemap(LateTask):
def write_sitemap():
# Have to rescan, because files may have been added between
# task dep scanning and task execution
- with codecs.open(sitemap_path, 'wb+', 'utf8') as outf:
+ with io.open(sitemap_path, 'w+', encoding='utf8') as outf:
outf.write(urlset_header)
for k in sorted(urlset.keys()):
outf.write(urlset[k])
@@ -196,7 +197,7 @@ class Sitemap(LateTask):
sitemapindex[sitemap_url] = sitemap_format.format(sitemap_url, self.get_lastmod(sitemap_path))
def write_sitemapindex():
- with codecs.open(sitemapindex_path, 'wb+', 'utf8') as outf:
+ with io.open(sitemapindex_path, 'w+', encoding='utf8') as outf:
outf.write(sitemapindex_header)
for k in sorted(sitemapindex.keys()):
outf.write(sitemapindex[k])
diff --git a/nikola/plugins/task/sources.py b/nikola/plugins/task/sources.py
index 2324af2..4c669c2 100644
--- a/nikola/plugins/task/sources.py
+++ b/nikola/plugins/task/sources.py
@@ -60,11 +60,11 @@ class Sources(Task):
continue
output_name = os.path.join(
kw['output_folder'], post.destination_path(
- lang, post.source_ext()))
- source = post.source_path
- dest_ext = self.site.get_compiler(post.source_path).extension()
- if dest_ext == post.source_ext():
+ lang, post.source_ext(True)))
+ # do not publish PHP sources
+ if post.source_ext(True) == post.compiler.extension():
continue
+ source = post.source_path
if lang != kw["default_lang"]:
source_lang = utils.get_translation_candidate(self.site.config, source, lang)
if os.path.exists(source_lang):
diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py
index f7f3579..8d43f13 100644
--- a/nikola/plugins/task/tags.py
+++ b/nikola/plugins/task/tags.py
@@ -25,7 +25,6 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import unicode_literals
-import codecs
import json
import os
try:
@@ -124,8 +123,8 @@ class RenderTags(Task):
def write_tag_data(data):
utils.makedirs(os.path.dirname(output_name))
- with codecs.open(output_name, 'wb+', 'utf8') as fd:
- fd.write(json.dumps(data))
+ with open(output_name, 'w+') as fd:
+ json.dump(data, fd)
task = {
'basename': str(self.name),
@@ -169,7 +168,7 @@ class RenderTags(Task):
else:
context["cat_items"] = None
context["permalink"] = self.site.link("tag_index", None, lang)
- context["description"] = None
+ context["description"] = context["title"]
task = self.site.generic_post_list_renderer(
lang,
[],
@@ -231,7 +230,7 @@ class RenderTags(Task):
page_name(tag, i + 1, lang))
context["permalink"] = self.site.link(kind, tag, lang)
context["tag"] = tag
- context["description"] = None
+ context["description"] = context["title"]
task = self.site.generic_post_list_renderer(
lang,
post_list,
@@ -259,7 +258,7 @@ class RenderTags(Task):
context["permalink"] = self.site.link(kind, tag, lang)
context["tag"] = tag
context["kind"] = kind
- context["description"] = None
+ context["description"] = context["title"]
task = self.site.generic_post_list_renderer(
lang,
post_list,
diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py
index 097ec96..5156f38 100644
--- a/nikola/plugins/template/jinja.py
+++ b/nikola/plugins/template/jinja.py
@@ -55,6 +55,8 @@ class JinjaTemplates(TemplateSystem):
self.lookup.lstrip_blocks = True
self.lookup.filters['tojson'] = json.dumps
self.lookup.globals['enumerate'] = enumerate
+ self.lookup.globals['isinstance'] = isinstance
+ self.lookup.globals['tuple'] = tuple
def set_directories(self, directories, cache_folder):
"""Create a template lookup."""
diff --git a/nikola/post.py b/nikola/post.py
index 3e3b608..350014a 100644
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -26,7 +26,7 @@
from __future__ import unicode_literals, print_function, absolute_import
-import codecs
+import io
from collections import defaultdict
import datetime
import os
@@ -318,8 +318,12 @@ class Post(object):
deps = []
if self.default_lang in self.translated_to:
deps.append(self.base_path)
+ deps.append(self.source_path)
if lang != self.default_lang:
- deps += [get_translation_candidate(self.config, self.base_path, lang)]
+ cand_1 = get_translation_candidate(self.config, self.source_path, lang)
+ cand_2 = get_translation_candidate(self.config, self.base_path, lang)
+ if os.path.exists(cand_1):
+ deps.extend([cand_1, cand_2])
return deps
def compile(self, lang):
@@ -327,10 +331,10 @@ class Post(object):
def wrap_encrypt(path, password):
"""Wrap a post with encryption."""
- with codecs.open(path, 'rb+', 'utf8') as inf:
+ with io.open(path, 'r+', encoding='utf8') as inf:
data = inf.read() + "<!--tail-->"
data = CRYPT.substitute(data=rc4(password, data))
- with codecs.open(path, 'wb+', 'utf8') as outf:
+ with io.open(path, 'w+', encoding='utf8') as outf:
outf.write(data)
dest = self.translated_base_path(lang)
@@ -354,7 +358,7 @@ class Post(object):
"""
dep_path = self.base_path + '.dep'
if os.path.isfile(dep_path):
- with codecs.open(dep_path, 'rb+', 'utf8') as depf:
+ with io.open(dep_path, 'r+', encoding='utf8') as depf:
return [l.strip() for l in depf.readlines()]
return []
@@ -416,24 +420,14 @@ class Post(object):
All links in the returned HTML will be relative.
The HTML returned is a bare fragment, not a full document.
"""
- def strip_root_element(el):
- ''' Strips root tag from an Element.
-
- Required because lxml has an tendency to add <div>, <body>
- root tags to strings which are generated by using
- lxml.html.tostring()
-
- :param Element el: the root element to strip
- '''
- return (el.text or '') + ''.join(
- [lxml.html.tostring(child, encoding='unicode')
- for child in el.iterchildren()])
if lang is None:
lang = nikola.utils.LocaleBorg().current_lang
file_name = self._translated_file_path(lang)
- with codecs.open(file_name, "r", "utf8") as post_file:
+ with io.open(file_name, "r", encoding="utf8") as post_file:
data = post_file.read().strip()
+ if self.compiler.extension() == '.php':
+ return data
try:
document = lxml.html.fragment_fromstring(data, "body")
except lxml.etree.ParserError as e:
@@ -448,13 +442,10 @@ class Post(object):
if self.hyphenate:
hyphenate(document, lang)
- data = lxml.html.tostring(document, encoding='unicode')
- # data here is a full HTML doc, including HTML and BODY tags
- # which is not ideal (Issue #464)
try:
- data = strip_root_element(document.body)
- except IndexError: # No body there, it happens sometimes
- pass
+ data = lxml.html.tostring(document.body, encoding='unicode')
+ except:
+ data = lxml.html.tostring(document, encoding='unicode')
if teaser_only:
teaser = TEASER_REGEXP.split(data)[0]
@@ -477,7 +468,7 @@ class Post(object):
# This closes all open tags and sanitizes the broken HTML
document = lxml.html.fromstring(teaser)
try:
- data = strip_root_element(document)
+ data = lxml.html.tostring(document.body, encoding='unicode')
except IndexError:
data = lxml.html.tostring(document, encoding='unicode')
@@ -494,7 +485,7 @@ class Post(object):
try:
document = lxml.html.fromstring(data)
demote_headers(document, self.demote_headers)
- data = strip_root_element(document)
+ data = lxml.html.tostring(document.body, encoding='unicode')
except (lxml.etree.ParserError, IndexError):
data = lxml.html.tostring(document, encoding='unicode')
@@ -527,7 +518,7 @@ class Post(object):
# duplicated with Post.text()
lang = nikola.utils.LocaleBorg().current_lang
file_name = self._translated_file_path(lang)
- with codecs.open(file_name, "r", "utf8") as post_file:
+ with io.open(file_name, "r", encoding="utf8") as post_file:
data = post_file.read().strip()
try:
document = lxml.html.fragment_fromstring(data, "body")
@@ -561,9 +552,10 @@ class Post(object):
def source_link(self, lang=None):
"""Return absolute link to the post's source."""
+ ext = self.source_ext(True)
return "/" + self.destination_path(
lang=lang,
- extension=self.source_ext(),
+ extension=ext,
sep='/')
def destination_path(self, lang=None, extension='.html', sep=os.sep):
@@ -588,6 +580,10 @@ class Post(object):
if lang is None:
lang = nikola.utils.LocaleBorg().current_lang
+ # Let compilers override extension (e.g. the php compiler)
+ if self.compiler.extension() != '.html':
+ extension = self.compiler.extension()
+
pieces = self.translations[lang].split(os.sep)
pieces += self.folder.split(os.sep)
if self._has_pretty_url(lang):
@@ -604,8 +600,21 @@ class Post(object):
else:
return link
- def source_ext(self):
- return os.path.splitext(self.source_path)[1]
+ def source_ext(self, prefix=False):
+ """
+ Return the source file extension.
+
+ If `prefix` is True, a `.src.` prefix will be added to the resulting extension
+ if it’s equal to the destination extension.
+ """
+
+ ext = os.path.splitext(self.source_path)[1]
+ # do not publish PHP sources
+ if prefix and ext == '.html':
+ # ext starts with a dot
+ return '.src' + ext
+ else:
+ return ext
# Code that fetches metadata from different places
@@ -655,7 +664,7 @@ def get_metadata_from_file(source_path, config=None, lang=None):
source_path = get_translation_candidate(config, source_path, lang)
elif lang:
source_path += '.' + lang
- with codecs.open(source_path, "r", "utf8") as meta_file:
+ with io.open(source_path, "r", encoding="utf8") as meta_file:
meta_data = [x.strip() for x in meta_file.readlines()]
return _get_metadata_from_file(meta_data)
except (UnicodeDecodeError, UnicodeEncodeError):
@@ -723,7 +732,7 @@ def get_metadata_from_meta_file(path, config=None, lang=None):
elif lang:
meta_path += '.' + lang
if os.path.isfile(meta_path):
- with codecs.open(meta_path, "r", "utf8") as meta_file:
+ with io.open(meta_path, "r", encoding="utf8") as meta_file:
meta_data = meta_file.readlines()
# Detect new-style metadata.
diff --git a/nikola/utils.py b/nikola/utils.py
index 9420595..87826ff 100644
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -85,6 +85,8 @@ STDERR_HANDLER = [ColorfulStderrHandler(
LOGGER = get_logger('Nikola', STDERR_HANDLER)
STRICT_HANDLER = ExceptionHandler(ApplicationWarning, level='WARNING')
+USE_SLUGIFY = True
+
# This will block out the default handler and will hide all unwanted
# messages, properly.
logbook.NullHandler().push_application()
@@ -170,6 +172,7 @@ else:
from doit import tools
from unidecode import unidecode
from pkg_resources import resource_filename
+from nikola import filters as task_filters
import PyRSS2Gen as rss
@@ -668,11 +671,11 @@ def remove_file(source):
# slugify is copied from
# http://code.activestate.com/recipes/
# 577257-slugify-make-a-string-usable-in-a-url-or-filename/
-_slugify_strip_re = re.compile(r'[^\w\s-]')
+_slugify_strip_re = re.compile(r'[^+\w\s-]')
_slugify_hyphenate_re = re.compile(r'[-\s]+')
-def slugify(value):
+def slugify(value, force=False):
"""
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
@@ -691,11 +694,26 @@ def slugify(value):
"""
if not isinstance(value, unicode_str):
raise ValueError("Not a unicode object: {0}".format(value))
- value = unidecode(value)
- # WARNING: this may not be python2/3 equivalent
- # value = unicode(_slugify_strip_re.sub('', value).strip().lower())
- value = str(_slugify_strip_re.sub('', value).strip().lower())
- return _slugify_hyphenate_re.sub('-', value)
+ if USE_SLUGIFY or force:
+ # This is the standard state of slugify, which actually does some work.
+ # It is the preferred style, especially for Western languages.
+ value = unidecode(value)
+ value = str(_slugify_strip_re.sub('', value).strip().lower())
+ return _slugify_hyphenate_re.sub('-', value)
+ else:
+ # This is the “disarmed” state of slugify, which lets the user
+ # have any character they please (be it regular ASCII with spaces,
+ # or another alphabet entirely). This might be bad in some
+ # environments, and as such, USE_SLUGIFY is better off being True!
+
+ # We still replace some characters, though. In particular, we need
+ # to replace ? and #, which should not appear in URLs, and some
+ # Windows-unsafe characters. This list might be even longer.
+ rc = '/\\?#"\'\r\n\t*:<>|"'
+
+ for c in rc:
+ value = value.replace(c, '-')
+ return value
def unslugify(value, discard_numbers=True):
@@ -769,7 +787,7 @@ def current_time(tzinfo=None):
return dt
-def apply_filters(task, filters):
+def apply_filters(task, filters, skip_ext=None):
"""
Given a task, checks its targets.
If any of the targets has a filter that matches,
@@ -777,6 +795,12 @@ def apply_filters(task, filters):
and the filter itself to the uptodate of the task.
"""
+ if '.php' in filters.keys():
+ if task_filters.php_template_injection not in filters['.php']:
+ filters['.php'].append(task_filters.php_template_injection)
+ else:
+ filters['.php'] = [task_filters.php_template_injection]
+
def filter_matches(ext):
for key, value in list(filters.items()):
if isinstance(key, (tuple, list)):
@@ -790,6 +814,8 @@ def apply_filters(task, filters):
for target in task.get('targets', []):
ext = os.path.splitext(target)[-1].lower()
+ if skip_ext and ext in skip_ext:
+ continue
filter_ = filter_matches(ext)
if filter_:
for action in filter_: