diff --git a/pelican_comment_system/Readme.md b/pelican_comment_system/Readme.md index e95d86a..3b4dfd4 100644 --- a/pelican_comment_system/Readme.md +++ b/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 | | ## 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 diff --git a/pelican_comment_system/comment.py b/pelican_comment_system/comment.py new file mode 100644 index 0000000..84f03e6 --- /dev/null +++ b/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) diff --git a/pelican_comment_system/doc/avatars.md b/pelican_comment_system/doc/avatars.md index 8d05e70..f0567c7 100644 --- a/pelican_comment_system/doc/avatars.md +++ b/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 {{ comment.content }} + ... +{% endfor %} +``` +## Theme +#### Link +To display a link to the article feed simply add the following to your theme: + +```html +{% if article %} + Comment Atom Feed +{% endif %} +``` + + diff --git a/pelican_comment_system/doc/installation.md b/pelican_comment_system/doc/installation.md index 7fda760..97e6c65 100644 --- a/pelican_comment_system/doc/installation.md +++ b/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 %}
Permalink -

{{ metadata['author'] }}

-

Posted on {{ metadata['locale_date'] }}

- +

{{ comment.author }}

+

Posted on {{ comment.locale_date }}

+ {{ comment.metadata['my_custom_metadata'] }} {{ comment.content }} {% if comment.replies %} {{ loop(comment.replies) }} diff --git a/pelican_comment_system/pelican_comment_system.py b/pelican_comment_system/pelican_comment_system.py index 10b86f6..f9cce59 100644 --- a/pelican_comment_system/pelican_comment_system.py +++ b/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)