Skip to content

Commit

Permalink
Merge pull request #2100 from getnikola/feed-previewimage
Browse files Browse the repository at this point in the history
New option FEED_PREVIEWIMAGE includes post previewimage meta
  • Loading branch information
da2x committed Sep 20, 2015
2 parents a390487 + b417c82 commit 24c86aa
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 68 deletions.
7 changes: 7 additions & 0 deletions CHANGES.txt
Expand Up @@ -4,10 +4,17 @@ New in master
Features
--------

* New option ``FEED_PREVIEWIMAGE`` includes the ``post.meta.previewimage``
image in Atom and RSS feeds. (Issue #2095)

Bugfixes
--------

* Fix reST post list date formatting error (Issue #2104)
* Deprecated ``RSS_TEASERS``, ``RSS_PLAIN``, ``RSS_READ_MORE_LINK``, and
``RSS_LINKS_APPEND_QUERY`` in favor of ``FEED_TEASERS``, ``FEED_PLAIN``,
``FEED_READ_MORE_LINK``, and ``FEED_LINKS_APPEND_QUERY`` for both Atom
and RSS feeds. (Issue #2095)
* /robots.txt was never being built (Issue #2098)

New in v7.7.1
Expand Down
4 changes: 2 additions & 2 deletions docs/manual.txt
Expand Up @@ -515,8 +515,8 @@ In Markdown (or basically, the resulting HTML of any format):
By default all your RSS feeds will be shortened (they'll contain only teasers)
whereas your index page will still show complete posts. You can change
this behaviour with your ``conf.py``: ``INDEX_TEASERS`` defines whether index
page should display the whole contents or only teasers. ``RSS_TEASERS``
works the same way for your RSS feeds.
page should display the whole contents or only teasers. ``FEED_TEASERS``
works the same way for your Atom and RSS feeds.

By default, teasers will include a "read more" link at the end. If you want to
change that text, you can use a custom teaser:
Expand Down
35 changes: 21 additions & 14 deletions nikola/conf.py.in
Expand Up @@ -425,9 +425,6 @@ HIDDEN_AUTHORS = ['Guest']
# output / TRANSLATION[lang] / RSS_PATH / rss.xml
# RSS_PATH = ""

# Number of posts in RSS feeds
# FEED_LENGTH = 10

# Slug the Tag URL. Easier for users to type, special characters are
# often removed or replaced as well.
# SLUG_TAG_PATH = True
Expand Down Expand Up @@ -677,18 +674,18 @@ IMAGE_FOLDERS = {'images': 'images'}

# 'Read more...' for the index page, if INDEX_TEASERS is True (translatable)
INDEX_READ_MORE_LINK = ${INDEX_READ_MORE_LINK}
# 'Read more...' for the RSS_FEED, if RSS_TEASERS is True (translatable)
RSS_READ_MORE_LINK = ${RSS_READ_MORE_LINK}
# 'Read more...' for the RSS_FEED, if FEED_TEASERS is True (translatable)
FEED_READ_MORE_LINK = ${FEED_READ_MORE_LINK}

# Append a URL query to the RSS_READ_MORE_LINK in Atom and RSS feeds. Advanced
# Append a URL query to the FEED_READ_MORE_LINK in Atom and RSS feeds. Advanced
# option used for traffic source tracking.
# Minimum example for use with Piwik: "pk_campaign=feed"
# The following tags exist and are replaced for you:
# {feedRelUri} A relative link to the feed.
# {feedFormat} The name of the syndication format.
# Example using replacement for use with Google Analytics:
# "utm_source={feedRelUri}&utm_medium=nikola_feed&utm_campaign={feedFormat}_feed"
RSS_LINKS_APPEND_QUERY = False
FEED_LINKS_APPEND_QUERY = False

# A HTML fragment describing the license, for the sidebar.
# (translatable)
Expand Down Expand Up @@ -892,22 +889,32 @@ MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite', 'extra']
# them. Generate Atom for tags by setting TAG_PAGES_ARE_INDEXES to True.
# Atom feeds are built based on INDEX_DISPLAY_POST_COUNT and not FEED_LENGTH
# Switch between plain-text summaries and full HTML content using the
# RSS_TEASER option. RSS_LINKS_APPEND_QUERY is also respected. Atom feeds
# FEED_TEASER option. FEED_LINKS_APPEND_QUERY is also respected. Atom feeds
# are generated even for old indexes and have pagination link relations
# between each other. Old Atom feeds with no changes are marked as archived.
# GENERATE_ATOM = False

# Only inlclude teasers in Atom and RSS feeds. Disabling include the full
# content. Defaults to True.
# FEED_TEASERS = True

# Strip HTML from Atom annd RSS feed summaries and content. Defaults to False.
# FEED_PLAIN = False

# Number of posts in Atom and RSS feeds.
# FEED_LENGTH = 10

# Inclue preview image as a <figure><img></figure> at the top of the entry.
# Requires FEED_PLAIN = False. If the preview image is found in the content,
# it will not be included again. Image will be included as-is, aim to optmize
# the image source for Feedly, Apple News, Flipboard, and other popular clients.
# FEED_PREVIEWIMAGE = True

# RSS_LINK is a HTML fragment to link the RSS or Atom feeds. If set to None,
# the base.tmpl will use the feed Nikola generates. However, you may want to
# change it for a FeedBurner feed or something else.
# RSS_LINK = None

# Show teasers (instead of full posts) in feeds? Defaults to True.
# RSS_TEASERS = True

# Strip HTML in the RSS feed? Default to False
# RSS_PLAIN = False

# A search form to search this site, for the sidebar. You can use a Google
# custom search (https://www.google.com/cse/)
# Or a DuckDuckGo search: https://duckduckgo.com/search_box.html
Expand Down
108 changes: 81 additions & 27 deletions nikola/nikola.py
Expand Up @@ -80,7 +80,7 @@

# Default "Read more..." link
DEFAULT_INDEX_READ_MORE_LINK = '<p class="more"><a href="{link}">{read_more}…</a></p>'
DEFAULT_RSS_READ_MORE_LINK = '<p><a href="{link}">{read_more}…</a> ({min_remaining_read})</p>'
DEFAULT_FEED_READ_MORE_LINK = '<p><a href="{link}">{read_more}…</a> ({min_remaining_read})</p>'

# Default pattern for translation files' names
DEFAULT_TRANSLATIONS_PATTERN = '{path}.{lang}.{ext}'
Expand Down Expand Up @@ -450,16 +450,17 @@ def __init__(self, **config):
'PRETTY_URLS': False,
'FUTURE_IS_NOW': False,
'INDEX_READ_MORE_LINK': DEFAULT_INDEX_READ_MORE_LINK,
'RSS_READ_MORE_LINK': DEFAULT_RSS_READ_MORE_LINK,
'RSS_LINKS_APPEND_QUERY': False,
'REDIRECTIONS': [],
'ROBOTS_EXCLUSIONS': [],
'GENERATE_ATOM': False,
'FEED_TEASERS': True,
'FEED_PLAIN': False,
'FEED_PREVIEWIMAGE': True,
'FEED_READ_MORE_LINK': DEFAULT_FEED_READ_MORE_LINK,
'FEED_LINKS_APPEND_QUERY': False,
'GENERATE_RSS': True,
'RSS_LINK': None,
'RSS_PATH': '',
'RSS_PLAIN': False,
'RSS_TEASERS': True,
'SASS_COMPILER': 'sass',
'SASS_OPTIONS': [],
'SEARCH_FORM': '',
Expand Down Expand Up @@ -543,7 +544,7 @@ def __init__(self, **config):
'EXTRA_HEAD_DATA',
'NAVIGATION_LINKS',
'INDEX_READ_MORE_LINK',
'RSS_READ_MORE_LINK',
'FEED_READ_MORE_LINK',
'INDEXES_TITLE',
'POSTS_SECTION_COLORS',
'POSTS_SECTION_DESCRIPTIONS',
Expand Down Expand Up @@ -608,6 +609,38 @@ def __init__(self, **config):
for i1, i2, i3 in self.config['PAGES']:
self.config['post_pages'].append([i1, i2, i3, False])

# RSS_TEASERS has been replaced with FEED_TEASERS
# TODO: remove on v8
if 'RSS_TEASERS' in config:
utils.LOGGER.warn('The RSS_TEASERS option is deprecated, use FEED_TEASERS instead.')
if 'FEED_TEASERS' in config:
utils.LOGGER.warn('FEED_TEASERS conflicts with RSS_TEASERS, ignoring RSS_TEASERS.')
self.config['FEED_TEASERS'] = config['RSS_TEASERS']

# RSS_PLAIN has been replaced with FEED_PLAIN
# TODO: remove on v8
if 'RSS_PLAIN' in config:
utils.LOGGER.warn('The RSS_PLAIN option is deprecated, use FEED_PLAIN instead.')
if 'FEED_PLAIN' in config:
utils.LOGGER.warn('FEED_PLIN conflicts with RSS_PLAIN, ignoring RSS_PLAIN.')
self.config['FEED_PLAIN'] = config['RSS_PLAIN']

# RSS_LINKS_APPEND_QUERY has been replaced with FEED_LINKS_APPEND_QUERY
# TODO: remove on v8
if 'RSS_LINKS_APPEND_QUERY' in config:
utils.LOGGER.warn('The RSS_LINKS_APPEND_QUERY option is deprecated, use FEED_LINKS_APPEND_QUERY instead.')
if 'FEED_TEASERS' in config:
utils.LOGGER.warn('FEED_LINKS_APPEND_QUERY conflicts with RSS_LINKS_APPEND_QUERY, ignoring RSS_LINKS_APPEND_QUERY.')
self.config['FEED_LINKS_APPEND_QUERY'] = utils.TranslatableSetting('FEED_LINKS_APPEND_QUERY', config['RSS_LINKS_APPEND_QUERY'], self.config['TRANSLATIONS'])

# RSS_READ_MORE_LINK has been replaced with FEED_READ_MORE_LINK
# TODO: remove on v8
if 'RSS_READ_MORE_LINK' in config:
utils.LOGGER.warn('The RSS_READ_MORE_LINK option is deprecated, use FEED_READ_MORE_LINK instead.')
if 'FEED_READ_MORE_LINK' in config:
utils.LOGGER.warn('FEED_READ_MORE_LINK conflicts with RSS_READ_MORE_LINK, ignoring RSS_READ_MORE_LINK')
self.config['FEED_READ_MORE_LINK'] = utils.TranslatableSetting('FEED_READ_MORE_LINK', config['RSS_READ_MORE_LINK'], self.config['TRANSLATIONS'])

# DEFAULT_TRANSLATIONS_PATTERN was changed from "p.e.l" to "p.l.e"
# TODO: remove on v8
if 'TRANSLATIONS_PATTERN' not in self.config:
Expand Down Expand Up @@ -1280,10 +1313,12 @@ def generic_rss_renderer(self, lang, title, link, description, timeline, output_

for post in timeline[:feed_length]:
data = post.text(lang, teaser_only=rss_teasers, strip_html=rss_plain,
rss_read_more_link=True, rss_links_append_query=feed_append_query)
feed_read_more_link=True, feed_links_append_query=feed_append_query)
if feed_url is not None and data:
# Massage the post's HTML (unless plain)
if not rss_plain:
if self.config["FEED_PREVIEWIMAGE"] and 'previewimage' in post.meta[lang] and post.meta[lang]['previewimage'] not in data:
data = "<figure><img src=\"{}\"></figure> {}".format(post.meta[lang]['previewimage'], data)
# FIXME: this is duplicated with code in Post.text()
try:
doc = lxml.html.document_fromstring(data)
Expand Down Expand Up @@ -1835,8 +1870,6 @@ def atom_link(link_rel, link_type, link_href):
nslist = {}
if context["is_feed_stale"] or "feedpagenum" in context and (not context["feedpagenum"] == context["feedpagecount"] - 1 and not context["feedpagenum"] == 0):
nslist["fh"] = "http://purl.org/syndication/history/1.0"
if not self.config["RSS_TEASERS"]:
nslist["xh"] = "http://www.w3.org/1999/xhtml"
feed_xsl_link = self.abs_link("/assets/xml/atom.xsl")
feed_root = lxml.etree.Element("feed", nsmap=nslist)
feed_root.addprevious(lxml.etree.ProcessingInstruction(
Expand Down Expand Up @@ -1881,31 +1914,45 @@ def atom_link(link_rel, link_type, link_href):
feed_generator.text = "Nikola"

feed_append_query = None
if self.config["RSS_LINKS_APPEND_QUERY"]:
feed_append_query = self.config["RSS_LINKS_APPEND_QUERY"].format(
if self.config["FEED_LINKS_APPEND_QUERY"]:
feed_append_query = self.config["FEED_LINKS_APPEND_QUERY"].format(
feedRelUri=context["feedlink"],
feedFormat="atom")

for post in posts:
data = post.text(lang, teaser_only=self.config["RSS_TEASERS"], strip_html=self.config["RSS_TEASERS"],
rss_read_more_link=True, rss_links_append_query=feed_append_query)
if not self.config["RSS_TEASERS"]:
def atom_post_text(post, text):
if not self.config["FEED_PLAIN"]:
if self.config["FEED_PREVIEWIMAGE"] and 'previewimage' in post.meta[lang] and post.meta[lang]['previewimage'] not in text:
text = "<figure><img src=\"{}\"></figure> {}".format(post.meta[lang]['previewimage'], text)

# FIXME: this is duplicated with code in Post.text() and generic_rss_renderer
try:
doc = lxml.html.document_fromstring(data)
doc = lxml.html.document_fromstring(text)
doc.rewrite_links(lambda dst: self.url_replacer(post.permalink(lang), dst, lang, 'absolute'))
try:
body = doc.body
data = (body.text or '') + ''.join(
text = (body.text or '') + ''.join(
[lxml.html.tostring(child, encoding='unicode')
for child in body.iterchildren()])
except IndexError: # No body there, it happens sometimes
data = ''
text = ''
except lxml.etree.ParserError as e:
if str(e) == "Document is empty":
data = ""
text = ""
else: # let other errors raise
raise(e)
return text

for post in posts:
summary = atom_post_text(post, post.text(lang, teaser_only=True,
strip_html=self.config["FEED_PLAIN"],
feed_read_more_link=True,
feed_links_append_query=feed_append_query))
content = None
if not self.config["FEED_TEASERS"]:
content = atom_post_text(post, post.text(lang, teaser_only=self.config["FEED_TEASERS"],
strip_html=self.config["FEED_PLAIN"],
feed_read_more_link=True,
feed_links_append_query=feed_append_query))

entry_root = lxml.etree.SubElement(feed_root, "entry")
entry_title = lxml.etree.SubElement(entry_root, "title")
Expand All @@ -1922,14 +1969,19 @@ def atom_link(link_rel, link_type, link_href):
entry_root.append(atom_link("alternate", "text/html",
post.permalink(lang, absolute=True,
query=feed_append_query)))
if self.config["RSS_TEASERS"]:
entry_summary = lxml.etree.SubElement(entry_root, "summary")
entry_summary.text = data
entry_summary = lxml.etree.SubElement(entry_root, "summary")
if not self.config["FEED_PLAIN"]:
entry_summary.set("type", "html")
else:
entry_summary.set("type", "text")
entry_summary.text = summary
if content:
entry_content = lxml.etree.SubElement(entry_root, "content")
entry_content.set("type", "xhtml")
entry_content_nsdiv = lxml.etree.SubElement(entry_content, "{http://www.w3.org/1999/xhtml}div")
entry_content_nsdiv.text = data
if not self.config["FEED_PLAIN"]:
entry_content.set("type", "html")
else:
entry_content.set("type", "text")
entry_content.text = content
for category in post.tags_for_language(lang):
entry_category = lxml.etree.SubElement(entry_root, "category")
entry_category.set("term", utils.slugify(category))
Expand Down Expand Up @@ -1980,8 +2032,7 @@ def generic_index_renderer(self, lang, posts, indexes_title, template_name, cont
kw['indexes_prety_page_url'] = self.config["INDEXES_PRETTY_PAGE_URL"]
kw['demote_headers'] = self.config['DEMOTE_HEADERS']
kw['generate_atom'] = self.config["GENERATE_ATOM"]
kw['feed_link_append_query'] = self.config["RSS_LINKS_APPEND_QUERY"]
kw['feed_teasers'] = self.config["RSS_TEASERS"]
kw['feed_link_append_query'] = self.config["FEED_LINKS_APPEND_QUERY"]
kw['currentfeed'] = None

# Split in smaller lists
Expand Down Expand Up @@ -2072,6 +2123,9 @@ def generic_index_renderer(self, lang, posts, indexes_title, template_name, cont
context["currentfeedlink"] = kw["currentfeed"]
context["feedpagenum"] = i
context["feedpagecount"] = num_pages
kw['feed_teasers'] = self.config['FEED_TEASERS']
kw['feed_plain'] = self.config['FEED_PLAIN']
kw['feed_previewimage'] = self.config['FEED_PREVIEWIMAGE']
atom_task = {
"basename": basename,
"name": atom_output_name,
Expand Down
2 changes: 1 addition & 1 deletion nikola/plugins/command/check.py
Expand Up @@ -227,7 +227,7 @@ def analyze(self, fname, find_sources=False, check_remote=False):
d = lxml.etree.parse(filename)
link_elements = lxml.html.fromstring('<html/>')
for elm in d.findall('*//{http://www.w3.org/2005/Atom}link'):
feed_link = elm.attrib['href'].split('?')[0].strip() # strip RSS_LINKS_APPEND_QUERY
feed_link = elm.attrib['href'].split('?')[0].strip() # strip FEED_LINKS_APPEND_QUERY
link_elements.append(lxml.etree.Element('a', href=feed_link))
link_elements = list(link_elements.iterlinks())
elif filename.endswith('sitemap.xml') or filename.endswith('sitemapindex.xml'):
Expand Down
8 changes: 4 additions & 4 deletions nikola/plugins/command/init.py
Expand Up @@ -41,7 +41,7 @@
import tarfile

import nikola
from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN, DEFAULT_INDEX_READ_MORE_LINK, DEFAULT_RSS_READ_MORE_LINK, LEGAL_VALUES, urlsplit, urlunsplit
from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN, DEFAULT_INDEX_READ_MORE_LINK, DEFAULT_FEED_READ_MORE_LINK, LEGAL_VALUES, urlsplit, urlunsplit
from nikola.plugin_categories import Command
from nikola.utils import ask, ask_yesno, get_logger, makedirs, STDERR_HANDLER, load_messages
from nikola.packages.tzlocal import get_localzone
Expand Down Expand Up @@ -71,7 +71,7 @@
'CATEGORY_OUTPUT_FLAT_HIERARCHY': False,
'TRANSLATIONS_PATTERN': DEFAULT_TRANSLATIONS_PATTERN,
'INDEX_READ_MORE_LINK': DEFAULT_INDEX_READ_MORE_LINK,
'RSS_READ_MORE_LINK': DEFAULT_RSS_READ_MORE_LINK,
'FEED_READ_MORE_LINK': DEFAULT_FEED_READ_MORE_LINK,
'POSTS': """(
("posts/*.rst", "posts", "post.tmpl"),
("posts/*.txt", "posts", "post.tmpl"),
Expand Down Expand Up @@ -210,10 +210,10 @@ def prepare_config(config):
"""Parse sample config with JSON."""
p = config.copy()
p.update({k: json.dumps(v, ensure_ascii=False) for k, v in p.items()
if k not in ('POSTS', 'PAGES', 'COMPILERS', 'TRANSLATIONS', 'NAVIGATION_LINKS', '_SUPPORTED_LANGUAGES', '_SUPPORTED_COMMENT_SYSTEMS', 'INDEX_READ_MORE_LINK', 'RSS_READ_MORE_LINK')})
if k not in ('POSTS', 'PAGES', 'COMPILERS', 'TRANSLATIONS', 'NAVIGATION_LINKS', '_SUPPORTED_LANGUAGES', '_SUPPORTED_COMMENT_SYSTEMS', 'INDEX_READ_MORE_LINK', 'FEED_READ_MORE_LINK')})
# READ_MORE_LINKs require some special treatment.
p['INDEX_READ_MORE_LINK'] = "'" + p['INDEX_READ_MORE_LINK'].replace("'", "\\'") + "'"
p['RSS_READ_MORE_LINK'] = "'" + p['RSS_READ_MORE_LINK'].replace("'", "\\'") + "'"
p['FEED_READ_MORE_LINK'] = "'" + p['FEED_READ_MORE_LINK'].replace("'", "\\'") + "'"
# fix booleans and None
p.update({k: str(v) for k, v in config.items() if isinstance(v, bool) or v is None})
return p
Expand Down

0 comments on commit 24c86aa

Please sign in to comment.