Kaynağa Gözat

[pelican_comment_system] Added comment feeds

Bernhard Scheirle 11 yıl önce
ebeveyn
işleme
24fce038c3

+ 2 - 0
pelican_comment_system/Readme.md

@@ -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

+ 45 - 0
pelican_comment_system/comment.py

@@ -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)

+ 1 - 1
pelican_comment_system/doc/avatars.md

@@ -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 }}"

+ 28 - 0
pelican_comment_system/doc/feed.md

@@ -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 %}
+```
+
+

+ 27 - 25
pelican_comment_system/doc/installation.md

@@ -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) }}

+ 29 - 52
pelican_comment_system/pelican_comment_system.py

@@ -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)