[pelican_comment_system] Added comment feeds

This commit is contained in:
Bernhard Scheirle
2014-04-14 11:55:29 +02:00
parent 98e2f16059
commit 24fce038c3
6 changed files with 132 additions and 78 deletions

View File

@@ -6,6 +6,7 @@ The comments are stored in Markdown files. Each comment in it own file.
- Static comments for each article
- Replies to comments
- Avatars and [Identicons](https://en.wikipedia.org/wiki/Identicon)
- Comment Atom Feed for each article
- Easy styleable via the themes
@@ -18,6 +19,7 @@ Bernhard Scheirle | <http://blog.scheirle.de> | <https://github.com/Scheirle>
## Instructions
- [Installation and basic usage](doc/installation.md)
- [Avatars and Identicons](doc/avatars.md)
- [Comment Atom Feed](doc/feed.md)
- [Comment Form (aka: never gather Metadata)](doc/form.md)
## Requirements

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
"""
from pelican import contents
from pelican.contents import Content
class Comment(Content):
mandatory_properties = ('author', 'date')
default_template = 'None'
def __init__(self, id, avatar, content, metadata, settings, source_path, context):
super(Comment,self).__init__( content, metadata, settings, source_path, context )
self.id = id
self.replies = []
self.avatar = avatar
self.title = "Posted by: " + str(metadata['author'])
def addReply(self, comment):
self.replies.append(comment)
def getReply(self, id):
for reply in self.replies:
if reply.id == id:
return reply
else:
deepReply = reply.getReply(id)
if deepReply != None:
return deepReply
return None
def __lt__(self, other):
return self.metadata['date'] < other.metadata['date']
def sortReplies(self):
for r in self.replies:
r.sortReplies()
self.replies = sorted(self.replies)
def countReplies(self):
amount = 0
for r in self.replies:
amount += r.countReplies()
return amount + len(self.replies)

View File

@@ -23,7 +23,7 @@ PELICAN_COMMENT_SYSTEM_AUTHORS = {
```
## Theme
To display the avatars and identicons simply add the following in the "comment for loop" in you theme:
To display the avatars and identicons simply add the following in the "comment for loop" in your theme:
```html
<img src="{{ SITEURL }}/{{ comment.avatar }}"

View File

@@ -0,0 +1,28 @@
# Comment Atom Feed
## Custom comment url
Be sure that the id of the html tag containing the comment matches `COMMENT_URL`.
##### pelicanconf.py
```python
COMMENT_URL = "#my_own_comment_id_{path}"
```
##### Theme
```html
{% for comment in article.comments recursive %}
...
<article id="my_own_comment_id_{{comment.id}}">{{ comment.content }}</article>
...
{% endfor %}
```
## Theme
#### Link
To display a link to the article feed simply add the following to your theme:
```html
{% if article %}
<a href="{{ FEED_DOMAIN }}/{{ PELICAN_COMMENT_SYSTEM_FEED|format(article.slug) }}">Comment Atom Feed</a>
{% endif %}
```

View File

@@ -8,15 +8,16 @@ Activate the plugin by adding it to your `pelicanconf.py`
And modify your `article.html` theme (see below).
## Settings
Name | Type | Default | Description
-----------------------------------------------|-----------|--------------------|-------
`PELICAN_COMMENT_SYSTEM` | `boolean` | `False` | Activates or deactivates the comment system
`PELICAN_COMMENT_SYSTEM_DIR` | `string` | `comments` | Folder where the comments are stored
`PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH` | `string` | `images/identicon` | Output folder where the identicons are stored
`PELICAN_COMMENT_SYSTEM_IDENTICON_DATA` | `tuple` | `()` | Contains all Metadata tags, which in combination identifies a comment author (like `('author', 'email')`)
`PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE` | `int` | `72` | Width and height of the identicons. Has to be a multiple of 3.
`PELICAN_COMMENT_SYSTEM_AUTHORS` | `dict` | `{}` | Comment authors, which should have a specific avatar. More infos [here](avatars.md)
Name | Type | Default | Description
-----------------------------------------------|-----------|----------------------------|-------
`PELICAN_COMMENT_SYSTEM` | `boolean` | `False` | Activates or deactivates the comment system
`PELICAN_COMMENT_SYSTEM_DIR` | `string` | `comments` | Folder where the comments are stored
`PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH` | `string` | `images/identicon` | Relative URL to the output folder where the identicons are stored
`PELICAN_COMMENT_SYSTEM_IDENTICON_DATA` | `tuple` | `()` | Contains all Metadata tags, which in combination identifies a comment author (like `('author', 'email')`)
`PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE` | `int` | `72` | Width and height of the identicons. Has to be a multiple of 3.
`PELICAN_COMMENT_SYSTEM_AUTHORS` | `dict` | `{}` | Comment authors, which should have a specific avatar. More info [here](avatars.md)
`PELICAN_COMMENT_SYSTEM_FEED` | `string` |`feeds/comment.%s.atom.xml` | Relative URL to output the Atom feed for each article.`%s` gets replaced with the slug of the article. More info [here](http://docs.getpelican.com/en/latest/settings.html#feed-settings)
`COMMENT_URL` | `string` | `#comment-{path}` | `{path}` gets replaced with the id of the comment. More info [here](feed.md)
## Folder structure
Every comment file has to be stored in a sub folder of `PELICAN_COMMENT_SYSTEM_DIR`.
@@ -24,7 +25,7 @@ Sub folders are named after the `slug` of the articles.
So the comments to your `foo-bar` article are stored in `comments/foo-bar/`
The filenames of the comment files are up to you. But the filename is the Identifier of the comment (without extension).
The filenames of the comment files are up to you. But the filename is the Identifier of the comment (**with** extension).
##### Example folder structure
@@ -44,8 +45,8 @@ The filenames of the comment files are up to you. But the filename is the Identi
Tag | Required | Description
--------------|-----------|----------------
`date` | yes | Date when the comment was posted
`replyto` | no | Identifier of the parent comment. Identifier = Filename (without extension)
`locale_date` | forbidden | Will be overwritten with a locale representation of the date
`author` | yes | Name of the comment author
`replyto` | no | Identifier of the parent comment. Identifier = Filename (**with** extension)
Every other (custom) tag gets parsed as well and will be available through the theme.
@@ -62,26 +63,27 @@ Every other (custom) tag gets parsed as well and will be available through the t
## Theme
In the `article.html` theme file are now two more variables available.
Variables | Description
----------------------------------|--------------------------
`article.metadata.comments_count` | Amount of total comments for this article (including replies to comments)
`article.metadata.comments` | Array containing the top level comments for this article (no replies to comments)
Variables | Description
-------------------------|--------------------------
`article.comments_count` | Amount of total comments for this article (including replies to comments)
`article.comments` | Array containing the top level comments for this article (no replies to comments)
### Comment object
Variables | Description
The comment object is a [content](https://github.com/getpelican/pelican/blob/master/pelican/contents.py#L34) object, so all common attributes are available (like author, content, date, local_date, metadata, ...).
Additional following attributes are added:
Attribute | Description
-----------|--------------------------
`id` | Identifier of this comment
`content` | Content of this comment
`metadata` | All metadata as in the comment file (or described above)
`replies` | Array containing the top level replies for this comment
`avatar` | Path to the avatar or identicon of the comment author
##### Example article.html theme
(only the comment section)
```html
{% if article.metadata.comments %}
{% for comment in article.metadata.comments recursive %}
{% set metadata = comment.metadata %}
{% if article.comments %}
{% for comment in article.comments recursive %}
{% if loop.depth0 == 0 %}
{% set marginLeft = 0 %}
{% else %}
@@ -89,9 +91,9 @@ Variables | Description
{% endif %}
<article id="comment-{{comment.id}}" style="border: 1px solid #DDDDDD; padding: 5px 0px 0px 5px; margin: 0px -1px 5px {{marginLeft}}px;">
<a href="{{ SITEURL }}/{{ article.url }}#comment-{{comment.id}}" rel="bookmark" title="Permalink to this comment">Permalink</a>
<h4>{{ metadata['author'] }}</h4>
<p>Posted on <abbr class="published" title="{{ metadata['date'].isoformat() }}">{{ metadata['locale_date'] }}</abbr></p>
<h4>{{ comment.author }}</h4>
<p>Posted on <abbr class="published" title="{{ comment.date.isoformat() }}">{{ comment.locale_date }}</abbr></p>
{{ comment.metadata['my_custom_metadata'] }}
{{ comment.content }}
{% if comment.replies %}
{{ loop(comment.replies) }}

View File

@@ -10,51 +10,17 @@ Author: Bernhard Scheirle
import logging
import os
import copy
logger = logging.getLogger(__name__)
from itertools import chain
from pelican import signals
from pelican.utils import strftime
from pelican.readers import MarkdownReader
from pelican.writers import Writer
import avatars
class Comment:
def __init__(self, id, metadata, content, avatar):
self.id = id
self.content = content
self.metadata = metadata
self.replies = []
self.avatar = avatar
def addReply(self, comment):
self.replies.append(comment)
def getReply(self, id):
for reply in self.replies:
if reply.id == id:
return reply
else:
deepReply = reply.getReply(id)
if deepReply != None:
return deepReply
return None
def __lt__(self, other):
return self.metadata['date'] < other.metadata['date']
def sortReplies(self):
for r in self.replies:
r.sortReplies()
self.replies = sorted(self.replies)
def countReplies(self):
amount = 0
for r in self.replies:
amount += r.countReplies()
return amount + len(self.replies)
from comment import Comment
def pelican_initialized(pelican):
from pelican.settings import DEFAULT_CONFIG
@@ -64,6 +30,8 @@ def pelican_initialized(pelican):
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_DATA', ())
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml'))
DEFAULT_CONFIG.setdefault('COMMENT_URL', '#comment-{path}')
if pelican:
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM', False)
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_DIR', 'comments')
@@ -71,6 +39,8 @@ def pelican_initialized(pelican):
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_DATA', ())
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml'))
pelican.settings.setdefault('COMMENT_URL', '#comment-{path}')
def initialize(article_generator):
@@ -82,21 +52,26 @@ def initialize(article_generator):
article_generator.settings['PELICAN_COMMENT_SYSTEM_AUTHORS'],
)
def add_static_comments(gen, metadata):
def add_static_comments(gen, content):
if gen.settings['PELICAN_COMMENT_SYSTEM'] != True:
return
metadata['comments_count'] = 0
metadata['comments'] = []
content.comments_count = 0
content.comments = []
if not 'slug' in metadata:
logger.warning("pelican_comment_system: cant't locate comments files without slug tag in the article")
return
#Modify the local context, so we get proper values for the feed
context = copy.copy(gen.context)
context['SITEURL'] += "/" + content.url
context['SITENAME'] = "Comments for: " + content.title
context['SITESUBTITLE'] = ""
path = gen.settings['PELICAN_COMMENT_SYSTEM_FEED'] % content.slug
writer = Writer(gen.output_path, settings=gen.settings)
folder = os.path.join(gen.settings['PELICAN_COMMENT_SYSTEM_DIR'], metadata['slug'])
folder = os.path.join(gen.settings['PELICAN_COMMENT_SYSTEM_DIR'], content.slug)
if not os.path.isdir(folder):
logger.debug("No comments found for: " + metadata['slug'])
logger.debug("No comments found for: " + content.slug)
writer.write_feed( [], context, path)
return
reader = MarkdownReader(gen.settings)
@@ -106,17 +81,19 @@ def add_static_comments(gen, metadata):
for file in os.listdir(folder):
name, extension = os.path.splitext(file)
if extension[1:].lower() in reader.file_extensions:
content, meta = reader.read(os.path.join(folder, file))
meta['locale_date'] = strftime(meta['date'], gen.settings['DEFAULT_DATE_FORMAT'])
com_content, meta = reader.read(os.path.join(folder, file))
avatar_path = avatars.getAvatarPath(name, meta)
com = Comment(name, meta, content, avatar_path)
com = Comment(file, avatar_path, com_content, meta, gen.settings, file, context)
if 'replyto' in meta:
replies.append( com )
else:
comments.append( com )
writer.write_feed( comments + replies, context, path)
#TODO: Fix this O(n²) loop
for reply in replies:
for comment in chain(comments, replies):
@@ -130,8 +107,8 @@ def add_static_comments(gen, metadata):
comments = sorted(comments)
metadata['comments_count'] = len(comments) + count
metadata['comments'] = comments
content.comments_count = len(comments) + count
content.comments = comments
def writeIdenticonsToDisk(gen, writer):
avatars.generateAndSaveMissingAvatars()
@@ -139,5 +116,5 @@ def writeIdenticonsToDisk(gen, writer):
def register():
signals.initialized.connect(pelican_initialized)
signals.article_generator_init.connect(initialize)
signals.article_generator_context.connect(add_static_comments)
signals.article_generator_write_article.connect(add_static_comments)
signals.article_writer_finalized.connect(writeIdenticonsToDisk)