Compare commits

222 Commits

Author SHA1 Message Date
4c19fb997b exif_info: Retrieve EXIF information 2014-08-30 23:13:11 +02:00
fb53d674a7 gallery: Add comment per image support 2014-06-01 14:19:43 +02:00
0574098ee5 gallery: Add support for non-english captions 2014-06-01 10:33:37 +02:00
a8830b8825 gallery: Add per-image caption/title support.
To add caption/title to a picture, in your article simply add a
'gallerycaptions' tag in your metadata, whose value is a dictionnary of
(filename, title) tuples.

Example:
gallery: <Gallery Name>
gallerycaptions: { 'image_name1': 'caption1', 'image_name4': 'caption4', }

Then you will be able to use article.gallerycaptions.get('image_nameX')
in the templates, to retrieve the title associated to the image.
2014-05-29 15:45:11 +02:00
Justin Mayer
7b3851a62b Merge pull request #205 from TheRealBill/master
Add Markdown-specific instructions to Gravatar plugin README
2014-05-14 20:39:34 -07:00
bill5244
d47fdc1ea5 adding markdown specific instructions to gravatar readme 2014-05-13 11:28:08 -05:00
Kyle Fuller
932537f26d Merge pull request #203 from adworacz/fixSitemapUrlGeneration
Fix Sitemap generation for non-file urls.
2014-05-12 09:07:34 +01:00
Austin Dworaczyk Wiltshire
8497de9413 Fix Sitemap generation for non-file urls.
Fixes getpelican/pelican-plugins#168.
2014-05-11 17:22:02 -07:00
Justin Mayer
47f5050463 Merge pull request #200 from khuevu/master
Add a plugin to extract representative image for an article
2014-05-08 19:35:59 -07:00
Justin Mayer
9d9f272895 Merge pull request #199 from krischer/master
Liquid Tags: line numbers for include_code
2014-05-06 11:03:25 -07:00
Khue Vu
624734724b update Readme.md 2014-05-07 00:40:00 +08:00
Justin Mayer
7f52729121 Merge pull request #153 from joergdietrich/collapse-code
Collapsible notebook code cells in liquid_tags plugin
2014-05-06 06:48:13 -07:00
Jörg Dietrich
620d176f30 Update Readme to specify which template is needed for which IPython version 2014-05-06 11:04:17 +02:00
Jörg Dietrich
5d1b77a238 work with IPython 1.x and 2.0 2014-05-06 10:51:34 +02:00
Khue Vu
c15a9dc250 change image property name to featured_image 2014-05-06 09:20:30 +08:00
Khue Vu
2b37362873 add the plugin to extract representative image for article and assign to an article's property 2014-05-06 00:27:42 +08:00
Justin Mayer
4252f8f230 Merge pull request #197 from Jenselme/creole_reader
Creole reader: add source code highlighting support
2014-05-02 08:02:54 -07:00
Jörg Dietrich
42f90c94fb attempt to merge master with collapsible code 2014-05-02 13:51:33 +02:00
Lion Krischer
97e2d7549b Liquid Tags: line numbers for include_code 2014-05-01 01:18:08 +02:00
Julien Enselme
3a804aec12 Creole reader: add source code highlighting support.
Correct typo.
2014-04-30 10:32:48 +02:00
Justin Mayer
1d4d19aca5 Merge pull request #195 from Jenselme/creole_reader
Add a plugin to read the wikicreole syntax.
2014-04-22 20:20:53 -07:00
Jenselme
9750c13665 Add dependency information. 2014-04-22 00:05:08 +02:00
Julien Enselme
046eaadd61 Add the Creole Reader plugin 2014-04-21 23:57:04 +02:00
Justin Mayer
c96846c201 Merge pull request #190 from Scheirle/master
[pelican_comment_system] Added Avatars, Identicons and Comment Atom Feed
2014-04-19 07:00:24 -07:00
Bernhard Scheirle
02a4645912 [pelican_comment_system] made python3 compatible 2014-04-14 18:15:14 +02:00
Bernhard Scheirle
24fce038c3 [pelican_comment_system] Added comment feeds 2014-04-14 11:55:29 +02:00
Bernhard Scheirle
98e2f16059 [pelican_comment_system] Added Avatars and Identicons 2014-04-10 08:58:19 +02:00
Justin Mayer
235c9e3b91 Merge pull request #188 from Scheirle/master
[pelican_comment_system] Add recommendation
2014-04-04 08:53:50 -07:00
Bernhard Scheirle
1a20cc84b2 [pelican_comment_system] Added Recommendation 2014-04-03 19:38:45 +02:00
Justin Mayer
057ab2b4b2 Merge pull request #185 from Scheirle/master
Added plugin: Pelican Comment System
2014-04-02 06:02:41 -07:00
Bernhard Scheirle
20f8a24971 Added plugin: pelican comment system 2014-03-30 14:14:22 +02:00
Kyle Fuller
90c915ef44 Merge pull request #179 from barrysteyn/master
Fixed errors arising from Python 3.x
2014-03-18 14:45:52 +00:00
Barry Steyn
fb570c528b Fixed errors arising from Python 3.x 2014-03-18 06:42:41 -07:00
Justin Mayer
ad188b05c2 Merge pull request #175 from minrk/ipython-2.0
Support IPython 2.0 in notebook liquid tag
2014-03-10 23:39:56 +01:00
MinRK
657505cf35 adjust embedded notebook CSS for IPython 2.0
remove text-cell prompt and padding, so it aligns with outer body text
2014-03-09 16:51:31 -07:00
MinRK
db4683c515 support IPython 2.0 in notebook liquid tag
The only relevant changes seem to be a few renamed classes, and changed signatures.
2014-03-09 12:30:07 -07:00
Justin Mayer
dc96679d03 Merge pull request #174 from svenkreiss/master
Add Vimeo support to liquid_tags plugin
2014-03-09 12:04:38 +01:00
Sven Kreiss
b95142b415 New vimeo tag. 2014-03-07 17:57:39 -05:00
Justin Mayer
c38851cf9c Grammar and PEP8 fixes for render_math plugin 2014-03-07 08:33:06 +01:00
Justin Mayer
4386da4adf Merge pull request #171 from ariddell/patch-1
Add Python 3 compatibility to render_math plugin
2014-03-07 08:16:23 +01:00
Justin Mayer
1100ccd872 Merge pull request #172 from jesrui/static_comments
Add static_comments plugin
2014-03-05 16:57:05 +01:00
Jesus Ruiz
8052ea9f9e add static_comments plugin 2014-03-04 16:47:56 +01:00
Allen Riddell
3bcb54ac55 Add Python 3 compatibility 2014-03-02 23:46:58 -05:00
Justin Mayer
8ca6e5e514 Merge pull request #170 from barrysteyn/master
render_math plugin: alert user to incorrect Typogrify usage
2014-03-03 01:07:48 +01:00
Barry Steyn
f93764f737 alert user to incorrect usage of typogrify 2014-03-02 11:44:14 -08:00
Justin Mayer
0b69e1bdb2 Merge pull request #169 from barrysteyn/master
Add ability to specify SSL protocol
2014-02-26 14:46:00 +01:00
Barry Steyn
47dc3e9747 ability to specify ssl protocol 2014-02-25 16:53:16 -08:00
Justin Mayer
9b47bbf67d Merge pull request #166 from barrysteyn/master
latex plugin name changed to render_math
2014-02-23 00:28:54 +01:00
Barry Steyn
5af9d08c06 moved latex to render_math; latex now symbolic link 2014-02-22 13:15:24 -08:00
Justin Mayer
7ba212ecc4 Merge pull request #162 from barrysteyn/master
Logic for MathMl; Fixed Bug; Updated Readme
2014-02-19 14:59:47 -08:00
Barry Steyn
64dca25e86 Fixed slight error with mathml regex in summary 2014-02-19 10:19:49 -08:00
Barry Steyn
5d3ab5b964 Logic for MathMl; Fixed Bug; Updated Readme 2014-02-19 08:18:43 -08:00
Justin Mayer
126a1290f3 Merge pull request #161 from barrysteyn/master
latex plugin revamp
2014-02-18 09:26:27 -08:00
Barry Steyn
8ac0e61720 Update Readme.md
Fixed spelling error
2014-02-18 08:21:09 -08:00
Barry Steyn
474b7fd2ff new latex math plugin, now handles typogrify correctly and does not need template alteration to be used 2014-02-18 06:48:05 -08:00
Justin Mayer
30657eca3a Merge pull request #157 from samyarous/master
Pelican plugin providing RST directives for twitter bootstrap partial intergration
2014-02-16 10:37:24 -08:00
Samy Arous
b86e17607c Initial release of the twitter bootstrap rst directives plugin 2014-02-16 19:23:48 +01:00
Justin Mayer
5e7bf54a95 Merge pull request #156 from stuartlangridge/simple-footnotes
Add Simple Footnotes plugin
2014-02-15 14:33:54 -08:00
sil
c85ea081c0 Simple Footnotes plugin. Add footnotes to your text by enclosing them in [ref] and [/ref]. 2014-02-15 21:34:28 +00:00
Jörg Dietrich
b412bb1c56 Explain new collapsible code cells feature in Readme.md 2014-02-11 22:31:30 +01:00
Jörg Dietrich
8d6a4a9d79 Maintain backward compatibility by not requiring that pelicanhtml is copied to the blog home 2014-02-11 22:15:37 +01:00
Justin Mayer
f7178ae0e9 Merge pull request #150 from runarberg/master
Fix missing quote mark in subcategories plugin docs
2014-02-10 10:28:31 -08:00
Justin Mayer
82597bb62e Merge pull request #152 from mlang/master
Add optional max number of entries to github_activity plugin
2014-02-10 10:26:47 -08:00
Mario Lang
b35996ca43 Allow for an optional maximum number of entries to display. 2014-02-10 17:27:54 +01:00
Rúnar Berg Baugsson Sigríðarson
3bf1ac5e98 Fixed missing quote mark in an HTML example 2014-02-09 00:54:01 +00:00
Jörg Dietrich
5d0a5d2ec5 get jquery via https from google's cdn 2014-02-08 10:47:18 +01:00
Jörg Dietrich
e1fd114a0e remove unnessecary replace from template, require white space after hash 2014-02-08 10:34:53 +01:00
Justin Mayer
d7cb218e46 Merge pull request #149 from talha131/fix-neighbors
Neighbors plugin shouldn't require subcategories
2014-02-07 14:45:53 -08:00
Jörg Dietrich
c6258ecf95 Merge branch 'master' into collapse-code 2014-02-07 23:19:01 +01:00
Jörg Dietrich
1876bf4438 add jinja2 template file to have collapsible python code 2014-02-07 23:10:34 +01:00
Jörg Dietrich
b898230913 Make python code collapsible:
- Remove unused code: https://github.com/getpelican/pelican-plugins/issues/140
- Add javascript and CSS to header to toggle code areas
- load jinja2 template from file
2014-02-07 23:08:23 +01:00
Talha Mansoor
073c997eea Fix an issue in neighbors plugin which was introduced after subcategory support
It is not necessary that articles will have subcategories. You may
end up getting following critical error.

>
CRITICAL: ("'ArticlesGenerator' object has no attribute 'subcategories'",)
CRITICAL: 'ArticlesGenerator' object has no attribute 'subcategories'
Traceback (most recent call last):
  File "/Users/talha/Repos/VirtualEnvs/pelican-dev/bin/pelican", line 8, in <module>
    load_entry_point('pelican==3.3', 'console_scripts', 'pelican')()
  File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/pelican-3.3-py2.7.egg/pelican/__init__.py", line 346, in main
    pelican.run()
  File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/pelican-3.3-py2.7.egg/pelican/__init__.py", line 162, in run
    p.generate_context()
  File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/pelican-3.3-py2.7.egg/pelican/generators.py", line 480, in generate_context
    signals.article_generator_finalized.send(self)
  File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/blinker/base.py", line 267, in send
    for receiver in self.receivers_for(sender)]
  File "/Users/talha/Repos/pelican-plugins/neighbors/neighbors.py", line 49, in neighbors
    for subcategory, articles in generator.subcategories:
AttributeError: 'ArticlesGenerator' object has no attribute 'subcategories'
2014-02-07 11:40:48 +05:00
Justin Mayer
0a4bf7b9b0 Merge pull request #146 from smartass101/master
i18n_subsites: add lang_subsites template var, improve docs
2014-02-05 20:53:39 -08:00
Ondrej Grover
6f7dafaee6 i18n_subsites: add lang_subsites template var, improve docs
- the new variable makes it easier to implement language buttons
- a short howto on language buttons is also provided
- the dictionary mapping template vars are now OrderedDict
  for consistent-looking language buttons
2014-02-05 21:42:12 +01:00
Justin Mayer
3d65e38700 Merge pull request #118 from deepy/master
Add option to keep original filename and put thumbnail in a folder instead
2014-02-05 07:31:54 -08:00
Justin Mayer
88cd8305d2 Merge pull request #145 from smartass101/master
i18n_subsites: consider templates lang, expand docs, several fixes
2014-02-04 07:59:38 -08:00
Ondrej Grover
6feff11273 18n_subsites: use configure_settings() to sanitize
Also needed for LOCALE overrides, etc.
2014-02-04 15:07:24 +01:00
Ondrej Grover
0665333e8e i18n_subsites: improve develop server support
1. main site url root fix
2. support autoreload mode
Also removed trailing slash on dir name.
2014-02-04 14:24:44 +01:00
Ondrej Grover
dda48f6428 i18n_subsites: fix HIDE_UNTRANSLATED_CONTENT
1. was removing items of an active iterator -> only first worked
2. Article inherits Page, so first check if is Article
2014-02-04 14:14:43 +01:00
Ondrej Grover
e76f3cf914 i18n_subsites: consider templates lang, expand docs
this commit removes the need to make a dummy translation for
the language in which the templates are written.
This only affects themes using jinja2.ext.i18n.
The I18N_THEMES_LANG is introduced to address this issue.

Also expanded the docs for making gettext .po files with babel.
2014-02-03 20:04:40 +01:00
Justin Mayer
16437e1a05 Merge pull request #144 from idyedov/update_disqus_static
Fix structure of disqus_static plugin and update README
2014-02-03 06:58:07 -08:00
Justin Mayer
aa268e4883 Minor fixes to Custom Article URLs plugin README 2014-02-02 18:02:56 -08:00
Ivan Dyedov
a73d9dddcb fix structure of disqus_static plugin and update README 2014-02-02 20:58:27 -05:00
Justin Mayer
84b1962afc More fixes for Subcategory plugin README 2014-02-02 17:56:08 -08:00
Justin Mayer
56ca351f98 Minor fixes to Subcategory plugin README 2014-02-02 17:52:40 -08:00
Justin Mayer
ce06f87a43 Merge pull request #141 from alistairmagee/neighbors
Add categories and subcategories support to Neighbors plugin
2014-02-02 17:03:03 -08:00
Justin Mayer
614ea30efd Merge pull request #142 from alistairmagee/custom_article_urls
Custom article URLs
2014-02-02 17:01:24 -08:00
Ondrej Grover
ca377d918e i18n_subsites plugin: implement jinja2.ext.i18n support
this commit introduces optional support for translatable templates
2014-02-02 16:50:15 -08:00
Ondrej Grover
5e07f2dfc6 new plugin: i18n_subsites
This plugin extends the translations functionality by creating i8n-ized
sub-sites for the default site.
This commit implements the basic generation functionality.
2014-02-02 09:09:20 +01:00
Justin Mayer
d13465d1b2 Merge pull request #143 from alistairmagee/subcategory
Sub-categories with same basename but different parents should be unique
2014-02-01 12:31:05 -08:00
Justin Mayer
a0797a498b Merge pull request #137 from alistairmagee/clean_summary
Add "Clean Summary" plugin
2014-02-01 10:48:33 -08:00
Alistair Magee
02fb072aa3 altered plugin to work with updated subcategory plugin 2014-02-01 18:35:49 +00:00
Alistair Magee
dcc95a4611 update to the neighbors plugin to retrieve neigbors for categories and subcategories 2014-02-01 18:30:09 +00:00
Alistair Magee
b86be87c61 fixed typos in README 2014-02-01 18:21:56 +00:00
Alistair Magee
250cef84f0 subcategories with same basename but different parents were not unique. Fixed now. 2014-02-01 04:50:53 +00:00
Alistair Magee
f3549dd296 plugin to specify maxmium number of images to appear in summary 2014-01-31 16:32:03 +00:00
Justin Mayer
2ca07107d4 Merge pull request #120 from VuongN/master
Read more link plugin
2014-01-30 21:49:03 -08:00
Justin Mayer
614e670026 Merge pull request #136 from talha131/share-post
Add plugin to add share URLs to article
2014-01-26 14:37:52 -08:00
Justin Mayer
1793ebbf79 Merge pull request #135 from alistairmagee/custom_article_urls
Category-based URL structure plugin
2014-01-26 14:36:40 -08:00
Talha Mansoor
9a9f9bdb83 Add plugin to add share URLs to article 2014-01-26 18:44:36 +05:00
Alistair Magee
2e98bd8bdf remove uneeded imports 2014-01-25 20:14:49 +00:00
Alistair Magee
1f8b7e0f87 Support for custom urls for different categories of article 2014-01-25 20:03:01 +00:00
Justin Mayer
8f69faa77c Merge pull request #132 from talha131/patch-1
Clarify documentation of related_posts plugin
2014-01-24 13:15:50 -08:00
Justin Mayer
5cb7b449f7 Merge pull request #133 from alistairmagee/subcategory
A plugin to add subcategories to Article categories
2014-01-24 13:01:28 -08:00
Alistair Magee
1facc9468b added support for generating subcategory feeds 2014-01-24 12:59:57 +00:00
Talha Mansoor
b268db7312 Clarify documentation of related_posts plugin
It is not clear how the plugin determines which posts are related.
2014-01-24 17:55:03 +05:00
Alistair Magee
48b990c0cd A plugin to add subcategories to Article categories 2014-01-24 06:39:29 +00:00
Justin Mayer
5e6307d9e2 Merge pull request #131 from calfzhou/neighbors
Make neighbors plugin support translated articles.
2014-01-23 14:59:14 -08:00
zhouji
211d835f30 Make neighbors plugin support translated articles. 2014-01-23 09:26:47 +08:00
Justin Mayer
c4d7a761c8 Merge pull request #126 from rmorehead/tipue_search_no_cat_fix
Fix tipue_search failing with pages due to no category
2014-01-15 20:15:43 -08:00
Rod Morehead
95d7ebb470 Fixed tipue_search failing with pages due to no category. 2014-01-09 09:26:37 -06:00
Justin Mayer
f30a68fbf4 Merge pull request #125 from XenGi/patch-1
Sort images by filename in Gallery plugin
2014-01-08 09:41:46 -08:00
Ricardo Band
dacc64023d added sorting of images
The images are now sorted by their filename.
2014-01-07 21:16:06 +01:00
Kyle Fuller
7e832f780e Merge pull request #122 from aparij/master
Update disqus_static.py
2013-12-22 07:26:46 -08:00
Alex Parij
8de762f97d Update disqus_static.py
There is no '_' prefix for DEFAULT_CONFIG
2013-12-21 12:00:12 -05:00
Justin Mayer
ddcd9ee4f6 Merge pull request #119 from calfzhou/tipue
Escape "^" in the json file because it is a special character for tique ...
2013-12-21 08:49:37 -08:00
Vuong Nguyen
30638d8ac4 Update Readme.md 2013-12-16 17:02:16 -05:00
Vuong Nguyen
1cf85f7a06 Update Readme.md 2013-12-16 17:01:57 -05:00
zhouji
1c6fa7893e Also escape "^" for page tile in the tipue json file. 2013-12-16 17:13:08 +08:00
zhouji
9a952032f4 Escape "^" in the json file because it is a special character for tique search. 2013-12-16 17:06:12 +08:00
Alex Nordlund
a5269f599a Adds THUMBNAIL_KEEP_NAME which if set puts the thumbnail in a folder named
like the key in THUMBNAIL_SIZES.
2013-12-09 00:44:30 +00:00
Vuong Nguyen
943e590c24 Read more link plugin -- initial commit 2013-12-04 23:21:19 -05:00
Justin Mayer
f5d0f4ecb9 Remove gzip references in Assets plugin README
gzip filter removed in:
d0fb241420

Fixes #115
2013-12-03 07:04:06 -08:00
Justin Mayer
36bdaae588 Merge pull request #113 from florianjacob/feed_summary
Add feed summary plugin
2013-11-29 21:30:28 -08:00
Florian Jacob
2a564dce1a Added feed summary plugin.
This plugin allows to use the `Summary:` metadata as feed contents.
2013-11-27 23:21:24 +01:00
Justin Mayer
bd12282f92 Merge pull request #109 from astrieanna/patch-1
Correct package name from bs4 to beautifulsoup4
2013-11-18 07:32:20 -08:00
Justin Mayer
64b03f9d43 Merge pull request #110 from jseabold/ipython-fixes
IPython cleanup
2013-11-16 15:59:17 -08:00
Skipper Seabold
ce58bd5704 Fix error message for IPython 2.0 2013-11-16 16:53:25 +00:00
Skipper Seabold
3a8f323563 DOC: nbconvert no longer necessary 2013-11-16 16:46:09 +00:00
Justin Mayer
bd455c3dcc Merge pull request #108 from graffic/patch-2
The `asset_url` was also hardcoded instead of using `theme_static_dir`
2013-11-16 06:54:48 -08:00
Leah Hanson
9aae49360a Update readme.rst
The name of the package in pip is `beautifulsoup4`, not `bs4`.
2013-11-14 17:45:58 -06:00
Javier Gonel
e00e7d35c9 The asset_url needs to use also theme_static_dir
Sorry for this mistake, I was using harcoded paths in my template.

If the destination changes in the output folder (`THEME_STATIC_DIR`) also the URL used for the assets should change.
2013-11-14 18:41:33 +02:00
Justin Mayer
507443b3b2 Merge pull request #107 from graffic/patch-1
Use THEME_STATIC_DIR instead of hardcoded 'theme'
2013-11-14 08:23:04 -08:00
Javier Gonel
c6003cca22 Use THEME_STATIC_DIR instead of hardcoded 'theme'
The output folder for the theme can be changed via `THEME_STATIC_DIR` setting. But the assets plugin uses the harcoded `theme` string. This small patch makes the asset plugin use the setting.
2013-11-14 18:18:01 +02:00
Justin Mayer
744c1620f5 Merge pull request #105 from talha131/minify-json
Minify JSON to improve search speed
2013-11-10 10:16:50 -08:00
Talha Mansoor
baa90d815e Minify JSON to improve search speed 2013-11-10 14:51:20 +05:00
Justin Mayer
c5788c8e3a Merge pull request #104 from talha131/cleanup-sitemap
Refactor and fix minor typo in sitemap code
2013-11-09 15:56:39 -08:00
Talha Mansoor
335c9c63e1 Refactor and fix minor typo in sitemap code 2013-11-10 04:47:13 +05:00
Justin Mayer
6ae722fefd Merge pull request #103 from talha131/fix-sitemap
Update sitemap plugin to play nicely with modified metadata
2013-11-09 15:35:10 -08:00
Talha Mansoor
df9b772f3e Update sitemap plugin to place nicely with modified metadata
modified is a string in pelican <= 3.3 and datetime > 3.3

Sitemap used to treat it as string.
2013-11-10 04:17:13 +05:00
Justin Mayer
17980a031f Merge pull request #102 from Shaked/master
Override related_posts from post metadata
2013-11-08 17:51:21 -08:00
Shaked Klain
936bb03d7e added the ability to override\force related_posts from the post's meta data 2013-11-07 02:14:41 +01:00
Justin Mayer
0553dbe36d Merge pull request #99 from fly/better_requirements
update better_figures_and_images readme with requirements
2013-11-02 08:52:42 -07:00
Jon Chen
bfaa9e690d update readme with requirements
better_figures_and_images doesn't make it clear in the readme what
requirements are required.
2013-11-02 00:42:28 -04:00
Justin Mayer
c21c932433 Merge pull request #98 from nt3rp/add-youtube-liquid-tag
Add support for YouTube to Liquid Tags plugin
2013-10-29 09:31:36 -07:00
Nicholas Terwoord
370bc6c124 Adds youtube liquid tag support
- Works as the existing [octopress / jekyll plugin](https://gist.github.com/jamieowen/2063748)
2013-10-27 23:17:06 -04:00
Justin Mayer
ee2684ea4b Merge pull request #95 from dflock/better_figures_and_images
Better figures and images - improved path handling & Pelican 3.3 fixes
2013-10-24 07:52:10 -07:00
Alexis Metaireau
3b4737325e Merge pull request #96 from talha131/patch-1
Improve summary of interlinks plugin
2013-10-24 02:02:55 -07:00
Talha Mansoor
3df48a1b77 Improve summary of interlinks plugin 2013-10-24 12:48:16 +05:00
Duncan Lock
37809a83a8 Improved path handling and Pelican 3.3 support for filename placeholder. 2013-10-20 12:35:44 -07:00
Justin Mayer
3ba3bf1f9b Merge pull request #93 from jedbrown/jed/fix-latex-signal-name
latex: fix signal names to standardized (pelican-3.2 and later)
2013-10-18 13:00:18 -07:00
Jed Brown
0f55bd63f2 latex: fix signal names to standardized (pelican-3.2 and later)
The names were accidentally reverted to pre-pelican-3.2 in
5fa7ddc0ea08d517c4a767ceaa1df6ccc4d721c "revised the latex plugin so
that it will work in both http and https protocols".
2013-10-11 22:22:49 -05:00
Justin Mayer
0cd5586664 Merge pull request #89 from grapeot/master
Revise the LaTeX plugin to support both HTTP and HTTPS protocols
2013-10-10 13:16:01 -07:00
Yan Wang
a5fa7ddc0e revised the latex plugin so that it will work in both http and https protocols 2013-10-09 03:36:08 +00:00
Justin Mayer
800a53c1f8 Merge pull request #51 from malept/assets/source-paths
Assets: add support for specifying additional source load paths
2013-10-08 05:09:53 -07:00
Justin Mayer
d09e022bfd Merge pull request #50 from malept/assets/settings-bundles
Assets: add support for named bundles via the settings file
2013-10-08 05:08:55 -07:00
Alexis Metaireau
dd77741bf0 Merge pull request #88 from yuex/master
New plugin to support CJK fonts spacing
2013-09-30 01:14:28 -07:00
Yue Xin
3f47cbd468 add cjk-auto-spacing as submodule 2013-09-29 22:49:49 +08:00
Justin Mayer
3e4dcc293f Merge pull request #87 from zonca/googleplus_comments
Add Google+ Comments plugin
2013-09-28 06:05:58 -07:00
Justin Mayer
64e35fe195 Merge pull request #85 from jakevdp/liquid_tags_import
BUG: use relative import for mdx_liquid_tags
2013-09-28 06:00:28 -07:00
Andrea Zonca
13a8ea5d5f first implementation of google plus comments 2013-09-27 18:02:53 -07:00
Justin Mayer
fb421f5ac5 Merge pull request #86 from zonca/submoduleshttps
Submodules ssh->https
2013-09-27 12:54:03 -07:00
Andrea Zonca
532a7b01a2 Submodules ssh->https 2013-09-27 12:33:36 -07:00
Jake Vanderplas
e9e34856bf BUG: use relative import for mdx_liquid_tags 2013-09-27 11:52:25 -07:00
Justin Mayer
1e4ad04c61 Merge pull request #83 from calfzhou/master
Update signal names for Pelican 3.3+
2013-09-27 01:50:35 -07:00
zhouji
768e02b2d5 Update signal names for Pelican 3.2+ 2013-09-27 16:28:46 +08:00
Justin Mayer
10094868f2 Merge pull request #61 from talha131/update-signal-article_generate_context
Update article_generate_context to article_generator_context
2013-09-24 13:07:19 -07:00
Justin Mayer
8d1f6a940d Merge pull request #59 from sukharevd/master
Sitemap: Made last article within URL wrappers determine its modification date; added check for modification attribute
2013-09-23 10:37:09 -07:00
Justin Mayer
8bea5f5670 Merge pull request #52 from eikiu/master
Created interlinks plugin
2013-09-18 09:57:31 -07:00
Alexis Metaireau
db0454de1b Merge pull request #80 from derekvan/master
Updates to gallery plugin
2013-09-16 02:04:46 -07:00
Justin Mayer
76e799641f Merge pull request #79 from diimpp/patch-1
Add missing __init__ to gallery plugin
2013-09-13 09:03:47 -07:00
blog derek
8a846c5888 removed code example from article.html example because it referenced page-only commands 2013-09-13 04:46:07 -07:00
blog derek
92746659b1 added file to make gallery recognized by pelican. recommended by __number5__ in #pelican IRC chat. 2013-09-13 04:43:24 -07:00
Dmitri Perunov
d726fd9b05 Add missing __init__ 2013-09-12 14:28:49 -07:00
Justin Mayer
506f265214 Merge pull request #72 from talha131/fix-liquid-tags-include_code
Remove legacy code in liquid tags include_code plugin
2013-09-01 05:52:32 -07:00
Talha Mansoor
29b9e8d3ae Remove incorrect folder from description in docs
Plugin reads files from `content/code/`, instead of
`content/downloads/code/`.
2013-09-01 17:35:34 +05:00
Talha Mansoor
4a37cee555 Remove legacy STATIC_OUT_DIR from code
Closes getpelican/pelican-plugins#71
2013-09-01 17:35:07 +05:00
Alexis Metaireau
24b1332753 Merge pull request #70 from talha131/fix-liquid-tag-issue-n-doc
Fix liquid tag that was broken due to change in pelican
2013-09-01 04:49:06 -07:00
Talha Mansoor
7013a82063 Import default markdown extensions from settings instead of readers
Closes getpelican/pelican-plugins#69

Due to change in pelican code, `import EXTENSIONS` was no longer
working.
2013-09-01 14:26:09 +05:00
Talha Mansoor
470c8237d1 Fix a typo in the Readme
It showed incorrect tag and the syntax was not in a code block
2013-09-01 14:25:01 +05:00
Alexis Metaireau
562c57c4e9 Merge pull request #67 from talha131/rename-json-serializer
Rename json_serializer plugin to tipue_search
2013-08-29 07:13:59 -07:00
Talha Mansoor
0cafe10d16 Rename json_serializer plugin to tipue_search
This plugin is renamed after suggestions from @astorije and @ametaireau
which can be viewed
[here](https://github.com/getpelican/pelican-plugins/pull/66)
2013-08-29 14:38:39 +05:00
Alexis Metaireau
0d30333281 Merge pull request #66 from talha131/json-serializer
Add JSON Serializer plugin to enable search
2013-08-29 01:29:23 -07:00
Talha Mansoor
7b4d03a204 Add JSON Serializer plugin
Updates getpelican/pelican#170
2013-08-29 01:32:32 +05:00
Justin Mayer
1bda804dc2 Merge pull request #65 from jakevdp/notebook_bug
BUG: liquid tags notebook version check
2013-08-28 12:38:19 -07:00
Jake Vanderplas
ecf7c01825 BUG: liquid tags notebook version check 2013-08-28 10:42:56 -07:00
Alexis Métaireau
b01cb35645 move the gallery out of pelicanext 2013-08-28 14:13:50 +02:00
Alexis Metaireau
fecb834abb Merge pull request #17 from McPo/gallery
Gallery Plugin
2013-08-28 05:12:19 -07:00
Justin Mayer
a20ca76b7c Merge pull request #21 from jakevdp/liquid_tags
Add Liquid Tags plugin
2013-08-27 19:18:08 -07:00
Jake Vanderplas
6cd4482143 explicitly check for IPython version 2013-08-25 20:15:35 -07:00
Jake Vanderplas
e36dae6c11 provide warning if IPython 2.0-dev is being used 2013-08-20 10:20:33 -07:00
Jake Vanderplas
9e416a73b2 fix mathjax script 2013-08-20 09:53:05 -07:00
Alexis Metaireau
4964604e7c Merge pull request #64 from kura/vimeo-youtube
Added YouTube and Vimeo plugins
2013-08-19 03:43:48 -07:00
Kura
1f2c383b7e Added YouTube and Vimeo plugins 2013-08-19 11:25:52 +01:00
Talha Mansoor
d3495d0249 Update article_generate_context to article_generator_context
getpelican/pelican@f2d6f77462 changed
`article_generate_context` to `article_generator_context`. This commit
updates the plugin to reflect the change.
2013-08-11 02:14:54 +05:00
Dmitriy Sukharev
1ef014c91d Made modification date for URL wrappers (categories,tags,etc) be the date of modification of last article within this wrapper; made plugin check modification attribute, not only date attribute. 2013-08-10 14:47:29 +03:00
Jake Vanderplas
4643ab7186 adjust anim_icon CSS 2013-08-07 13:01:22 -07:00
Jake Vanderplas
c545c9e56d modify mathjax to not interfere with normal posts 2013-08-06 20:08:37 -07:00
Jake Vanderplas
499ee9890a Change names for compatibility with IPython 1.0alpha 2013-08-06 19:18:13 -07:00
eikiu
59def6018d Created interlinks plugin 2013-07-22 22:10:21 -03:00
Mark Lee
c9823a72f7 assets: add support for specifying additional source load paths 2013-07-21 17:03:56 -07:00
Mark Lee
250b08a389 assets: add support for named bundles via the settings file 2013-07-21 16:43:43 -07:00
Jake Vanderplas
2ad2132261 ActivatableTransformer -> Transformer 2013-07-18 07:21:49 -07:00
Jake Vanderplas
6f8d398bb6 address notebook comments 2013-07-17 19:54:35 -07:00
Jake Vanderplas
2c4f75e008 make liquid_tags.notebook compatible with IPython 1.0 2013-07-16 15:00:33 -07:00
Jake Vanderplas
5af2ff3c81 make compatible with newer IPython 2013-06-01 10:32:15 -07:00
Emmet McPoland
3df9768a04 Removed Docstring clarified stub 2013-05-24 01:42:26 +00:00
Jake Vanderplas
92e448340c properly set language in include_code 2013-05-09 07:38:34 -07:00
Jake Vanderplas
ce11ec8b20 add literal tag for displaying {% ... %} 2013-05-07 21:30:53 -07:00
Jake Vanderplas
5977160bb6 fix notebook cell parsing 2013-05-05 21:00:09 -07:00
Jake Vanderplas
76d27c2876 fix cell indexing issue 2013-05-05 07:36:27 -07:00
Jake Vanderplas
e0e303adf3 save notebook header only once 2013-05-05 06:49:00 -07:00
Jake Vanderplas
d091f2780b add ability to specify static directory 2013-05-04 21:25:34 -07:00
Jake Vanderplas
d5e4d179e9 add ablility to specify notebook cells 2013-05-04 07:21:10 -07:00
Jake Vanderplas
c0e756209d change to work with new nbconvert 2013-05-04 06:43:19 -07:00
Jake Vanderplas
a6ad0e66e6 update readme 2013-05-03 07:42:22 -07:00
Jake Vanderplas
790fe92e3e make code & notebook directories configurable 2013-05-03 07:36:36 -07:00
Jake Vanderplas
a934cde442 fix notebook formatting 2013-05-03 07:22:41 -07:00
Jake Vanderplas
87f81ab4f5 add ~ files to gitignore 2013-05-03 07:22:31 -07:00
Jake Vanderplas
1ae1833864 add notebook tag 2013-05-02 22:47:52 -07:00
Jake Vanderplas
57d62ce99f initial commit: img, video, include_code tags 2013-05-02 08:36:54 -07:00
Emmet McPoland
fec36746b3 Initial Gallery Plugin Commit 2013-04-11 12:53:24 +01:00
122 changed files with 5910 additions and 209 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*.pyc
*.log
*.log
*~

9
.gitmodules vendored Normal file
View File

@@ -0,0 +1,9 @@
[submodule "pelican_youtube"]
path = pelican_youtube
url = https://github.com/kura/pelican_youtube.git
[submodule "pelican_vimeo"]
path = pelican_vimeo
url = https://github.com/kura/pelican_vimeo.git
[submodule "cjk-auto-spacing"]
path = cjk-auto-spacing
url = https://github.com/yuex/cjk-auto-spacing.git

View File

@@ -13,7 +13,7 @@ functions, including:
* CSS compiler (``less``, ``sass``, ...)
* JS minifier (``uglifyjs``, ``yui_js``, ``closure``, ...)
Others filters include gzip compression, integration of images in CSS via data
Others filters include CSS URL rewriting, integration of images in CSS via data
URIs, and more. Webassets can also append a version identifier to your asset
URL to convince browsers to download new versions of your assets when you use
far-future expires headers. Please refer to the `Webassets documentation`_ for
@@ -51,11 +51,11 @@ Another example for Javascript:
.. code-block:: jinja
{% assets filters="uglifyjs,gzip", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %}
{% assets filters="uglifyjs", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %}
<script src="{{ SITEURL }}/{{ ASSET_URL }}"></script>
{% endassets %}
The above will produce a minified and gzipped JS file:
The above will produce a minified JS file:
.. code-block:: html
@@ -64,6 +64,19 @@ The above will produce a minified and gzipped JS file:
Pelican's debug mode is propagated to Webassets to disable asset packaging
and instead work with the uncompressed assets.
If you need to create named bundles (for example, if you need to compile SASS
files before minifying with other CSS files), you can use the ``ASSET_BUNDLES``
variable in your settings file. This is an ordered sequence of 3-tuples, where
the 3-tuple is defined as ``(name, args, kwargs)``. This tuple is passed to the
`environment's register() method`_. The following will compile two SCSS files
into a named bundle, using the ``pyscss`` filter:
.. code-block:: python
ASSET_BUNDLES = (
('scss', ['colors.scss', 'main.scss'], {'filters': 'pyscss'}),
)
Many of Webasset's available compilers have additional configuration options
(i.e. 'Less', 'Sass', 'Stylus', 'Closure_js'). You can pass these options to
Webassets using the ``ASSET_CONFIG`` in your settings file.
@@ -76,5 +89,18 @@ LessCSS's binary:
ASSET_CONFIG = (('closure_compressor_optimization', 'WHITESPACE_ONLY'),
('less_bin', 'lessc.cmd'), )
If you wish to place your assets in locations other than the theme output
directory, you can use ``ASSET_SOURCE_PATHS`` in your settings file to provide
webassets with a list of additional directories to search, relative to the
theme's top-level directory. For example:
.. code-block:: python
ASSET_SOURCE_PATHS = (
'vendor/css',
'scss',
)
.. _Webassets: https://github.com/miracle2k/webassets
.. _Webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
.. _environment's register() method: http://webassets.readthedocs.org/en/latest/environment.html#registering-bundles

View File

@@ -38,17 +38,30 @@ def add_jinja2_ext(pelican):
def create_assets_env(generator):
"""Define the assets environment and pass it to the generator."""
assets_url = 'theme/'
assets_src = os.path.join(generator.output_path, 'theme')
generator.env.assets_environment = Environment(assets_src, assets_url)
theme_static_dir = generator.settings['THEME_STATIC_DIR']
assets_src = os.path.join(generator.output_path, theme_static_dir)
generator.env.assets_environment = Environment(
assets_src, theme_static_dir)
if 'ASSET_CONFIG' in generator.settings:
for item in generator.settings['ASSET_CONFIG']:
generator.env.assets_environment.config[item[0]] = item[1]
if 'ASSET_BUNDLES' in generator.settings:
for name, args, kwargs in generator.settings['ASSET_BUNDLES']:
generator.env.assets_environment.register(name, *args, **kwargs)
if logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
generator.env.assets_environment.debug = True
if 'ASSET_SOURCE_PATHS' in generator.settings:
# the default load path gets overridden if additional paths are
# specified, add it back
generator.env.assets_environment.append_path(assets_src)
for path in generator.settings['ASSET_SOURCE_PATHS']:
full_path = os.path.join(generator.theme, path)
generator.env.assets_environment.append_path(full_path)
def register():
"""Plugin registration."""

View File

@@ -13,13 +13,15 @@ TODO: Need to add a test.py for this plugin.
"""
import os
from os import path, access, R_OK
from pelican import signals
from bs4 import BeautifulSoup
from PIL import Image
import logging
logger = logging.getLogger(__name__)
def content_object_init(instance):
@@ -29,9 +31,30 @@ def content_object_init(instance):
if 'img' in content:
for img in soup('img'):
# TODO: Pretty sure this isn't the right way to do this, too hard coded.
# There must be a setting that I should be using?
src = instance.settings['PATH'] + '/images/' + os.path.split(img['src'])[1]
logger.debug('Better Fig. PATH: %s', instance.settings['PATH'])
logger.debug('Better Fig. img.src: %s', img['src'])
img_path, img_filename = path.split(img['src'])
logger.debug('Better Fig. img_path: %s', img_path)
logger.debug('Better Fig. img_fname: %s', img_filename)
# Strip off {filename}, |filename| or /static
if img_path.startswith(('{filename}', '|filename|')):
img_path = img_path[10:]
elif img_path.startswith('/static'):
img_path = img_path[7:]
else:
logger.warning('Better Fig. Error: img_path should start with either {filename}, |filename| or /static')
# Build the source image filename
src = instance.settings['PATH'] + img_path + '/' + img_filename
logger.debug('Better Fig. src: %s', src)
if not (path.isfile(src) and access(src, R_OK)):
logger.error('Better Fig. Error: image not found: {}'.format(src))
# Open the source image and query dimensions; build style string
im = Image.open(src)
extra_style = 'width: {}px; height: auto;'.format(im.size[0])

View File

@@ -1,3 +1,8 @@
Requirements
------------
* pip install pillow beautifulsoup4
Summary
===========
@@ -47,4 +52,4 @@ or this, if RESPONSIVE_IMAGES = True:
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</div>
</div>
</div>

1
cjk-auto-spacing Submodule

Submodule cjk-auto-spacing added at 92346597b8

34
clean_summary/README.md Normal file
View File

@@ -0,0 +1,34 @@
#Clean Summary Plugin#
Plugin to clean your summary of excess images. Images can take up much more
space than text and lead to summaries being different sizes on archive and
index pages. With this plugin you can specify a maximum number of images that
will appear in your summaries.
There is also an option to include a minimum of one image.
##Settings##
This plugin has two settings. `CLEAN_SUMMARY_MAXIMUM` which takes an int, and
`CLEAN_SUMMARY_MINIMUM_ONE` which takes a boolean. They default to 0 and False.
`CLEAN_SUMMARY_MAXIMUM` sets the maximum number of images that will appear in
your summary.
if `CLEAN_SUMMARY_MINIMUM_ONE` is set to `True` and your summary doesn't already
contain an image, the plugin will add the first image in your article (if one
exists) to the beginning of the summary.
##Requirements##
Requires Beautiful Soup:
pip install BeautifulSoup4
##Usage with Summary Plugin##
If using the summary plugin, make sure summary appears in your plugins before
clean summary. Eg.
PLUGINS = ['summary', 'clean_summary', ... ]

View File

@@ -0,0 +1 @@
from .clean_summary import *

View File

@@ -0,0 +1,38 @@
"""
Clean Summary
-------------
adds option to specify maximum number of images to appear in article summary
also adds option to include an image by default if one exists in your article
"""
from pelican import signals
from pelican.contents import Content, Article
from bs4 import BeautifulSoup
from six import text_type
def clean_summary(instance):
if "CLEAN_SUMMARY_MAXIMUM" in instance.settings:
maximum_images = instance.settings["CLEAN_SUMMARY_MAXIMUM"]
else:
maximum_images = 0
if "CLEAN_SUMMARY_MINIMUM_ONE" in instance.settings:
minimum_one = instance.settings['CLEAN_SUMMARY_MINIMUM_ONE']
else:
minimum_one = False
if type(instance) == Article:
summary = instance.summary
summary = BeautifulSoup(instance.summary, 'html.parser')
images = summary.findAll('img')
if (len(images) > maximum_images):
for image in images[maximum_images:]:
image.extract()
if len(images) < 1 and minimum_one: #try to find one
content = BeautifulSoup(instance.content, 'html.parser')
first_image = content.find('img')
if first_image:
summary.insert(0, first_image)
instance._summary = text_type(summary)
def register():
signals.content_object_init.connect(clean_summary)

41
creole_reader/Readme.md Normal file
View File

@@ -0,0 +1,41 @@
# Creole Reader
This plugins allows you to write your posts using the wikicreole syntax. Give to
these files the creole extension. The metadata are between `<<header>> <</header>>`
tags.
## Dependency
This plugin relies on [python-creole](https://pypi.python.org/pypi/python-creole/) to work. Install it with:
`pip install python-creole`
## Syntax
Use ** for strong, // for emphasis, one = for 1st level titles. Please use the
following macro for code highlighting:
`<<code ext=".file_extension">> <</code>>`
For the complete syntax, look at: http://www.wikicreole.org/
## Basic example
```
<<header>>
title: Créole
tags: creole, python, pelican_open
date: 2013-12-12
<</header>>
= Title 1
== Title 2
Some nice text with **strong** and //emphasis//.
* A nice list
** With sub-elements
* Python
<<code ext=".py">>
print("Hello World")
<</code>>
# An ordered list
# A second item
```

View File

@@ -0,0 +1 @@
from .creole_reader import *

View File

@@ -0,0 +1,101 @@
#-*- conding: utf-8 -*-
'''
Creole Reader
-------------
This plugins allows you to write your posts using the wikicreole syntax. Give to
these files the creole extension.
For the syntax, look at: http://www.wikicreole.org/
'''
from pelican import readers
from pelican import signals
from pelican import settings
from pelican.utils import pelican_open
try:
from creole import creole2html
creole = True
except ImportError:
creole = False
try:
from pygments import lexers
from pygments.formatters import HtmlFormatter
from pygments import highlight
PYGMENTS = True
except:
PYGMENTS = False
class CreoleReader(readers.BaseReader):
enabled = creole
file_extensions = ['creole']
def __init__(self, settings):
super(CreoleReader, self).__init__(settings)
def _parse_header_macro(self, text):
for line in text.split('\n'):
name, value = line.split(':')
name, value = name.strip(), value.strip()
if name == 'title':
self._metadata[name] = value
else:
self._metadata[name] = self.process_metadata(name, value)
return u''
def _no_highlight(self, text):
html = u'\n<pre><code>{}</code></pre>\n'.format(text)
return html
def _get_lexer(self, source_type, code):
try:
return lexers.get_lexer_by_name(source_type)
except:
return lexers.guess_lexer(code)
def _get_formatter(self):
formatter = HtmlFormatter(lineos = True, encoding='utf-8',
style='colorful', outencoding='utf-8',
cssclass='pygments')
return formatter
def _parse_code_macro(self, ext, text):
if not PYGMENTS:
return self._no_highlight(text)
try:
source_type = ''
if '.' in ext:
source_type = ext.strip().split('.')[1]
else:
source_type = ext.strip()
except IndexError:
source_type = ''
lexer = self._get_lexer(source_type, text)
formatter = self._get_formatter()
try:
return highlight(text, lexer, formatter).decode('utf-8')
except:
return self._no_highlight(text)
# You need to have a read method, which takes a filename and returns
# some content and the associated metadata.
def read(self, source_path):
"""Parse content and metadata of creole files"""
self._metadata = {}
with pelican_open(source_path) as text:
content = creole2html(text, macros={'header': self._parse_header_macro,
'code': self._parse_code_macro})
return content, self._metadata
def add_reader(readers):
readers.reader_classes['creole'] = CreoleReader
def register():
signals.readers_init.connect(add_reader)

View File

@@ -0,0 +1,36 @@
#Custom Article URLs#
This plugin adds support for defining different default URLs for different
categories, or different subcategories if using the subcategory plugin.
##Usage##
After adding `custom_article_urls` to your `PLUGINS`, add a
`CUSTOM_ARTICLE_URLS` setting, which is a dictionary of rules. The rules are
also a dictionary, consisting of the `URL` and the `SAVE_AS` values.
For example, if you had two categories, *Category 1* and *Category 2*, and you
would like *Category 1* saved as `category-1/article-slug/` and *Category 2*
saved as `/year/month/article-slug/`, you would add:
CUSTOM_ARTICLE_URLS = {
'Category 1': {'URL': '{category}/{slug}/,
'SAVE_AS': '{category}/{slug}/index.html},
'Category 2': {'URL': '{date:%Y}/{date:%B}/{slug}/,
'SAVE_AS': '{date:%Y}/{date:%B}/{slug}/index.html},
}
If you had any other categories, they would use the default `ARTICLE_SAVE_AS`
and `ARTICLE_URL` settings.
If you are using the subcategory plugin, you can define them the same way.
For example, if *Category 1* had a subcategory called *Sub Category*, you could
define its rules with::
'Category 1/Sub Category`: ...
##Other Usage: Article Metadata##
If you define `URL` and `Save_as` in your article metadata, then this plugin
will not alter that value. So you can still specify special one-off URLs as
you normally would.

View File

@@ -0,0 +1 @@
from .custom_article_urls import *

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
@Author: Alistair Magee
Adds ability to specify custom urls for different categories
(or subcategories if using subcategory plugin) of article
using a dictionary stored in pelican settings file as
{category: {article_url_structure: stirng, article_save_as: string}}
"""
from pelican import signals
from pelican.contents import Content, Category
from six import text_type
def custom_url(generator, metadata):
if 'CUSTOM_ARTICLE_URLS' in generator.settings:
custom_urls = generator.settings['CUSTOM_ARTICLE_URLS']
category = text_type(metadata['category'])
pattern_matched = {}
if category in custom_urls:
pattern_matched = custom_urls[category]
if 'subcategories' in metadata: #using subcategory plugin
for subcategory in metadata['subcategories']:
if subcategory in custom_urls:
pattern_matched = custom_urls[subcategory]
if pattern_matched:
#only alter url if hasn't been set in the metdata
if ('url', 'save_as') in metadata:
""" if both url and save_as are set in the metadata already
then there is already a custom url set, skip this one
"""
pass
else:
temp_article = Content(None, metadata=metadata)
url_format = pattern_matched['URL']
save_as_format = pattern_matched['SAVE_AS']
url = url_format.format(**temp_article.url_format)
save_as = save_as_format.format(**temp_article.url_format)
metadata.update({'url': url, 'save_as': save_as})
def register():
signals.article_generator_context.connect(custom_url)

View File

@@ -15,7 +15,7 @@ We use disqus-python package for communication with disqus API:
Put ``disqus_static.py`` plugin in ``plugins`` folder in pelican installation
and use the following in your settings::
PLUGINS = [u"pelican.plugins.disqus_static"]
PLUGINS = [u"disqus_static"]
DISQUS_SITENAME = u'YOUR_SITENAME'
DISQUS_SECRET_KEY = u'YOUR_SECRET_KEY'

1
disqus_static/__init__py Normal file
View File

@@ -0,0 +1 @@
from .disqus_static import *

View File

@@ -10,9 +10,9 @@ from disqusapi import DisqusAPI, Paginator
from pelican import signals
def initialized(pelican):
from pelican.settings import _DEFAULT_CONFIG
_DEFAULT_CONFIG.setdefault('DISQUS_SECRET_KEY', '')
_DEFAULT_CONFIG.setdefault('DISQUS_PUBLIC_KEY', '')
from pelican.settings import DEFAULT_CONFIG
DEFAULT_CONFIG.setdefault('DISQUS_SECRET_KEY', '')
DEFAULT_CONFIG.setdefault('DISQUS_PUBLIC_KEY', '')
if pelican:
pelican.settings.setdefault('DISQUS_SECRET_KEY', '')
pelican.settings.setdefault('DISQUS_PUBLIC_KEY', '')

69
exif_info/README.md Normal file
View File

@@ -0,0 +1,69 @@
EXIF Info
==================
* Retrieve EXIF informations from pictures
##How to Use
1. Install the PIL (Python Imaging Library) libraries, please refer to http://pythonware.com/products/pil/ or your OS manual for instructions.
2. This plugin is an extension to the Gallery plugin, so first install it, and refer to its README for usage instructions.
3. Add 'exif_info' to the plugin list.
If you want, you can also specify in your configuration:
EXIF_INFO_DEFAULT = True/False
To set whether or not to retrieve by default the EXIF informations from your pictures. This setting can be overriden on a per article/album basis.
###Articles
Override on a per article/post basis the default behaviour by adding the following:
exifinfo: "True"
or
exifinfo: "False"
###Gallery Page
At this time the Gallery page is *not* supported.
##Examples
###article.html
{% if article.album %}
{% for image in article.galleryimages %}
{% if article.galleryexif and article.galleryexif.get(image) %}
<table>
{% if article.galleryexif.get(image).get("Model") %}
<tr><th colspan="4">{{article.galleryexif.get(image).get("Model")}}</th></tr>
{% endif %}
{% if article.galleryexif.get(image).get("LensModel") %}
<tr><th colspan="4">{{article.galleryexif.get(image).get("LensModel")}}</th></tr>
{% endif %}
<tr>
{% if article.galleryexif.get(image).get("ISOSpeedRatings") %}
<td>{{article.galleryexif.get(image).get("ISOSpeedRatings")}}</td>
{% endif %}
{% if article.galleryexif.get(image).get("FocalLength") %}
<td>{{article.galleryexif.get(image).get("FocalLength")}}mm</td>
{% endif %}
{% if article.galleryexif.get(image).get("FNumber") %}
<td>f/{{article.galleryexif.get(image).get("FNumber")}}</td>
{% endif %}
{% if article.galleryexif.get(image).get("ExposureTime") %}
<td>{{article.galleryexif.get(image).get("ExposureTime")}}</td>
{% endif %}
</tr>
</table>
{% endif %}
{% endfor %}
{% endif %}
##Reasoning
This was developped as an external plugin, instead of adapting the Gallery plugin because this relies on the PIL libraries to be installed, and working.
As this set of library may - or not - be available on a given platform, it seemed unreasonable to limit the Gallery plugin to the systems where it is.

1
exif_info/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .exif_info import *

86
exif_info/exif_info.py Normal file
View File

@@ -0,0 +1,86 @@
import os
import re
from pelican import signals
from PIL import Image
from PIL.ExifTags import TAGS
def get_exif_data(fname):
"""Get embedded EXIF data from an image file."""
exif = {}
ret = {}
try:
img = Image.open(fname)
if hasattr( img, '_getexif' ):
exifinfo = img._getexif()
if exifinfo != None:
for tag, value in exifinfo.items():
decoded = TAGS.get(tag, tag)
ret[decoded] = value
except IOError:
print 'IOERROR ' + fname
#print ret
# Keep and format the most interesting fields
for tag in ret:
if tag == "DateTimeOriginal":
exif[tag] = ret[tag]
if tag == "Model":
exif[tag] = ret[tag]
if tag == "LensModel":
exif[tag] = ret[tag]
if tag == "ISOSpeedRatings":
exif[tag] = ret[tag]
if tag == "FocalLength":
exif[tag] = "{:2.1f}".format(float(ret[tag][0]) / float(ret[tag][1]))
if tag == "FNumber":
exif[tag] = "{:2.1f}".format(float(ret[tag][0]) / float(ret[tag][1]))
if tag == "ExposureTime":
exif[tag] = str(ret[tag][0]) + "/" + str(ret[tag][1])
return exif
def add_exif_post(generator):
get_exif = generator.settings.get('EXIF_INFO_DEFAULT')
if get_exif == None:
get_exif = True;
contentpath = generator.settings.get('PATH')
gallerycontentpath = os.path.join(contentpath,'images/gallery')
for article in generator.articles:
if 'exifinfo' in article.metadata.keys():
if article.metadata.get('exifinfo'):
# Ignore anything which is not a capitalization variation of
# true/false
if article.metadata.get('exifinfo').lower() == "true":
get_exif = True;
if article.metadata.get('exifinfo').lower() == "false":
get_exif = False;
if get_exif:
if 'gallery' in article.metadata.keys():
album = article.metadata.get('gallery')
galleryexif = dict()
articlegallerypath=os.path.join(gallerycontentpath, album)
# If the gallery has not yet been generated generate one
if article.metadata.get('galleryimages'):
galleryimages = article.metadata.get('galleryimages');
else:
galleryimages = []
if(os.path.isdir(articlegallerypath)):
for i in os.listdir(articlegallerypath):
if os.path.isfile(os.path.join(os.path.join(gallerycontentpath, album), i)):
galleryimages.append(i)
# Retrieve the EXIF informations for all the images
for img in galleryimages:
galleryexif[img] = get_exif_data(articlegallerypath + "/" + img)
article.galleryexif = galleryexif
def register():
signals.article_generator_finalized.connect(add_exif_post)

33
feed_summary/Readme.md Normal file
View File

@@ -0,0 +1,33 @@
# Feed Summary #
This plugin allows article summaries to be used in ATOM and RSS feeds instead of the entire article. It uses the
built-in pelican `Summary:` metadata.
The summary of an article can either be set explicitly with the `Summary:` metadata attribute as described in the
[pelican getting started docs](http://docs.getpelican.com/en/latest/getting_started.html#file-metadata),
or automatically generated using the number of words specified in the
[SUMMARY_MAX_LENGTH](http://docs.getpelican.com/en/latest/settings.html) setting.
## Usage ##
To use this plugin, ensure the following are set in your `pelicanconf.py` file:
PLUGIN_PATH = '/path/to/pelican-plugins'
PLUGINS = [
'feed_summary',
]
'FEED_USE_SUMMARY' = True
The default value of `'FEED_USE_SUMMARY'` is `False`, so it must be set to `True` to enable the plugin, even if it is loaded.
This plugin is written for pelican 3.3 and later.
## Implementation Notes ##
This plugin derives `FeedSummaryWriter` from the `Writer` class, duplicating code of the `Writer._add_item_to_the_feed` method.
When the `initialized` signal is sent, it alternates the `get_writer` method of the `Pelican` object to use `FeedSummaryWriter` instead of `Writer`.
A little hackish, but currently this can't be done otherwise via the regular plugin methods.
* *Initial Code (PR #36): Michelle L. Gill <michelle.lynn.gill@gmail.com>*
* *Resumption of PR and Maintainer: Florian Jacob ( projects[PLUS]pelican[ÄT]florianjacob.de )*

1
feed_summary/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .feed_summary import *

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""
Feed Summary
============
This plugin allows summaries to be used in feeds instead of the full length article.
"""
from __future__ import unicode_literals
from jinja2 import Markup
import six
if not six.PY3:
from urlparse import urlparse
else:
from urllib.parse import urlparse
from pelican import signals
from pelican.writers import Writer
from pelican.utils import set_date_tzinfo
from .magic_set import magic_set
class FeedSummaryWriter(Writer):
def _add_item_to_the_feed(self, feed, item):
if self.settings['FEED_USE_SUMMARY']:
title = Markup(item.title).striptags()
link = '%s/%s' % (self.site_url, item.url)
feed.add_item(
title=title,
link=link,
unique_id='tag:%s,%s:%s' % (urlparse(link).netloc,
item.date.date(),
urlparse(link).path.lstrip('/')),
description=item.summary if hasattr(item, 'summary') else item.get_content(self.site_url),
categories=item.tags if hasattr(item, 'tags') else None,
author_name=getattr(item, 'author', ''),
pubdate=set_date_tzinfo(item.modified if hasattr(item, 'modified') else item.date,
self.settings.get('TIMEZONE', None)))
else:
super(FeedSummaryWriter, self)._add_item_to_the_feed(feed, item)
def set_feed_use_summary_default(pelican_object):
# modifying DEFAULT_CONFIG doesn't have any effect at this point in pelican setup
# everybody who uses DEFAULT_CONFIG is already used/copied it or uses the pelican_object.settings copy.
pelican_object.settings.setdefault('FEED_USE_SUMMARY', False)
def patch_pelican_writer(pelican_object):
@magic_set(pelican_object)
def get_writer(self):
return FeedSummaryWriter(self.output_path,settings=self.settings)
def register():
signals.initialized.connect(set_feed_use_summary_default)
signals.initialized.connect(patch_pelican_writer)

92
feed_summary/magic_set.py Normal file
View File

@@ -0,0 +1,92 @@
import types
import inspect
# Modifies class methods (or instances of them) on the fly
# http://blog.ianbicking.org/2007/08/08/opening-python-classes/
# http://svn.colorstudy.com/home/ianb/recipes/magicset.py
def magic_set(obj):
"""
Adds a function/method to an object. Uses the name of the first
argument as a hint about whether it is a method (``self``), class
method (``cls`` or ``klass``), or static method (anything else).
Works on both instances and classes.
>>> class color:
... def __init__(self, r, g, b):
... self.r, self.g, self.b = r, g, b
>>> c = color(0, 1, 0)
>>> c # doctest: +ELLIPSIS
<__main__.color instance at ...>
>>> @magic_set(color)
... def __repr__(self):
... return '<color %s %s %s>' % (self.r, self.g, self.b)
>>> c
<color 0 1 0>
>>> @magic_set(color)
... def red(cls):
... return cls(1, 0, 0)
>>> color.red()
<color 1 0 0>
>>> c.red()
<color 1 0 0>
>>> @magic_set(color)
... def name():
... return 'color'
>>> color.name()
'color'
>>> @magic_set(c)
... def name(self):
... return 'red'
>>> c.name()
'red'
>>> @magic_set(c)
... def name(cls):
... return cls.__name__
>>> c.name()
'color'
>>> @magic_set(c)
... def pr(obj):
... print obj
>>> c.pr(1)
1
"""
def decorator(func):
is_class = (isinstance(obj, type)
or isinstance(obj, types.ClassType))
args, varargs, varkw, defaults = inspect.getargspec(func)
if not args or args[0] not in ('self', 'cls', 'klass'):
# Static function/method
if is_class:
replacement = staticmethod(func)
else:
replacement = func
elif args[0] == 'self':
if is_class:
replacement = func
else:
def replacement(*args, **kw):
return func(obj, *args, **kw)
try:
replacement.func_name = func.func_name
except:
pass
else:
if is_class:
replacement = classmethod(func)
else:
def replacement(*args, **kw):
return func(obj.__class__, *args, **kw)
try:
replacement.func_name = func.func_name
except:
pass
setattr(obj, func.func_name, replacement)
return replacement
return decorator
if __name__ == '__main__':
import doctest
doctest.testmod()

114
gallery/README.md Normal file
View File

@@ -0,0 +1,114 @@
Gallery
==================
* Allows an article to contain an album of pictures.
* All albums can also be syndicated into a central gallery page.
##How to Use
1. Group images into folders, with each folder representing an album.
2. Place all album folders within a folder named gallery, which resides within the images folder.
./content/images/gallery/album_name
###Articles
Attach an album to an article/post by placing a gallery metadata tag with the name of the album.
gallery:album_name
Optionaly you can also add (on one(1) line!)
gallerycaptions:{'file1':'title1', 'file4':'title4'}
The template has access to the album name.
article.album
And the filename of images within an album.
article.albumimages
And the titles associated to the pictures, as a dictionary.
article.gallerycaptions
###Gallery Page
Create a page and a gallery template (named gallery.html). And inform pelican to use the gallery template for the page.
template:gallery
The template has access to a dictionary of lists.
The dictionary key is the name of the album and the lists contain the filenames.
page.gallery
##Examples
###article.html
<h2><a href="{{ SITEURL }}/pages/gallery.html#{{ article.album }}">{{ article.album }}</a></h2>
<ul>
{% for image in article.galleryimages %}
{% if article.gallerycaptions.get(image) %}
<li><a class="{{ article.album }} cboxElement" href="{{ SITEURL }}/static/images/gallery/{{ article.album }}/{{ image }}" title="{{ article.gallerycaptions.get(image) }}">{{ article.gallerycaptions.get(image) }}<br><img src="{{ SITEURL }}/static/images/gallery200x200/{{ article.album }}/{{ image }}"></a></li>
{% else %}
<li><a class="{{ article.album }} cboxElement" href="{{ SITEURL }}/static/images/gallery/{{ article.album }}/{{ image }}"><img src="{{ SITEURL }}/static/images/gallery200x200/{{ article.album }}/{{ image }}"></a></li>
{% endif %}
{% endfor %}
</ul>
###gallery.html
{% for album, images in page.gallery.iteritems() %}
<h2><a name="{{ album }}">{{ album }}</a></h2>
<ul>
{% for image in images %}
<li><a class="{{ album }} cboxElement" href="{{ SITEURL }}/static/images/gallery/{{album}}/{{ image }}" title="{{ image }}"><img src="{{ SITEURL }}/static/images/gallery200x200/{{album}}/{{ image }}"></a></li>
{% endfor %}
</ul>
{% endfor %}
###posts/foo.md
title:Foo
gallery:albumname
or, to add captions:
title:Foo
gallery:albumname
gallerycaptions:{'file1':'title1', 'file4':'title4'}
###pages/gallery.md
title:All Images
template:gallery
##Reasoning
The album name and filenames are returned as opposed to the direct path to the images,
to allow flexibility of different thumbnail sizes to be used it different locations of a website.
href="{{ SITEURL }}/static/images/gallery/{{album}}/{{ image }}"
href="{{ SITEURL }}/static/images/gallery200x200/{{album}}/{{ image }}"
It also allows a thumbnail to link to the full image,
as well as the filename extension to be stripped and the title of an image to be displayed along side the title of an album.
##Recommendation
It is recommended to use this extension along with the thumbnailer plugin.
RESIZE = [
('gallery', False, 200,200),
]
You may also wish to use this along with a gallery plugin such as [Colorbox](http://www.jacklmoore.com/colorbox/).
##In Use
* [SESIF Article](http://sesif.github.io/my-super-title.html)
* [SESIF Gallery](http://sesif.github.io/pages/gallery.html)
* [SESIF Source](http://github.com/SESIF/SESIF.github.io/tree/source)

1
gallery/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .gallery import *

61
gallery/gallery.py Normal file
View File

@@ -0,0 +1,61 @@
import os
import ast
from pelican import signals
def add_gallery_post(generator):
contentpath = generator.settings.get('PATH')
gallerycontentpath = os.path.join(contentpath,'images/gallery')
for article in generator.articles:
if 'gallery' in article.metadata.keys():
album = article.metadata.get('gallery')
galleryimages = []
gallerycaptions = dict()
gallerycomments = dict()
articlegallerypath=os.path.join(gallerycontentpath, album)
if(os.path.isdir(articlegallerypath)):
for i in os.listdir(articlegallerypath):
if os.path.isfile(os.path.join(os.path.join(gallerycontentpath, album), i)):
galleryimages.append(i)
if 'gallerycaptions' in article.metadata.keys():
line = article.metadata.get('gallerycaptions').encode('ascii','xmlcharrefreplace')
gallerycaptions = ast.literal_eval(line)
if 'gallerycomments' in article.metadata.keys():
line = article.metadata.get('gallerycomments').encode('ascii','xmlcharrefreplace')
gallerycomments = ast.literal_eval(line)
article.album = album
article.galleryimages = sorted(galleryimages)
article.gallerycaptions = gallerycaptions
article.gallerycomments = gallerycomments
def generate_gallery_page(generator):
contentpath = generator.settings.get('PATH')
gallerycontentpath = os.path.join(contentpath,'images/gallery')
for page in generator.pages:
if page.metadata.get('template') == 'gallery':
gallery = dict()
for a in os.listdir(gallerycontentpath):
if os.path.isdir(os.path.join(gallerycontentpath, a)):
for i in os.listdir(os.path.join(gallerycontentpath, a)):
if os.path.isfile(os.path.join(os.path.join(gallerycontentpath, a), i)):
gallery.setdefault(a, []).append(i)
gallery[a].sort()
page.gallery=gallery
def register():
signals.article_generator_finalized.connect(add_gallery_post)
signals.page_generator_finalized.connect(generate_gallery_page)

View File

@@ -9,6 +9,11 @@ For example, to track Pelican project activity, the setting would be::
GITHUB_ACTIVITY_FEED = 'https://github.com/getpelican.atom'
If you want to limit the amount of entries to a certain maximum set the
``GITHUB_ACTIVITY_MAX_ENTRIES`` parameter.
GITHUB_ACTIVITY_MAX_ENTRIES = 10
On the template side, you just have to iterate over the ``github_activity``
variable, as in this example::

View File

@@ -25,6 +25,7 @@ class GitHubActivity():
import feedparser
self.activities = feedparser.parse(
generator.settings['GITHUB_ACTIVITY_FEED'])
self.max_entries = generator.settings['GITHUB_ACTIVITY_MAX_ENTRIES']
def fetch(self):
"""
@@ -37,7 +38,7 @@ class GitHubActivity():
[element for element in [activity['title'],
activity['content'][0]['value']]])
return entries
return entries[0:self.max_entries]
def fetch_github_activity(gen, metadata):
@@ -65,7 +66,7 @@ def register():
"""
try:
signals.article_generator_init.connect(feed_parser_initialization)
signals.article_generate_context.connect(fetch_github_activity)
signals.article_generator_context.connect(fetch_github_activity)
except ImportError:
logger.warning('`github_activity` failed to load dependency `feedparser`.'
'`github_activity` plugin not loaded.')

View File

@@ -15,4 +15,4 @@ def add_license(generator, metadata):
metadata['license'] = generator.settings['LICENSE']
def register():
signals.article_generate_context.connect(add_license)
signals.article_generator_context.connect(add_license)

View File

@@ -57,7 +57,7 @@ def initialize_feedparser(generator):
def register():
try:
signals.article_generator_init.connect(initialize_feedparser)
signals.article_generate_context.connect(fetch_goodreads_activity)
signals.article_generator_context.connect(fetch_goodreads_activity)
except ImportError:
logger.warning('`goodreads_activity` failed to load dependency `feedparser`.'
'`goodreads_activity` plugin not loaded.')

View File

@@ -0,0 +1,20 @@
GooglePlus Comments Plugin For Pelican
==================================
Adds GooglePlus comments to Pelican
Add the plugin to `pelicanconf.py`:
PLUGIN_PATH = 'pelican-plugins'
PLUGINS = ["googleplus_comments"]
Add a `<div>` for comments to the `article.html` of your template:
<div id="commentswrap">
<div id="comments"></div>
</div>
{{ article.metadata.googleplus_comments }}
See it working, and ask for support:
<http://zonca.github.io/2013/09/google-plus-comments-plugin-for-pelican.html>

View File

@@ -0,0 +1 @@
from .googleplus_comments import *

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""
Google Comments Plugin For Pelican
==================================
Adds Google comments to Pelican
"""
from pelican import signals
googleplus_comments_snippet = """
<script src="https://apis.google.com/js/plusone.js"></script>
<script>
$(document).ready(function () {
gapi.comments.render('comments', {
href: window.location,
width: '600',
first_party_property: 'BLOGGER',
view_type: 'FILTERED_POSTMOD'
});
});
</script>
"""
def add_googleplus_comments(generator, metadata):
metadata["googleplus_comments"] = googleplus_comments_snippet
def register():
signals.article_generator_context.connect(add_googleplus_comments)

View File

@@ -7,9 +7,17 @@ makes the variable available within the article's context. You can add
address. Obviously, that email address must be associated with a Gravatar
account.
Alternatively, you can provide an email address from within article metadata::
Alternatively, you can provide an email address from within article metadata.
In ReSTructuredText::
:email: john.doe@example.com
If the email address is defined via at least one of the two methods above,
the ``author_gravatar`` variable is added to the article's context.
In Markdown::
Email: john.doe@example.com
If the email address is defined via at least one of the two methods above, the
``author_gravatar`` variable is added to the article's context. For markdown,
it is critical the 'E' in Email is capitalized.

View File

@@ -28,4 +28,4 @@ def add_gravatar(generator, metadata):
def register():
signals.article_generate_context.connect(add_gravatar)
signals.article_generator_context.connect(add_gravatar)

73
i18n_subsites/README.rst Normal file
View File

@@ -0,0 +1,73 @@
======================
I18N Sub-sites Plugin
======================
This plugin extends the translations functionality by creating internationalized sub-sites for the default site. It is therefore redundant with the *\*_LANG_{SAVE_AS,URL}* variables, so it disables them to prevent conflicts.
What it does
============
1. The *\*_LANG_URL* and *\*_LANG_SAVE_AS* variables are set to their normal counterparts (e.g. *ARTICLE_URL*) so they don't conflict with this scheme.
2. While building the site for *DEFAULT_LANG* the translations of pages and articles are not generated, but their relations to the original content is kept via links to them.
3. For each non-default language a "sub-site" with a modified config [#conf]_ is created [#run]_, linking the translations to the originals (if available). The configured language code is appended to the *OUTPUT_PATH* and *SITEURL* of each sub-site. For each sub-site, *DEFAULT_LANG* is changed to the language of the sub-site so that articles in a different language are treated as translations.
If *HIDE_UNTRANSLATED_CONTENT* is True (default), content without a translation for a language is generated as hidden (for pages) or draft (for articles) for the corresponding language sub-site.
.. [#conf] For each language a config override is given in the *I18N_SUBSITES* dictionary.
.. [#run] Using a new *PELICAN_CLASS* instance and its ``run`` method, so each sub-site could even have a different *PELICAN_CLASS* if specified in *I18N_SUBSITES* conf overrides.
Setting it up
=============
For each extra used language code, a language-specific variables overrides dictionary must be given (but can be empty) in the *I18N_SUBSITES* dictionary
.. code-block:: python
PLUGINS = ['i18n_subsites', ...]
# mapping: language_code -> conf_overrides_dict
I18N_SUBSITES = {
'cz': {
'SITENAME': 'Hezkej blog',
}
}
- The language code is the language identifier used in the *lang* metadata. It is appended to *OUTPUT_PATH* and *SITEURL* of each I18N sub-site.
- The internationalized config overrides dictionary may specify configuration variable overrides — e.g. a different *LOCALE*, *SITENAME*, *TIMEZONE*, etc. However, it **must not** override *OUTPUT_PATH* and *SITEURL* as they are modified automatically by appending the language code.
Localizing templates
--------------------
Most importantly, this plugin can use localized templates for each sub-site. There are two approaches to having the templates localized:
- You can set a different *THEME* override for each language in *I18N_SUBSITES*, e.g. by making a copy of a theme ``my_theme`` to ``my_theme_lang`` and then editing the templates in the new localized theme. This approach means you don't have to deal with gettext ``*.po`` files, but it is harder to maintain over time.
- You use only one theme and localize the templates using the `jinja2.ext.i18n Jinja2 extension <http://jinja.pocoo.org/docs/templates/#i18n>`_. For a kickstart read this `guide <./localizing_using_jinja2.rst>`_.
It may be convenient to add language buttons to your theme in addition to the translation links of articles and pages. These buttons could, for example, point to the *SITEURL* of each (sub-)site. For this reason the plugin adds these variables to the template context:
main_lang
The language of the top-level site — the original *DEFAULT_LANG*
main_siteurl
The *SITEURL* of the top-level site — the original *SITEURL*
lang_siteurls
An ordered dictionary, mapping all used languages to their *SITEURL*. The ``main_lang`` is the first key with ``main_siteurl`` as the value. This dictionary is useful for implementing global language buttons that show the language of the currently viewed (sub-)site too.
extra_siteurls
An ordered dictionary, subset of ``lang_siteurls``, the current *DEFAULT_LANG* of the rendered (sub-)site is not included, so for each (sub-)site ``set(extra_siteurls) == set(lang_siteurls) - set([DEFAULT_LANG])``. This dictionary is useful for implementing global language buttons that do not show the current language.
If you don't like the default ordering of the ordered dictionaries, use a Jinja2 filter to alter the ordering.
This short `howto <./implementing_language_buttons.rst>`_ shows two example implementations of language buttons.
Usage notes
===========
- It is **mandatory** to specify *lang* metadata for each article and page as *DEFAULT_LANG* is later changed for each sub-site, so content without *lang* metadata woudl be rendered in every (sub-)site.
- As with the original translations functionality, *slug* metadata is used to group translations. It is therefore often convenient to compensate for this by overriding the content URL (which defaults to slug) using the *URL* and *Save_as* metadata.
Future plans
============
- add a test suite
Development
===========
- A demo and test site is in the ``gh-pages`` branch and can be seen at http://smartass101.github.io/pelican-plugins/

View File

@@ -0,0 +1 @@
from .i18n_subsites import *

View File

@@ -0,0 +1,81 @@
import math
import random
from collections import defaultdict
from operator import attrgetter, itemgetter
def regenerate_context_articles(generator):
"""Helper to regenerate context after modifying articles draft state
essentially just a copy from pelican.generators.ArticlesGenerator.generate_context
after process_translations up to signal sending
This has to be kept in sync untill a better solution is found
This is for Pelican version 3.3.0
"""
# Simulate __init__ for fields that need it
generator.dates = {}
generator.tags = defaultdict(list)
generator.categories = defaultdict(list)
generator.authors = defaultdict(list)
# Simulate ArticlesGenerator.generate_context
for article in generator.articles:
# only main articles are listed in categories and tags
# not translations
generator.categories[article.category].append(article)
if hasattr(article, 'tags'):
for tag in article.tags:
generator.tags[tag].append(article)
# ignore blank authors as well as undefined
if hasattr(article, 'author') and article.author.name != '':
generator.authors[article.author].append(article)
# sort the articles by date
generator.articles.sort(key=attrgetter('date'), reverse=True)
generator.dates = list(generator.articles)
generator.dates.sort(key=attrgetter('date'),
reverse=generator.context['NEWEST_FIRST_ARCHIVES'])
# create tag cloud
tag_cloud = defaultdict(int)
for article in generator.articles:
for tag in getattr(article, 'tags', []):
tag_cloud[tag] += 1
tag_cloud = sorted(tag_cloud.items(), key=itemgetter(1), reverse=True)
tag_cloud = tag_cloud[:generator.settings.get('TAG_CLOUD_MAX_ITEMS')]
tags = list(map(itemgetter(1), tag_cloud))
if tags:
max_count = max(tags)
steps = generator.settings.get('TAG_CLOUD_STEPS')
# calculate word sizes
generator.tag_cloud = [
(
tag,
int(math.floor(steps - (steps - 1) * math.log(count)
/ (math.log(max_count)or 1)))
)
for tag, count in tag_cloud
]
# put words in chaos
random.shuffle(generator.tag_cloud)
# and generate the output :)
# order the categories per name
generator.categories = list(generator.categories.items())
generator.categories.sort(
reverse=generator.settings['REVERSE_CATEGORY_ORDER'])
generator.authors = list(generator.authors.items())
generator.authors.sort()
generator._update_context(('articles', 'dates', 'tags', 'categories',
'tag_cloud', 'authors', 'related_posts'))

View File

@@ -0,0 +1,189 @@
"""i18n_subsites plugin creates i18n-ized subsites of the default site"""
import os
import six
import logging
from itertools import chain
from collections import defaultdict, OrderedDict
import gettext
from pelican import signals
from pelican.contents import Page, Article
from pelican.settings import configure_settings
from ._regenerate_context_helpers import regenerate_context_articles
# Global vars
_main_site_generated = False
_main_site_lang = "en"
_main_siteurl = ''
_lang_siteurls = None
logger = logging.getLogger(__name__)
def disable_lang_vars(pelican_obj):
"""Set lang specific url and save_as vars to the non-lang defaults
e.g. ARTICLE_LANG_URL = ARTICLE_URL
They would conflict with this plugin otherwise
"""
global _main_site_lang, _main_siteurl, _lang_siteurls
s = pelican_obj.settings
for content in ['ARTICLE', 'PAGE']:
for meta in ['_URL', '_SAVE_AS']:
s[content + '_LANG' + meta] = s[content + meta]
if not _main_site_generated:
_main_site_lang = s['DEFAULT_LANG']
_main_siteurl = s['SITEURL']
_lang_siteurls = [(lang, _main_siteurl + '/' + lang) for lang in s.get('I18N_SUBSITES', {}).keys()]
# To be able to use url for main site root when SITEURL == '' (e.g. when developing)
_lang_siteurls = [(_main_site_lang, ('/' if _main_siteurl == '' else _main_siteurl))] + _lang_siteurls
_lang_siteurls = OrderedDict(_lang_siteurls)
def create_lang_subsites(pelican_obj):
"""For each language create a subsite using the lang-specific config
for each generated lang append language subpath to SITEURL and OUTPUT_PATH
and set DEFAULT_LANG to the language code to change perception of what is translated
and set DELETE_OUTPUT_DIRECTORY to False to prevent deleting output from previous runs
Then generate the subsite using a PELICAN_CLASS instance and its run method.
"""
global _main_site_generated
if _main_site_generated: # make sure this is only called once
return
else:
_main_site_generated = True
orig_settings = pelican_obj.settings
for lang, overrides in orig_settings.get('I18N_SUBSITES', {}).items():
settings = orig_settings.copy()
settings.update(overrides)
settings['SITEURL'] = _lang_siteurls[lang]
settings['OUTPUT_PATH'] = os.path.join(orig_settings['OUTPUT_PATH'], lang, '')
settings['DEFAULT_LANG'] = lang # to change what is perceived as translations
settings['DELETE_OUTPUT_DIRECTORY'] = False # prevent deletion of previous runs
settings = configure_settings(settings) # to set LOCALE, etc.
cls = settings['PELICAN_CLASS']
if isinstance(cls, six.string_types):
module, cls_name = cls.rsplit('.', 1)
module = __import__(module)
cls = getattr(module, cls_name)
pelican_obj = cls(settings)
logger.debug("Generating i18n subsite for lang '{}' using class '{}'".format(lang, str(cls)))
pelican_obj.run()
_main_site_generated = False # for autoreload mode
def move_translations_links(content_object):
"""This function points translations links to the sub-sites
by prepending their location with the language code
or directs an original DEFAULT_LANG translation back to top level site
"""
for translation in content_object.translations:
if translation.lang == _main_site_lang:
# cannot prepend, must take to top level
lang_prepend = '../'
else:
lang_prepend = translation.lang + '/'
translation.override_url = lang_prepend + translation.url
def update_generator_contents(generator, *args):
"""Update the contents lists of a generator
Empty the (hidden_)translation attribute of article and pages generators
to prevent generating the translations as they will be generated in the lang sub-site
and point the content translations links to the sub-sites
Hide content without a translation for current DEFAULT_LANG
if HIDE_UNTRANSLATED_CONTENT is True
"""
generator.translations = []
is_pages_gen = hasattr(generator, 'pages')
if is_pages_gen:
generator.hidden_translations = []
for page in chain(generator.pages, generator.hidden_pages):
move_translations_links(page)
else: # is an article generator
for article in chain(generator.articles, generator.drafts):
move_translations_links(article)
if not generator.settings.get('HIDE_UNTRANSLATED_CONTENT', True):
return
contents = generator.pages if is_pages_gen else generator.articles
hidden_contents = generator.hidden_pages if is_pages_gen else generator.drafts
default_lang = generator.settings['DEFAULT_LANG']
for content_object in contents[:]: # loop over copy for removing
if content_object.lang != default_lang:
if isinstance(content_object, Article):
content_object.status = 'draft'
elif isinstance(content_object, Page):
content_object.status = 'hidden'
contents.remove(content_object)
hidden_contents.append(content_object)
if not is_pages_gen: # regenerate categories, tags, etc. for articles
if hasattr(generator, '_generate_context_aggregate'): # if implemented
# Simulate __init__ for fields that need it
generator.dates = {}
generator.tags = defaultdict(list)
generator.categories = defaultdict(list)
generator.authors = defaultdict(list)
generator._generate_context_aggregate()
else: # fallback for Pelican 3.3.0
regenerate_context_articles(generator)
def install_templates_translations(generator):
"""Install gettext translations for current DEFAULT_LANG in the jinja2.Environment
if the 'jinja2.ext.i18n' jinja2 extension is enabled
adds some useful variables into the template context
"""
generator.context['main_siteurl'] = _main_siteurl
generator.context['main_lang'] = _main_site_lang
generator.context['lang_siteurls'] = _lang_siteurls
current_def_lang = generator.settings['DEFAULT_LANG']
extra_siteurls = _lang_siteurls.copy()
extra_siteurls.pop(current_def_lang)
generator.context['extra_siteurls'] = extra_siteurls
if 'jinja2.ext.i18n' not in generator.settings['JINJA_EXTENSIONS']:
return
domain = generator.settings.get('I18N_GETTEXT_DOMAIN', 'messages')
localedir = generator.settings.get('I18N_GETTEXT_LOCALEDIR')
if localedir is None:
localedir = os.path.join(generator.theme, 'translations')
if current_def_lang == generator.settings.get('I18N_TEMPLATES_LANG', _main_site_lang):
translations = gettext.NullTranslations()
else:
languages = [current_def_lang]
try:
translations = gettext.translation(domain, localedir, languages)
except (IOError, OSError):
logger.error("Cannot find translations for language '{}' in '{}' with domain '{}'. Installing NullTranslations.".format(languages[0], localedir, domain))
translations = gettext.NullTranslations()
newstyle = generator.settings.get('I18N_GETTEXT_NEWSTYLE', True)
generator.env.install_gettext_translations(translations, newstyle)
def register():
signals.initialized.connect(disable_lang_vars)
signals.generator_init.connect(install_templates_translations)
signals.article_generator_finalized.connect(update_generator_contents)
signals.page_generator_finalized.connect(update_generator_contents)
signals.finalized.connect(create_lang_subsites)

View File

@@ -0,0 +1,113 @@
-----------------------------
Implementing language buttons
-----------------------------
Each article with translations has translations links, but that's the only way to switch between language subsites.
For this reason it is convenient to add language buttons to the top menu bar to make it simple to switch between the language subsites on all pages.
Example designs
---------------
Language buttons showing other available languages
..................................................
The ``extra_siteurls`` dictionary is a mapping of all other (not the *DEFAULT_LANG* of the current (sub-)site) languages to the *SITEURL* of the respective (sub-)sites
.. code-block:: jinja
<!-- SNIP -->
<nav><ul>
{% if extra_siteurls %}
{% for lang, url in extra_siteurls.items() %}
<li><a href="{{ url }}">{{ lang }}</a></li>
{% endfor %}
<!-- separator -->
<li style="background-color: white; padding: 5px;">&nbsp</li>
{% endif %}
{% for title, link in MENUITEMS %}
<!-- SNIP -->
Language buttons showing all available languages, current is active
..................................................................
The ``extra_siteurls`` dictionary is a mapping of all languages to the *SITEURL* of the respective (sub-)sites. This template sets the language of the current (sub-)site as active.
.. code-block:: jinja
<!-- SNIP -->
<nav><ul>
{% if lang_siteurls %}
{% for lang, url in lang_siteurls.items() %}
<li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ url }}">{{ lang }}</a></li>
{% endfor %}
<!-- separator -->
<li style="background-color: white; padding: 5px;">&nbsp</li>
{% endif %}
{% for title, link in MENUITEMS %}
<!-- SNIP -->
Tips and tricks
---------------
Showing more than language codes
................................
If you want the language buttons to show e.g. the names of the languages or flags [#flags]_, not just the language codes, you can use a jinja filter to translate the language codes
.. code-block:: python
languages_lookup = {
'en': 'English',
'cz': 'Čeština',
}
def lookup_lang_name(lang_code):
return languages_lookup[lang_code]
JINJA_FILTERS = {
...
'lookup_lang_name': lookup_lang_name,
}
And then the link content becomes
.. code-block:: jinja
<!-- SNIP -->
{% for lang, siteurl in lang_siteurls.items() %}
<li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ siteurl }}">{{ lang | lookup_lang_name }}</a></li>
{% endfor %}
<!-- SNIP -->
Changing the order of language buttons
......................................
Because ``lang_siteurls`` and ``extra_siteurls`` are instances of ``OrderedDict`` with ``main_lang`` being always the first key, you can change the order through a jinja filter.
.. code-block:: python
def my_ordered_items(ordered_dict):
items = list(ordered_dict.items())
# swap first and last using tuple unpacking
items[0], items[-1] = items[-1], items[0]
return items
JINJA_FILTERS = {
...
'my_ordered_items': my_ordered_items,
}
And then the ``for`` loop line in the template becomes
.. code-block:: jinja
<!-- SNIP -->
{% for lang, siteurl in lang_siteurls | my_ordered_items %}
<!-- SNIP -->
.. [#flags] Although it may look nice, `w3 discourages it <http://www.w3.org/TR/i18n-html-tech-lang/#ri20040808.173208643>`_.

View File

@@ -0,0 +1,142 @@
-----------------------------
Localizing themes with Jinja2
-----------------------------
1. Localize templates
---------------------
To enable the |ext| extension in your templates, you must add it to
*JINJA_EXTENSIONS* in your Pelican configuration
.. code-block:: python
JINJA_EXTENSIONS = ['jinja2.ext.i18n', ...]
Then follow the `Jinja2 templating documentation for the I18N plugin <http://jinja.pocoo.org/docs/templates/#i18n>`_ to make your templates localizable. This usually means surrounding strings with the ``{% trans %}`` directive or using ``gettext()`` in expressions
.. code-block:: jinja
{% trans %}translatable content{% endtrans %}
{{ gettext('a translatable string') }}
For pluralization support, etc. consult the documentation
To enable `newstyle gettext calls <http://jinja.pocoo.org/docs/extensions/#newstyle-gettext>`_ the *I18N_GETTEXT_NEWSTYLE* config variable must be set to ``True`` (default).
.. |ext| replace:: ``jinja2.ext.i18n``
2. Specify translations location
--------------------------------
The |ext| extension uses the `Python gettext library <http://docs.python.org/library/gettext.html>`_ for translating strings.
In your Pelican config you can give the path in which to look for translations in the *I18N_GETTEXT_LOCALEDIR* variable. If not given, it is assumed to be the ``translations`` subfolder in the top folder of the theme specified by *THEME*.
The domain of the translations (the name of each translation file is ``domain.mo``) is controlled by the *I18N_GETTEXT_DOMAIN* config variable (defaults to ``messages``).
Example
.......
With the following in your Pelican settings file
.. code-block:: python
I18N_GETTEXT_LOCALEDIR = 'some/path/'
I18N_GETTEXT_DOMAIN = 'my_domain'
… the translation for language 'cz' will be expected to be in ``some/path/cz/LC_MESSAGES/my_domain.mo``
3. Extract translatable strings and translate them
--------------------------------------------------
There are many ways to extract translatable strings and create ``gettext`` compatible translations. You can create the ``*.po`` and ``*.mo`` message catalog files yourself, or you can use some helper tool as described in `the Python gettext library tutorial <http://docs.python.org/library/gettext.html#internationalizing-your-programs-and-modules>`_.
You of course don't need to provide a translation for the language in which the templates are written which is assumed to be the original *DEFAULT_LANG*. This can be overridden in the *I18N_TEMPLATES_LANG* variable.
Recommended tool: babel
.......................
`Babel <http://babel.pocoo.org/>`_ makes it easy to extract translatable strings from the localized Jinja2 templates and assists with creating translations as documented in this `Jinja2-Babel tutorial <http://pythonhosted.org/Flask-Babel/#translating-applications>`_ [#flask]_ on which the following is based.
1. Add babel mapping
~~~~~~~~~~~~~~~~~~~~
Let's assume that you are localizing a theme in ``themes/my_theme/`` and that you use the default settings, i.e. the default domain ``messages`` and will put the translations in the ``translations`` subdirectory of the theme directory as ``themes/my_theme/translations/``.
It is up to you where to store babel mappings and translation files templates (``*.pot``), but a convenient place is to put them in ``themes/my_theme/`` and work in that directory. From now on let's assume that it will be our current working directory (CWD).
To tell babel to extract translatable strings from the templates create a mapping file ``babel.cfg`` with the following line
.. code-block:: cfg
[jinja2: ./templates/**.html]
2. Extract translatable strings from templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run the following command to create a ``messages.pot`` message catalog template file from extracted translatable strings
.. code-block:: bash
pybabel extract --mapping babel.cfg --output messages.pot ./
3. Initialize message catalogs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to translate the template to language ``lang``, run the following command to create a message catalog
``translations/lang/LC_MESSAGES/messages.po`` using the template ``messages.pot``
.. code-block:: bash
pybabel init --input-file messages.pot --output-dir translations/ --locale lang --domain messages
babel expects ``lang`` to be a valid locale identifier, so if e.g. you are translating for language ``cz`` but the corresponding locale is ``cs``, you have to use the locale identifier. Nevertheless, the gettext infrastructure should later correctly find the locale for a given language.
4. Fill the message catalogs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The message catalog files format is quite intuitive, it is fully documented in the `GNU gettext manual <http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files>`_. Essentially, you fill in the ``msgstr`` strings
.. code-block:: po
msgid "just a simple string"
msgstr "jenom jednoduchý řetězec"
msgid ""
"some multiline string"
"looks like this"
msgstr ""
"nějaký více řádkový řetězec"
"vypadá takto"
You might also want to remove ``#,fuzzy`` flags once the translation is complete and reviewed to show that it can be compiled.
5. Compile the message catalogs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The message catalogs must be compiled into binary format using this command
.. code-block:: bash
pybabel compile --directory translations/ --domain messages
This command might complain about "fuzzy" translations, which means you should review the translations and once done, remove the fuzzy flag line.
(6.) Update the catalogs when templates change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you add any translatable patterns into your templates, you have to update your message catalogs too.
First you extract a new message catalog template as described in the 2. step. Then you run the following command [#pybabel_error]_
.. code-block:: bash
pybabel update --input-file messages.pot --output-dir translations/ --domain messages
This will merge the new patterns with the old ones. Once you review and fill them, you have to recompile them as described in the 5. step.
.. [#flask] Although the tutorial is focused on Flask-based web applications, the linked translation tutorial is not Flask-specific.
.. [#pybabel_error] If you get an error ``TypeError: must be str, not bytes`` with Python 3.3, it is likely you are suffering from this `bug <https://github.com/mitsuhiko/flask-babel/issues/43>`_. Until the fix is released, you can use babel with Python 2.7.

View File

@@ -48,5 +48,5 @@ def add_ical(generator, metadata):
def register():
signals.pages_generator_init.connect(init_cal)
signals.pages_generate_context.connect(add_ical)
signals.page_generator_init.connect(init_cal)
signals.page_generator_context.connect(add_ical)

1
interlinks/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .interlinks import *

46
interlinks/interlinks.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""
Interlinks
=========================
This plugin allows you to include "interwiki" or shortcuts links into the blog, as keyword>rest_of_url
"""
from bs4 import BeautifulSoup
from pelican import signals
import re
interlinks = {}
def getSettings (generator):
global interlinks
interlinks = {'this': generator.settings['SITEURL']+"/"}
if 'INTERLINKS' in generator.settings:
for key, value in generator.settings['INTERLINKS'].items():
interlinks[key] = value
def content_object_init(instance):
if instance._content is not None:
content = instance._content
# use Python's built-in parser so no duplicated html & body tags appear, or use tag.unwrap()
text = BeautifulSoup(content, "html.parser")
if 'a' in content:
for link in text.find_all(href=re.compile("(.+?)>")):
url = link.get('href')
m = re.search(r"(.+?)>", url).groups()
name = m[0]
if name in interlinks:
hi = url.replace(name+">",interlinks[name])
link['href'] = hi
instance._content = text.decode()
def register():
signals.generator_init.connect(getSettings)
signals.content_object_init.connect(content_object_init)

49
interlinks/readme.md Normal file
View File

@@ -0,0 +1,49 @@
Interlinks
=========================
This plugin lets you add frequently used URLs to your markup using short keywords. Short URL format is
``keyword>rest-of-url`` where ``keyword`` is defined in the settings.py. Later it is replaced with acutal URL in
the generated HTML output.
Requirements
--------------------
``interlinks`` requires BeautifulSoup
pip install beautifulsoup4
Installation
--------------------
Install the plugin normally (plugins folder), then add interlinks in the settings.py:
PLUGINS = ["interlinks"]
Usage
------------------
The interlinks are specified in the settings.py file as (example):
INTERLINKS = {
'wikipedia_en': 'http://en.wikipedia.org/wiki/',
'wikipedia_es': 'http://es.wikipedia.org/wiki/',
'ddg': 'https://duckduckgo.com/?q='
}
There's also a default key, ``this``, that is mapped to the ``SITEURL`` variable.
Then, in a blog post, you just create a normal link but adding the ``keyword>`` syntax in the url specification, followed by the rest of the url.
Example
-------------------
(markdown syntax)
[Normal boring link](http://www.example.com). But this is a [cool link](this>) that links to this site.
Search in [Wikipedia](wikipedia_en>python), ([here](wikipedia_es>python) in spanish). Also can [search](ddg>python) it.
All the above will be rendered as:
<p><a href="http://www.example.com">Normal boring link</a>. But this is a <a href="http://[yoursite]/index.html">cool link</a> that links to this site.</p>
<p>Search in <a href="http://en.wikipedia.org/wiki/python">Wikipedia</a>, (<a href="http://es.wikipedia.org/wiki/python">here</a> in spanish). Also can <a href="https://duckduckgo.com/?q=python">search</a> it.</p>

View File

@@ -0,0 +1,9 @@
Title: Testing
Date: 3000-07-09
Slug: plugin-test
Testeando un poco la cosa
[Normal boring link](http://www.example.com). But this is a [cool link](this>) that links to this site.
Search in [Wikipedia](wikipedia_en>python), ([here](wikipedia_es>python) in spanish). Also can [search](ddg>python) it.

1
latex Symbolic link
View File

@@ -0,0 +1 @@
render_math/

View File

@@ -1,75 +0,0 @@
Latex Plugin For Pelican
========================
This plugin allows you to write mathematical equations in your articles using Latex.
It uses the MathJax Latex JavaScript library to render latex that is embedded in
between `$..$` for inline math and `$$..$$` for displayed math. It also allows for
writing equations in by using `\begin{equation}`...`\end{equation}`.
Installation
------------
To enable, ensure that `latex.py` is put somewhere that is accessible.
Then use as follows by adding the following to your settings.py:
PLUGINS = ["latex"]
Be careful: Not loading the plugin is easy to do, and difficult to detect. To
make life easier, find where pelican is installed, and then copy the plugin
there. An easy way to find where pelican is installed is to verbose list the
available themes by typing `pelican-themes -l -v`.
Once the pelican folder is found, copy `latex.py` to the `plugins` folder. Then
add to settings.py like this:
PLUGINS = ["pelican.plugins.latex"]
Now all that is left to do is to embed the following to your template file
between the `<head>` parameters (for the NotMyIdea template, this file is base.html)
{% if article and article.latex %}
{{ article.latex }}
{% endif %}
{% if page and page.latex %}
{{ page.latex }}
{% endif %}
Usage
-----
Latex will be embedded in every article. If however you want latex only for
selected articles, then in settings.py, add
LATEX = 'article'
And in each article, add the metadata key `latex:`. For example, with the above
settings, creating an article that I want to render latex math, I would just
include 'Latex' as part of the metadata without any value:
Date: 1 sep 2012
Status: draft
Latex:
Latex Examples
--------------
###Inline
Latex between `$`..`$`, for example, `$`x^2`$`, will be rendered inline
with respect to the current html block.
###Displayed Math
Latex between `$$`..`$$`, for example, `$$`x^2`$$`, will be rendered centered in a
new paragraph.
###Equations
Latex between `\begin` and `\end`, for example, `begin{equation}` x^2 `\end{equation}`,
will be rendered centered in a new paragraph with a right justified equation number
at the top of the paragraph. This equation number can be referenced in the document.
To do this, use a `label` inside of the equation format and then refer to that label
using `ref`. For example: `begin{equation}` `\label{eq}` X^2 `\end{equation}`. Now
refer to that equation number by `$`\ref{eq}`$`.
Template And Article Examples
-----------------------------
To see an example of this plugin in action, look at
[this article](http://doctrina.org/How-RSA-Works-With-Examples.html). To see how
this plugin works with a template, look at
[this template](https://github.com/barrysteyn/pelican_theme-personal_blog).

View File

@@ -1 +0,0 @@
from .latex import *

View File

@@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
"""
Latex Plugin For Pelican
========================
This plugin allows you to write mathematical equations in your articles using Latex.
It uses the MathJax Latex JavaScript library to render latex that is embedded in
between `$..$` for inline math and `$$..$$` for displayed math. It also allows for
writing equations in by using `\begin{equation}`...`\end{equation}`.
"""
from pelican import signals
latexScript = """
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type= "text/javascript">
MathJax.Hub.Config({
config: ["MMLorHTML.js"],
jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML"],
TeX: { extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"], equationNumbers: { autoNumber: "AMS" } },
extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js"],
tex2jax: {
inlineMath: [ [\'$\',\'$\'] ],
displayMath: [ [\'$$\',\'$$\'] ],
processEscapes: true },
"HTML-CSS": {
styles: { ".MathJax .mo, .MathJax .mi": {color: "black ! important"}}
}
});
</script>
"""
def addLatex(gen, metadata):
"""
The registered handler for the latex plugin. It will add
the latex script to the article metadata
"""
if 'LATEX' in gen.settings.keys() and gen.settings['LATEX'] == 'article':
if 'latex' in metadata.keys():
metadata['latex'] = latexScript
else:
metadata['latex'] = latexScript
def register():
"""
Plugin registration
"""
signals.article_generate_context.connect(addLatex)
signals.pages_generate_context.connect(addLatex)

130
liquid_tags/Readme.md Normal file
View File

@@ -0,0 +1,130 @@
# Liquid-style Tags
*Author: Jake Vanderplas <jakevdp@cs.washington.edu>*
This plugin allows liquid-style tags to be inserted into markdown within
Pelican documents. Liquid uses tags bounded by ``{% ... %}``, and is used
to extend markdown in other blogging platforms such as octopress.
This set of extensions does not actually interface with liquid, but allows
users to define their own liquid-style tags which will be inserted into
the markdown preprocessor stream. There are several built-in tags, which
can be added as follows.
First, in your pelicanconf.py file, add the plugins you want to use:
PLUGIN_PATH = '/path/to/pelican-plugins'
PLUGINS = ['liquid_tags.img', 'liquid_tags.video',
'liquid_tags.youtube', 'liquid_tags.vimeo',
'liquid_tags.include_code', 'liquid_tags.notebook']
There are several options available
## Image Tag
To insert a sized and labeled image in your document, enable the
``liquid_tags.img`` plugin and use the following:
{% img [class name(s)] path/to/image [width [height]] [title text | "title text" ["alt text"]] %}
## Youtube Tag
To insert youtube video into a post, enable the
``liquid_tags.youtube`` plugin, and add to your document:
{% youtube youtube_id [width] [height] %}
The width and height are in pixels, and can be optionally specified. If they
are not, then the dimensions will be 640 (wide) by 390 (tall).
## Vimeo Tag
To insert a Vimeo video into a post, enable the
``liquid_tags.vimeo`` plugin, and add to your document:
{% vimeo vimeo_id [width] [height] %}
The width and height are in pixels, and can be optionally specified. If they
are not, then the dimensions will be 640 (wide) by 390 (tall).
## Video Tag
To insert flash/HTML5-friendly video into a post, enable the
``liquid_tags.video`` plugin, and add to your document:
{% video /url/to/video.mp4 [width] [height] [/path/to/poster.png] %}
The width and height are in pixels, and can be optionally specified. If they
are not, then the original video size will be used. The poster is an image
which is used as a preview of the video.
To use a video from file, make sure it's in a static directory and put in
the appropriate url.
## Include Code
To include code from a file in your document with a link to the original
file, enable the ``liquid_tags.include_code`` plugin, and add to your
document:
{% include_code /path/to/code.py [lang:python] [lines:X-Y] [:hidefilename:] [title] %}
All arguments are optional but their order must be kept. `:hidefilename:` is
only allowed if a title is also given.
{% include_code /path/to/code.py lines:1-10 :hidefilename: Test Example %}
This example will show the first 10 lines of the file while hiding the actual
filename.
The script must be in the ``code`` subdirectory of your content folder:
this default location can be changed by specifying
CODE_DIR = 'code'
within your configuration file. Additionally, in order for the resulting
hyperlink to work, this directory must be listed under the STATIC_PATHS
setting, e.g.:
STATIC_PATHS = ['images', 'code']
## IPython notebooks
To insert an ipython notebook into your post, enable the
``liquid_tags.notebook`` plugin and add to your document:
{% notebook filename.ipynb %}
The file should be specified relative to the ``notebooks`` subdirectory of the
content directory. Optionally, this subdirectory can be specified in the
config file:
NOTEBOOK_DIR = 'notebooks'
Because the conversion and rendering of notebooks is rather involved, there
are a few extra steps required for this plugin:
- First, you will need to install IPython >= 1.0 [1]_
- After typing "make html" when using the notebook tag, a file called
``_nb_header.html`` will be produced in the main directory. The content
of the file should be included in the header of the theme. An easy way
to accomplish this is to add the following lines within the header template
of the theme you use:
{% if EXTRA_HEADER %}
{{ EXTRA_HEADER }}
{% endif %}
and in your configuration file, include the line:
EXTRA_HEADER = open('_nb_header.html').read().decode('utf-8')
this will insert the proper css formatting into your document.
### Collapsible Code in IPython Notebooks
The plugin also enables collapsible code input boxes. For this to work
you first need to copy the file ``pelicanhtml_1.tpl`` (for IPython
1.x) ``pelicanhtml_2.tpl`` (for IPython 2.x) to the top level of your
Pelican blog. Notebook input cells containing the comment line ``#
<!-- collapse=True -->`` will be collapsed when the html page is
loaded and can be expanded by clicking on them. Cells containing the
comment line ``# <!-- collapse=False -->`` will be open on load but
can be collapsed by clicking on their header. Cells without collapse
comments are rendered as standard code input cells.
[1] http://ipython.org/

1
liquid_tags/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .liquid_tags import *

65
liquid_tags/img.py Normal file
View File

@@ -0,0 +1,65 @@
"""
Image Tag
---------
This implements a Liquid-style image tag for Pelican,
based on the octopress image tag [1]_
Syntax
------
{% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | "title text" ["alt text"]] %}
Examples
--------
{% img /images/ninja.png Ninja Attack! %}
{% img left half http://site.com/images/ninja.png Ninja Attack! %}
{% img left half http://site.com/images/ninja.png 150 150 "Ninja Attack!" "Ninja in attack posture" %}
Output
------
<img src="/images/ninja.png">
<img class="left half" src="http://site.com/images/ninja.png" title="Ninja Attack!" alt="Ninja Attack!">
<img class="left half" src="http://site.com/images/ninja.png" width="150" height="150" title="Ninja Attack!" alt="Ninja in attack posture">
[1] https://github.com/imathis/octopress/blob/master/plugins/image_tag.rb
"""
import re
from .mdx_liquid_tags import LiquidTags
SYNTAX = '{% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | "title text" ["alt text"]] %}'
# Regular expression to match the entire syntax
ReImg = re.compile("""(?P<class>\S.*\s+)?(?P<src>(?:https?:\/\/|\/|\S+\/)\S+)(?:\s+(?P<width>\d+))?(?:\s+(?P<height>\d+))?(?P<title>\s+.+)?""")
# Regular expression to split the title and alt text
ReTitleAlt = re.compile("""(?:"|')(?P<title>[^"']+)?(?:"|')\s+(?:"|')(?P<alt>[^"']+)?(?:"|')""")
@LiquidTags.register('img')
def img(preprocessor, tag, markup):
attrs = None
# Parse the markup string
match = ReImg.search(markup)
if match:
attrs = dict([(key, val.strip())
for (key, val) in match.groupdict().iteritems() if val])
else:
raise ValueError('Error processing input. '
'Expected syntax: {0}'.format(SYNTAX))
# Check if alt text is present -- if so, split it from title
if 'title' in attrs:
match = ReTitleAlt.search(attrs['title'])
if match:
attrs.update(match.groupdict())
if not attrs.get('alt'):
attrs['alt'] = attrs['title']
# Return the formatted text
return "<img {0}>".format(' '.join('{0}="{1}"'.format(key, val)
for (key, val) in attrs.iteritems()))
#----------------------------------------------------------------------
# This import allows image tag to be a Pelican plugin
from liquid_tags import register

128
liquid_tags/include_code.py Normal file
View File

@@ -0,0 +1,128 @@
"""
Include Code Tag
----------------
This implements a Liquid-style video tag for Pelican,
based on the octopress video tag [1]_
Syntax
------
{% include_code path/to/code [lang:python] [Title text] %}
The "path to code" is specified relative to the ``code`` subdirectory of
the content directory Optionally, this subdirectory can be specified in the
config file:
CODE_DIR = 'code'
Example
-------
{% include_code myscript.py %}
This will import myscript.py from content/code/myscript.py
and output the contents in a syntax highlighted code block inside a figure,
with a figcaption listing the file name and download link.
The file link will be valid only if the 'code' directory is listed
in the STATIC_PATHS setting, e.g.:
STATIC_PATHS = ['images', 'code']
[1] https://github.com/imathis/octopress/blob/master/plugins/include_code.rb
"""
import re
import os
from .mdx_liquid_tags import LiquidTags
SYNTAX = "{% include_code /path/to/code.py [lang:python] [lines:X-Y] [:hidefilename:] [title] %}"
FORMAT = re.compile(r"""
^(?:\s+)? # Allow whitespace at beginning
(?P<src>\S+) # Find the path
(?:\s+)? # Whitespace
(?:(?:lang:)(?P<lang>\S+))? # Optional language
(?:\s+)? # Whitespace
(?:(?:lines:)(?P<lines>\d+-\d+))? # Optional lines
(?:\s+)? # Whitespace
(?P<hidefilename>:hidefilename:)? # Hidefilename flag
(?:\s+)? # Whitespace
(?P<title>.+)?$ # Optional title
""", re.VERBOSE)
@LiquidTags.register('include_code')
def include_code(preprocessor, tag, markup):
title = None
lang = None
src = None
match = FORMAT.search(markup)
if match:
argdict = match.groupdict()
title = argdict['title'] or ""
lang = argdict['lang']
lines = argdict['lines']
hide_filename = bool(argdict['hidefilename'])
if lines:
first_line, last_line = map(int, lines.split("-"))
src = argdict['src']
if not src:
raise ValueError("Error processing input, "
"expected syntax: {0}".format(SYNTAX))
settings = preprocessor.configs.config['settings']
code_dir = settings.get('CODE_DIR', 'code')
code_path = os.path.join('content', code_dir, src)
if not os.path.exists(code_path):
raise ValueError("File {0} could not be found".format(code_path))
with open(code_path) as fh:
if lines:
code = fh.readlines()[first_line - 1: last_line]
code[-1] = code[-1].rstrip()
code = "".join(code)
else:
code = fh.read()
if not title and hide_filename:
raise ValueError("Either title must be specified or filename must "
"be available")
if not hide_filename:
title += " %s" % os.path.basename(src)
if lines:
title += " [Lines %s]" % lines
title = title.strip()
url = '/{0}/{1}'.format(code_dir, src)
url = re.sub('/+', '/', url)
open_tag = ("<figure class='code'>\n<figcaption><span>{title}</span> "
"<a href='{url}'>download</a></figcaption>".format(title=title,
url=url))
close_tag = "</figure>"
# store HTML tags in the stash. This prevents them from being
# modified by markdown.
open_tag = preprocessor.configs.htmlStash.store(open_tag, safe=True)
close_tag = preprocessor.configs.htmlStash.store(close_tag, safe=True)
if lang:
lang_include = ':::' + lang + '\n '
else:
lang_include = ''
source = (open_tag
+ '\n\n '
+ lang_include
+ '\n '.join(code.split('\n')) + '\n\n'
+ close_tag + '\n')
return source
#----------------------------------------------------------------------
# This import allows image tag to be a Pelican plugin
from liquid_tags import register

View File

@@ -0,0 +1,16 @@
from pelican import signals
from .mdx_liquid_tags import LiquidTags
def addLiquidTags(gen):
if not gen.settings.get('MD_EXTENSIONS'):
from pelican.settings import DEFAULT_CONFIG
gen.settings['MD_EXTENSIONS'] = DEFAULT_CONFIG['MD_EXTENSIONS']
if LiquidTags not in gen.settings['MD_EXTENSIONS']:
configs = dict(settings=gen.settings)
gen.settings['MD_EXTENSIONS'].append(LiquidTags(configs))
def register():
signals.initialized.connect(addLiquidTags)

27
liquid_tags/literal.py Normal file
View File

@@ -0,0 +1,27 @@
"""
Literal Tag
-----------
This implements a tag that allows explicitly showing commands which would
otherwise be interpreted as a liquid tag.
For example, the line
{% literal video arg1 arg2 %}
would result in the following line:
{% video arg1 arg2 %}
This is useful when the resulting line would be interpreted as another
liquid-style tag.
"""
from .mdx_liquid_tags import LiquidTags
@LiquidTags.register('literal')
def literal(preprocessor, tag, markup):
return '{%% %s %%}' % markup
#----------------------------------------------------------------------
# This import allows image tag to be a Pelican plugin
from liquid_tags import register

View File

@@ -0,0 +1,77 @@
"""
Markdown Extension for Liquid-style Tags
----------------------------------------
A markdown extension to allow user-defined tags of the form::
{% tag arg1 arg2 ... argn %}
Where "tag" is associated with some user-defined extension.
These result in a preprocess step within markdown that produces
either markdown or html.
"""
import warnings
import markdown
import itertools
import re
import os
from functools import wraps
# Define some regular expressions
LIQUID_TAG = re.compile(r'\{%.*?%\}')
EXTRACT_TAG = re.compile(r'(?:\s*)(\S+)(?:\s*)')
class _LiquidTagsPreprocessor(markdown.preprocessors.Preprocessor):
_tags = {}
def __init__(self, configs):
self.configs = configs
def run(self, lines):
page = '\n'.join(lines)
liquid_tags = LIQUID_TAG.findall(page)
for i, markup in enumerate(liquid_tags):
# remove {% %}
markup = markup[2:-2]
tag = EXTRACT_TAG.match(markup).groups()[0]
markup = EXTRACT_TAG.sub('', markup, 1)
if tag in self._tags:
liquid_tags[i] = self._tags[tag](self, tag, markup.strip())
# add an empty string to liquid_tags so that chaining works
liquid_tags.append('')
# reconstruct string
page = ''.join(itertools.chain(*zip(LIQUID_TAG.split(page),
liquid_tags)))
# resplit the lines
return page.split("\n")
class LiquidTags(markdown.Extension):
"""Wrapper for MDPreprocessor"""
@classmethod
def register(cls, tag):
"""Decorator to register a new include tag"""
def dec(func):
if tag in _LiquidTagsPreprocessor._tags:
warnings.warn("Enhanced Markdown: overriding tag '%s'" % tag)
_LiquidTagsPreprocessor._tags[tag] = func
return func
return dec
def extendMarkdown(self, md, md_globals):
self.htmlStash = md.htmlStash
md.registerExtension(self)
# for the include_code preprocessor, we need to re-run the
# fenced code block preprocessor after substituting the code.
# Because the fenced code processor is run before, {% %} tags
# within equations will not be parsed as an include.
md.preprocessors.add('mdincludes',
_LiquidTagsPreprocessor(self), ">html_block")
def makeExtension(configs=None):
"""Wrapper for a MarkDown extension"""
return LiquidTags(configs=configs)

319
liquid_tags/notebook.py Normal file
View File

@@ -0,0 +1,319 @@
"""
Notebook Tag
------------
This is a liquid-style tag to include a static html rendering of an IPython
notebook in a blog post.
Syntax
------
{% notebook filename.ipynb [ cells[start:end] ]%}
The file should be specified relative to the ``notebooks`` subdirectory of the
content directory. Optionally, this subdirectory can be specified in the
config file:
NOTEBOOK_DIR = 'notebooks'
The cells[start:end] statement is optional, and can be used to specify which
block of cells from the notebook to include.
Requirements
------------
- The plugin requires IPython version 1.0 or above. It no longer supports the
standalone nbconvert package, which has been deprecated.
Details
-------
Because the notebook relies on some rather extensive custom CSS, the use of
this plugin requires additional CSS to be inserted into the blog theme.
After typing "make html" when using the notebook tag, a file called
``_nb_header.html`` will be produced in the main directory. The content
of the file should be included in the header of the theme. An easy way
to accomplish this is to add the following lines within the header template
of the theme you use:
{% if EXTRA_HEADER %}
{{ EXTRA_HEADER }}
{% endif %}
and in your ``pelicanconf.py`` file, include the line:
EXTRA_HEADER = open('_nb_header.html').read().decode('utf-8')
this will insert the appropriate CSS. All efforts have been made to ensure
that this CSS will not override formats within the blog theme, but there may
still be some conflicts.
"""
import re
import os
from .mdx_liquid_tags import LiquidTags
from distutils.version import LooseVersion
import IPython
if not LooseVersion(IPython.__version__) >= '1.0':
raise ValueError("IPython version 1.0+ required for notebook tag")
from IPython import nbconvert
try:
from IPython.nbconvert.filters.highlight import _pygments_highlight
except ImportError:
# IPython < 2.0
from IPython.nbconvert.filters.highlight import _pygment_highlight as _pygments_highlight
from pygments.formatters import HtmlFormatter
from IPython.nbconvert.exporters import HTMLExporter
from IPython.config import Config
from IPython.nbformat import current as nbformat
try:
from IPython.nbconvert.preprocessors import Preprocessor
except ImportError:
# IPython < 2.0
from IPython.nbconvert.transformers import Transformer as Preprocessor
from IPython.utils.traitlets import Integer
from copy import deepcopy
from jinja2 import DictLoader
#----------------------------------------------------------------------
# Some code that will be added to the header:
# Some of the following javascript/css include is adapted from
# IPython/nbconvert/templates/fullhtml.tpl, while some are custom tags
# specifically designed to make the results look good within the
# pelican-octopress theme.
JS_INCLUDE = r"""
<style type="text/css">
/* Overrides of notebook CSS for static HTML export */
div.entry-content {
overflow: visible;
padding: 8px;
}
.input_area {
padding: 0.2em;
}
a.heading-anchor {
white-space: normal;
}
.rendered_html
code {
font-size: .8em;
}
pre.ipynb {
color: black;
background: #f7f7f7;
border: none;
box-shadow: none;
margin-bottom: 0;
padding: 0;
margin: 0px;
font-size: 13px;
}
/* remove the prompt div from text cells */
div.text_cell .prompt {
display: none;
}
/* remove horizontal padding from text cells, */
/* so it aligns with outer body text */
div.text_cell_render {
padding: 0.5em 0em;
}
img.anim_icon{padding:0; border:0; vertical-align:middle; -webkit-box-shadow:none; -box-shadow:none}
div.collapseheader {
width=100%;
background-color:#d3d3d3;
padding: 2px;
cursor: pointer;
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
}
</style>
<script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" type="text/javascript"></script>
<script type="text/javascript">
init_mathjax = function() {
if (window.MathJax) {
// MathJax loaded
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
},
displayAlign: 'left', // Change this to 'center' to center equations.
"HTML-CSS": {
styles: {'.MathJax_Display': {"margin": 0}}
}
});
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
}
}
init_mathjax();
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
jQuery(document).ready(function($) {
$("div.collapseheader").click(function () {
$header = $(this).children("span").first();
$codearea = $(this).children(".input_area");
console.log($(this).children());
$codearea.slideToggle(500, function () {
$header.text(function () {
return $codearea.is(":visible") ? "Collapse Code" : "Expand Code";
});
});
});
});
</script>
"""
CSS_WRAPPER = """
<style type="text/css">
{0}
</style>
"""
#----------------------------------------------------------------------
# Create a custom preprocessor
class SliceIndex(Integer):
"""An integer trait that accepts None"""
default_value = None
def validate(self, obj, value):
if value is None:
return value
else:
return super(SliceIndex, self).validate(obj, value)
class SubCell(Preprocessor):
"""A transformer to select a slice of the cells of a notebook"""
start = SliceIndex(0, config=True,
help="first cell of notebook to be converted")
end = SliceIndex(None, config=True,
help="last cell of notebook to be converted")
def preprocess(self, nb, resources):
nbc = deepcopy(nb)
for worksheet in nbc.worksheets:
cells = worksheet.cells[:]
worksheet.cells = cells[self.start:self.end]
return nbc, resources
call = preprocess # IPython < 2.0
#----------------------------------------------------------------------
# Custom highlighter:
# instead of using class='highlight', use class='highlight-ipynb'
def custom_highlighter(source, language='ipython', metadata=None):
formatter = HtmlFormatter(cssclass='highlight-ipynb')
if not language:
language = 'ipython'
output = _pygments_highlight(source, formatter, language)
return output.replace('<pre>', '<pre class="ipynb">')
#----------------------------------------------------------------------
# Below is the pelican plugin code.
#
SYNTAX = "{% notebook /path/to/notebook.ipynb [ cells[start:end] ] %}"
FORMAT = re.compile(r"""^(\s+)?(?P<src>\S+)(\s+)?((cells\[)(?P<start>-?[0-9]*):(?P<end>-?[0-9]*)(\]))?(\s+)?$""")
@LiquidTags.register('notebook')
def notebook(preprocessor, tag, markup):
match = FORMAT.search(markup)
if match:
argdict = match.groupdict()
src = argdict['src']
start = argdict['start']
end = argdict['end']
else:
raise ValueError("Error processing input, "
"expected syntax: {0}".format(SYNTAX))
if start:
start = int(start)
else:
start = 0
if end:
end = int(end)
else:
end = None
settings = preprocessor.configs.config['settings']
nb_dir = settings.get('NOTEBOOK_DIR', 'notebooks')
nb_path = os.path.join('content', nb_dir, src)
if not os.path.exists(nb_path):
raise ValueError("File {0} could not be found".format(nb_path))
# Create the custom notebook converter
c = Config({'CSSHTMLHeaderTransformer':
{'enabled':True, 'highlight_class':'.highlight-ipynb'},
'SubCell':
{'enabled':True, 'start':start, 'end':end}})
template_file = 'basic'
if LooseVersion(IPython.__version__) >= '2.0':
if os.path.exists('pelicanhtml_2.tpl'):
template_file = 'pelicanhtml_2'
else:
if os.path.exists('pelicanhtml_1.tpl'):
template_file = 'pelicanhtml_1'
if LooseVersion(IPython.__version__) >= '2.0':
subcell_kwarg = dict(preprocessors=[SubCell])
else:
subcell_kwarg = dict(transformers=[SubCell])
exporter = HTMLExporter(config=c,
template_file=template_file,
filters={'highlight2html': custom_highlighter},
**subcell_kwarg)
# read and parse the notebook
with open(nb_path) as f:
nb_text = f.read()
nb_json = nbformat.reads_json(nb_text)
(body, resources) = exporter.from_notebook_node(nb_json)
# if we haven't already saved the header, save it here.
if not notebook.header_saved:
print ("\n ** Writing styles to _nb_header.html: "
"this should be included in the theme. **\n")
header = '\n'.join(CSS_WRAPPER.format(css_line)
for css_line in resources['inlining']['css'])
header += JS_INCLUDE
with open('_nb_header.html', 'w') as f:
f.write(header)
notebook.header_saved = True
# this will stash special characters so that they won't be transformed
# by subsequent processes.
body = preprocessor.configs.htmlStash.store(body, safe=True)
return body
notebook.header_saved = False
#----------------------------------------------------------------------
# This import allows notebook to be a Pelican plugin
from liquid_tags import register

View File

@@ -0,0 +1,44 @@
{%- extends 'html_basic.tpl' -%}
{% block stream_stdout -%}
<div class="box-flex1 output_subarea output_stream output_stdout">
<pre class="ipynb">{{output.text |ansi2html}}</pre>
</div>
{%- endblock stream_stdout %}
{% block stream_stderr -%}
<div class="box-flex1 output_subarea output_stream output_stderr">
<pre class="ipynb">{{output.text |ansi2html}}</pre>
</div>
{%- endblock stream_stderr %}
{% block pyerr -%}
<div class="box-flex1 output_subarea output_pyerr">
<pre class="ipynb">{{super()}}</pre>
</div>
{%- endblock pyerr %}
{%- block data_text %}
<pre class="ipynb">{{output.text | ansi2html}}</pre>
{%- endblock -%}
{% block input %}
{% if "# <!-- collapse=True -->" in cell.input %}
<div class="collapseheader box-flex1"><span style="font-weight: bold;">Expand Code</span>
<div class="input_area box-flex1" style="display:none">
{{ cell.input.replace("# <!-- collapse=True -->\n", "") | highlight2html(metadata=cell.metadata) }}
</div>
</div>
{% elif "# <!-- collapse=False -->" in cell.input %}
<div class="collapseheader box-flex1"><span style="font-weight: bold;">Collapse Code</span>
<div class="input_area box-flex1">
{{ cell.input.replace("# <!-- collapse=False -->\n", "") | highlight2html(metadata=cell.metadata) }}
</div>
</div>
{% else %}
<div class="input_area box-flex1">
{{ cell.input | highlight2html(metadata=cell.metadata) }}
</div>
{% endif %}
{%- endblock input %}

View File

@@ -0,0 +1,44 @@
{%- extends 'basic.tpl' -%}
{% block stream_stdout -%}
<div class="box-flex1 output_subarea output_stream output_stdout">
<pre class="ipynb">{{output.text |ansi2html}}</pre>
</div>
{%- endblock stream_stdout %}
{% block stream_stderr -%}
<div class="box-flex1 output_subarea output_stream output_stderr">
<pre class="ipynb">{{output.text |ansi2html}}</pre>
</div>
{%- endblock stream_stderr %}
{% block pyerr -%}
<div class="box-flex1 output_subarea output_pyerr">
<pre class="ipynb">{{super()}}</pre>
</div>
{%- endblock pyerr %}
{%- block data_text %}
<pre class="ipynb">{{output.text | ansi2html}}</pre>
{%- endblock -%}
{% block input %}
{% if "# <!-- collapse=True -->" in cell.input %}
<div class="collapseheader box-flex1"><span style="font-weight: bold;">Expand Code</span>
<div class="input_area box-flex1" style="display:none">
{{ cell.input.replace("# <!-- collapse=True -->\n", "") | highlight2html(metadata=cell.metadata) }}
</div>
</div>
{% elif "# <!-- collapse=False -->" in cell.input %}
<div class="collapseheader box-flex1"><span style="font-weight: bold;">Collapse Code</span>
<div class="input_area box-flex1">
{{ cell.input.replace("# <!-- collapse=False -->\n", "") | highlight2html(metadata=cell.metadata) }}
</div>
</div>
{% else %}
<div class="input_area box-flex1">
{{ cell.input | highlight2html(metadata=cell.metadata) }}
</div>
{% endif %}
{%- endblock input %}

70
liquid_tags/video.py Normal file
View File

@@ -0,0 +1,70 @@
"""
Video Tag
---------
This implements a Liquid-style video tag for Pelican,
based on the octopress video tag [1]_
Syntax
------
{% video url/to/video [width height] [url/to/poster] %}
Example
-------
{% video http://site.com/video.mp4 720 480 http://site.com/poster-frame.jpg %}
Output
------
<video width='720' height='480' preload='none' controls poster='http://site.com/poster-frame.jpg'>
<source src='http://site.com/video.mp4' type='video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"'/>
</video>
[1] https://github.com/imathis/octopress/blob/master/plugins/video_tag.rb
"""
import os
import re
from .mdx_liquid_tags import LiquidTags
SYNTAX = "{% video url/to/video [url/to/video] [url/to/video] [width height] [url/to/poster] %}"
VIDEO = re.compile(r'(/\S+|https?:\S+)(\s+(/\S+|https?:\S+))?(\s+(/\S+|https?:\S+))?(\s+(\d+)\s(\d+))?(\s+(/\S+|https?:\S+))?')
VID_TYPEDICT = {'.mp4':"type='video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"'",
'.ogv':"type='video/ogg; codecs=theora, vorbis'",
'.webm':"type='video/webm; codecs=vp8, vorbis'"}
@LiquidTags.register('video')
def video(preprocessor, tag, markup):
videos = []
width = None
height = None
poster = None
match = VIDEO.search(markup)
if match:
groups = match.groups()
videos = [g for g in groups[0:6:2] if g]
width = groups[6]
height = groups[7]
poster = groups[9]
if any(videos):
video_out = "<video width='{width}' height='{height}' preload='none' controls poster='{poster}'>".format(width=width, height=height, poster=poster)
for vid in videos:
base, ext = os.path.splitext(vid)
if ext not in VID_TYPEDICT:
raise ValueError("Unrecognized video extension: "
"{0}".format(ext))
video_out += ("<source src='{0}' "
"{1}>".format(vid, VID_TYPEDICT[ext]))
video_out += "</video>"
else:
raise ValueError("Error processing input, "
"expected syntax: {0}".format(SYNTAX))
return video_out
#----------------------------------------------------------------------
# This import allows image tag to be a Pelican plugin
from liquid_tags import register

54
liquid_tags/vimeo.py Normal file
View File

@@ -0,0 +1,54 @@
"""
Vimeo Tag
---------
This implements a Liquid-style vimeo tag for Pelican,
based on the youtube tag which is in turn based on
the jekyll / octopress youtube tag [1]_
Syntax
------
{% vimeo id [width height] %}
Example
-------
{% vimeo 10739054 640 480 %}
Output
------
<div style="width:640px; height:480px;"><iframe src="//player.vimeo.com/video/10739054?title=0&amp;byline=0&amp;portrait=0" width="640" height="480" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>
[1] https://gist.github.com/jamieowen/2063748
"""
import re
from .mdx_liquid_tags import LiquidTags
SYNTAX = "{% vimeo id [width height] %}"
VIMEO = re.compile(r'(\w+)(\s+(\d+)\s(\d+))?')
@LiquidTags.register('vimeo')
def vimeo(preprocessor, tag, markup):
width = 640
height = 390
vimeo_id = None
match = VIMEO.search(markup)
if match:
groups = match.groups()
vimeo_id = groups[0]
width = groups[2] or width
height = groups[3] or height
if vimeo_id:
vimeo_out = '<div style="width:{width}px; height:{height}px;"><iframe src="//player.vimeo.com/video/{vimeo_id}?title=0&amp;byline=0&amp;portrait=0" width="{width}" height="{height}" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe></div>'.format(width=width, height=height, vimeo_id=vimeo_id)
else:
raise ValueError("Error processing input, "
"expected syntax: {0}".format(SYNTAX))
return vimeo_out
#----------------------------------------------------------------------
# This import allows vimeo tag to be a Pelican plugin
from liquid_tags import register

53
liquid_tags/youtube.py Normal file
View File

@@ -0,0 +1,53 @@
"""
Youtube Tag
---------
This implements a Liquid-style youtube tag for Pelican,
based on the jekyll / octopress youtube tag [1]_
Syntax
------
{% youtube id [width height] %}
Example
-------
{% youtube dQw4w9WgXcQ 640 480 %}
Output
------
<iframe width="640" height="480" src="http://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
[1] https://gist.github.com/jamieowen/2063748
"""
import os
import re
from .mdx_liquid_tags import LiquidTags
SYNTAX = "{% youtube id [width height] %}"
YOUTUBE = re.compile(r'(\w+)(\s+(\d+)\s(\d+))?')
@LiquidTags.register('youtube')
def youtube(preprocessor, tag, markup):
width = 640
height = 390
youtube_id = None
match = YOUTUBE.search(markup)
if match:
groups = match.groups()
youtube_id = groups[0]
width = groups[2] or width
height = groups[3] or height
if youtube_id:
youtube_out = "<iframe width='{width}' height='{height}' src='http://www.youtube.com/embed/{youtube_id}' frameborder='0' webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>".format(width=width, height=height, youtube_id=youtube_id)
else:
raise ValueError("Error processing input, "
"expected syntax: {0}".format(SYNTAX))
return youtube_out
#----------------------------------------------------------------------
# This import allows image tag to be a Pelican plugin
from liquid_tags import register

View File

@@ -2,7 +2,10 @@ Neighbor Articles Plugin for Pelican
====================================
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
variables to the article's context
variables to the article's context.
Also adds ``next_article_in_category`` and ``prev_article_in_category``.
Usage
-----
@@ -24,4 +27,71 @@ Usage
</a>
</li>
{% endif %}
</ul>
</ul>
<ul>
{% if article.prev_article_in_category %}
<li>
<a href="{{ SITEURL }}/{{ article.prev_article_in_category.url}}">
{{ article.prev_article_in_category.title }}
</a>
</li>
{% endif %}
{% if article.next_article %}
<li>
<a href="{{ SITEURL }}/{{ article.next_article_in_category.url}}">
{{ article.next_article_in_category.title }}
</a>
</li>
{% endif %}
</ul>
Usage with the Subcategory plugin
---------------------------------
If you want to get the neigbors within a subcategory it's a little different.
Since an article can belong to more than one subcategory, subcategories are
stored in a list. If you have an article with subcategories like
``Category/Foo/Bar``
it will belong to both subcategory Foo, and Foo/Bar. Subcategory neighbors are
added to an article as ``next_article_in_subcategory#`` and
``prev_article_in_subcategory#`` where ``#`` is the level of subcategory. So using
the example from above, subcategory1 will be Foo, and subcategory2 Foo/Bar.
Therefor the usage with subcategories is:
.. code-block:: html+jinja
<ul>
{% if article.prev_article_subcategory1 %}
<li>
<a href="{{ SITEURL }}/{{ article.prev_article_in_subcategory1.url}}">
{{ article.prev_article_in_subcategory1.title }}
</a>
</li>
{% endif %}
{% if article.next_article %}
<li>
<a href="{{ SITEURL }}/{{ article.next_article_subcategory1.url}}">
{{ article.next_article_subcategory1.title }}
</a>
</li>
{% endif %}
</ul>
<ul>
{% if article.prev_article_in_subcategory2 %}
<li>
<a href="{{ SITEURL }}/{{ article.prev_article_in_subcategory2.url}}">
{{ article.prev_article_in_subcategory2.title }}
</a>
</li>
{% endif %}
{% if article.next_article %}
<li>
<a href="{{ SITEURL }}/{{ article.next_article_in_subcategory2.url}}">
{{ article.next_article_in_subcategory2.title }}
</a>
</li>
{% endif %}
</ul>

View File

@@ -6,7 +6,6 @@ Neighbor Articles Plugin for Pelican
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
variables to the article's context
"""
from pelican import signals
def iter3(seq):
@@ -18,10 +17,42 @@ def iter3(seq):
nxt, cur = cur, prv
yield nxt, cur, None
def get_translation(article, prefered_language):
if not article:
return None
for translation in article.translations:
if translation.lang == prefered_language:
return translation
return article
def set_neighbors(articles, next_name, prev_name):
for nxt, cur, prv in iter3(articles):
exec("cur.{} = nxt".format(next_name))
exec("cur.{} = prv".format(prev_name))
for translation in cur.translations:
exec(
"translation.{} = get_translation(nxt, translation.lang)".format(
next_name))
exec(
"translation.{} = get_translation(prv, translation.lang)".format(
prev_name))
def neighbors(generator):
for nxt, cur, prv in iter3(generator.articles):
cur.next_article = nxt
cur.prev_article = prv
set_neighbors(generator.articles, 'next_article', 'prev_article')
for category, articles in generator.categories:
articles.sort(key=(lambda x: x.date), reverse=(True))
set_neighbors(
articles, 'next_article_in_category', 'prev_article_in_category')
if hasattr(generator, 'subcategories'):
for subcategory, articles in generator.subcategories:
articles.sort(key=(lambda x: x.date), reverse=(True))
index = subcategory.name.count('/')
next_name = 'next_article_in_subcategory{}'.format(index)
prev_name = 'prev_article_in_subcategory{}'.format(index)
set_neighbors(articles, next_name, prev_name)
def register():
signals.article_generator_finalized.connect(neighbors)

View File

@@ -0,0 +1,31 @@
# Pelican comment system
The pelican comment system allows you to add static comments to your articles.
The comments are stored in Markdown files. Each comment in it own file.
#### Features
- 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
See it in action here: [blog.scheirle.de](http://blog.scheirle.de/posts/2014/March/29/static-comments-via-email/)
Author | Website | Github
-------------------|---------------------------|------------------------------
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
To create identicons the Python Image Library is needed. Therefore you either need PIL **or** Pillow (recommended).
##### Install Pillow
easy_install Pillow
If you don't use avatars or identicons this plugin works fine without PIL/Pillow. You will however get a warning that identicons are deactivated (as expected).

View File

@@ -0,0 +1 @@
from .pelican_comment_system import *

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
"""
from __future__ import unicode_literals
import logging
import os
import hashlib
logger = logging.getLogger(__name__)
_log = "pelican_comment_system: avatars: "
try:
from . identicon import identicon
_identiconImported = True
except ImportError as e:
logger.warning(_log + "identicon deactivated: " + str(e))
_identiconImported = False
# Global Variables
_identicon_save_path = None
_identicon_output_path = None
_identicon_data = None
_identicon_size = None
_initialized = False
_authors = None
_missingAvatars = []
def _ready():
if not _initialized:
logger.warning(_log + "Module not initialized. use init")
if not _identicon_data:
logger.debug(_log + "No identicon data set")
return _identiconImported and _initialized and _identicon_data
def init(pelican_output_path, identicon_output_path, identicon_data, identicon_size, authors):
global _identicon_save_path
global _identicon_output_path
global _identicon_data
global _identicon_size
global _initialized
global _authors
_identicon_save_path = os.path.join(pelican_output_path, identicon_output_path)
_identicon_output_path = identicon_output_path
_identicon_data = identicon_data
_identicon_size = identicon_size
_authors = authors
_initialized = True
def _createIdenticonOutputFolder():
if not _ready():
return
if not os.path.exists(_identicon_save_path):
os.makedirs(_identicon_save_path)
def getAvatarPath(comment_id, metadata):
if not _ready():
return ''
md5 = hashlib.md5()
author = tuple()
for data in _identicon_data:
if data in metadata:
string = str(metadata[data])
md5.update(string.encode('utf-8'))
author += tuple([string])
else:
logger.warning(_log + data + " is missing in comment: " + comment_id)
if author in _authors:
return _authors[author]
global _missingAvatars
code = md5.hexdigest()
if not code in _missingAvatars:
_missingAvatars.append(code)
return os.path.join(_identicon_output_path, '%s.png' % code)
def generateAndSaveMissingAvatars():
_createIdenticonOutputFolder()
for code in _missingAvatars:
avatar_path = '%s.png' % code
avatar = identicon.render_identicon(int(code, 16), _identicon_size)
avatar_save_path = os.path.join(_identicon_save_path, avatar_path)
avatar.save(avatar_save_path, 'PNG')

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
"""
from __future__ import unicode_literals
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

@@ -0,0 +1,35 @@
# Avatars and Identicons
To activate the avatars and [identicons](https://en.wikipedia.org/wiki/Identicon) you have to set `PELICAN_COMMENT_SYSTEM_IDENTICON_DATA`.
##### Example
```python
PELICAN_COMMENT_SYSTEM_IDENTICON_DATA = ('author')
```
Now every comment with the same author tag will be treated as if written from the same person. And therefore have the same avatar/identicon. Of cause you can modify this tuple so other metadata are checked.
## Specific Avatars
To set a specific avatar for a author you have to add them to the `PELICAN_COMMENT_SYSTEM_AUTHORS` dictionary.
The `key` of the dictionary has to be a tuple of the form of `PELICAN_COMMENT_SYSTEM_IDENTICON_DATA`, so in our case only the author's name.
The `value` of the dictionary is the path to the specific avatar.
##### Example
```python
PELICAN_COMMENT_SYSTEM_AUTHORS = {
('John'): "images/authors/john.png",
('Tom'): "images/authors/tom.png",
}
```
## 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 }}"
alt="Avatar"
height="{{ PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE }}"
width="{{ PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE }}">
```
Of cause the `height` and `width` are optional, but they make sure that everything has the same size (in particular specific avatars).

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

@@ -0,0 +1,83 @@
# Comment Form (aka: never gather Metadata)
Add a form, which allows your visitors to easily write comments.
But more importantly, on submit the form generates a mailto-link.
The resulting email contains a valid markdown block. Now you only have to copy this block in a new file. And therefore there is no need to gather the metadata (like date, author, replyto) yourself.
#### Reply button
Add this in the "comment for loop" in your article theme, so your visitors can reply to a comment.
```html
<button onclick="reply('{{comment.id | urlencode}}');">Reply</button>
```
#### Form
A basic form so your visitors can write comments.
```html
<form role="form" id="commentForm" action="#">
<input name="Name" type="text" id="commentForm_inputName" placeholder="Enter your name or synonym">
<textarea name="Text" id="commentForm_inputText" rows="10" style="resize:vertical;" placeholder="Your comment"></textarea>
<button type="submit" id="commentForm_button">Post via email</button>
<input name="replyto" type="hidden" id="commentForm_replyto">
</form>
```
You may want to add a button to reset the `replyto` field.
#### Javascript
To generate the mailto-Link and set the `replyto` field there is some javascript required.
```javascript
<script type="text/javascript">
function reply(id)
{
id = decodeURIComponent(id);
$('#commentForm_replyto').val(id);
}
$(document).ready(function() {
function generateMailToLink()
{
var user = 'your_user_name'; //user@domain = your email address
var domain = 'your_email_provider';
var subject = 'Comment for \'{{ article.slug }}\'' ;
var d = new Date();
var body = ''
+ 'Hey,\nI posted a new comment on ' + document.URL + '\n\nGreetings ' + $("#commentForm_inputName").val() + '\n\n\n'
+ 'Raw comment data:\n'
+ '----------------------------------------\n'
+ 'date: ' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + '\n'
+ 'author: ' + $("#commentForm_inputName").val() + '\n';
var replyto = $('#commentForm_replyto').val();
if (replyto.length != 0)
{
body += 'replyto: ' + replyto + '\n'
}
body += '\n'
+ $("#commentForm_inputText").val() + '\n'
+ '----------------------------------------\n';
var link = 'mailto:' + user + '@' + domain + '?subject='
+ encodeURIComponent(subject)
+ "&body="
+ encodeURIComponent(body);
return link;
}
$('#commentForm').on("submit",
function( event )
{
event.preventDefault();
$(location).attr('href', generateMailToLink());
}
);
});
</script>
```
(jQuery is required for this script)
Don't forget to set the Variables `user` and `domain`.

View File

@@ -0,0 +1,106 @@
# Installation
Activate the plugin by adding it to your `pelicanconf.py`
PLUGIN_PATH = '/path/to/pelican-plugins'
PLUGINS = ['pelican_comment_system']
PELICAN_COMMENT_SYSTEM = True
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` | 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`.
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 (**with** extension).
##### Example folder structure
.
└── comments
└── foo-bar
│ ├── 1.md
│ └── 0.md
└── some-other-slug
├── random-Name.md
├── 1.md
└── 0.md
## Comment file
### Meta information
Tag | Required | Description
--------------|-----------|----------------
`date` | yes | Date when the comment was posted
`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.
##### Example of a comment file
date: 2014-3-21 15:02
author: Author of the comment
website: http://authors.website.com
replyto: 7
anothermetatag: some random tag
Content of the comment.
## Theme
In the `article.html` theme file are now two more variables available.
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
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
`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.comments %}
{% for comment in article.comments recursive %}
{% if loop.depth0 == 0 %}
{% set marginLeft = 0 %}
{% else %}
{% set marginLeft = 50 %}
{% 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>{{ 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) }}
{% endif %}
</article>
{% endfor %}
{% else %}
<p>There are no comments yet.<p>
{% endif %}
```

View File

@@ -0,0 +1,11 @@
identicon.py is Licesensed under FreeBSD License.
(http://www.freebsd.org/copyright/freebsd-license.html)
Copyright 1994-2009 Shin Adachi. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,17 @@
identicon.py: identicon python implementation.
==============================================
:Author:Shin Adachi <shn@glucose.jp>
## usage
### commandline
python identicon.py [code]
### python
import identicon
identicon.render_identicon(code, size)
Return a PIL Image class instance which have generated identicon image.
`size` specifies patch size. Generated image size is 3 * `size`.

View File

@@ -0,0 +1,256 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
identicon.py
identicon python implementation.
by Shin Adachi <shn@glucose.jp>
= usage =
== commandline ==
>>> python identicon.py [code]
== python ==
>>> import identicon
>>> identicon.render_identicon(code, size)
Return a PIL Image class instance which have generated identicon image.
```size``` specifies `patch size`. Generated image size is 3 * ```size```.
"""
# g
# PIL Modules
from PIL import Image, ImageDraw, ImagePath, ImageColor
__all__ = ['render_identicon', 'IdenticonRendererBase']
class Matrix2D(list):
"""Matrix for Patch rotation"""
def __init__(self, initial=[0.] * 9):
assert isinstance(initial, list) and len(initial) == 9
list.__init__(self, initial)
def clear(self):
for i in xrange(9):
self[i] = 0.
def set_identity(self):
self.clear()
for i in xrange(3):
self[i] = 1.
def __str__(self):
return '[%s]' % ', '.join('%3.2f' % v for v in self)
def __mul__(self, other):
r = []
if isinstance(other, Matrix2D):
for y in range(3):
for x in range(3):
v = 0.0
for i in range(3):
v += (self[i * 3 + x] * other[y * 3 + i])
r.append(v)
else:
raise NotImplementedError
return Matrix2D(r)
def for_PIL(self):
return self[0:6]
@classmethod
def translate(kls, x, y):
return kls([1.0, 0.0, float(x),
0.0, 1.0, float(y),
0.0, 0.0, 1.0])
@classmethod
def scale(kls, x, y):
return kls([float(x), 0.0, 0.0,
0.0, float(y), 0.0,
0.0, 0.0, 1.0])
"""
# need `import math`
@classmethod
def rotate(kls, theta, pivot=None):
c = math.cos(theta)
s = math.sin(theta)
matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
if not pivot:
return matR
return kls.translate(-pivot[0], -pivot[1]) * matR *
kls.translate(*pivot)
"""
@classmethod
def rotateSquare(kls, theta, pivot=None):
theta = theta % 4
c = [1., 0., -1., 0.][theta]
s = [0., 1., 0., -1.][theta]
matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
if not pivot:
return matR
return kls.translate(-pivot[0], -pivot[1]) * matR * \
kls.translate(*pivot)
class IdenticonRendererBase(object):
PATH_SET = []
def __init__(self, code):
"""
@param code code for icon
"""
if not isinstance(code, int):
code = int(code)
self.code = code
def render(self, size):
"""
render identicon to PIL.Image
@param size identicon patchsize. (image size is 3 * [size])
@return PIL.Image
"""
# decode the code
middle, corner, side, foreColor, backColor = self.decode(self.code)
size = int(size)
# make image
image = Image.new("RGB", (size * 3, size * 3))
draw = ImageDraw.Draw(image)
# fill background
draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0)
kwds = {
'draw': draw,
'size': size,
'foreColor': foreColor,
'backColor': backColor}
# middle patch
self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds)
# side patch
kwds['type'] = side[0]
for i in range(4):
pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i]
self.drawPatch(pos, side[2] + 1 + i, side[1], **kwds)
# corner patch
kwds['type'] = corner[0]
for i in range(4):
pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i]
self.drawPatch(pos, corner[2] + 1 + i, corner[1], **kwds)
return image
def drawPatch(self, pos, turn, invert, type, draw, size, foreColor,
backColor):
"""
@param size patch size
"""
path = self.PATH_SET[type]
if not path:
# blank patch
invert = not invert
path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)]
patch = ImagePath.Path(path)
if invert:
foreColor, backColor = backColor, foreColor
mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\
Matrix2D.translate(*pos) *\
Matrix2D.scale(size, size)
patch.transform(mat.for_PIL())
draw.rectangle((pos[0] * size, pos[1] * size, (pos[0] + 1) * size,
(pos[1] + 1) * size), fill=backColor)
draw.polygon(patch, fill=foreColor, outline=foreColor)
### virtual functions
def decode(self, code):
raise NotImplementedError
class DonRenderer(IdenticonRendererBase):
"""
Don Park's implementation of identicon
see : http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released
"""
PATH_SET = [
[(0, 0), (4, 0), (4, 4), (0, 4)], # 0
[(0, 0), (4, 0), (0, 4)],
[(2, 0), (4, 4), (0, 4)],
[(0, 0), (2, 0), (2, 4), (0, 4)],
[(2, 0), (4, 2), (2, 4), (0, 2)], # 4
[(0, 0), (4, 2), (4, 4), (2, 4)],
[(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)],
[(0, 0), (4, 2), (2, 4)],
[(1, 1), (3, 1), (3, 3), (1, 3)], # 8
[(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)],
[(0, 0), (2, 0), (2, 2), (0, 2)],
[(0, 2), (4, 2), (2, 4)],
[(2, 2), (4, 4), (0, 4)],
[(2, 0), (2, 2), (0, 2)],
[(0, 0), (2, 0), (0, 2)],
[]] # 15
MIDDLE_PATCH_SET = [0, 4, 8, 15]
# modify path set
for idx in range(len(PATH_SET)):
if PATH_SET[idx]:
p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx])
p = list(p)
PATH_SET[idx] = p + p[:1]
def decode(self, code):
# decode the code
middleType = self.MIDDLE_PATCH_SET[code & 0x03]
middleInvert= (code >> 2) & 0x01
cornerType = (code >> 3) & 0x0F
cornerInvert= (code >> 7) & 0x01
cornerTurn = (code >> 8) & 0x03
sideType = (code >> 10) & 0x0F
sideInvert = (code >> 14) & 0x01
sideTurn = (code >> 15) & 0x03
blue = (code >> 16) & 0x1F
green = (code >> 21) & 0x1F
red = (code >> 27) & 0x1F
foreColor = (red << 3, green << 3, blue << 3)
return (middleType, middleInvert, 0),\
(cornerType, cornerInvert, cornerTurn),\
(sideType, sideInvert, sideTurn),\
foreColor, ImageColor.getrgb('white')
def render_identicon(code, size, renderer=None):
if not renderer:
renderer = DonRenderer
return renderer(code).render(size)
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
print('usage: python identicon.py [CODE]....')
raise SystemExit
for code in sys.argv[1:]:
if code.startswith('0x') or code.startswith('0X'):
code = int(code[2:], 16)
elif code.startswith('0'):
code = int(code[1:], 8)
else:
code = int(code)
icon = render_identicon(code, 24)
icon.save('%08x.png' % code, 'PNG')

View File

@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
"""
Pelican Comment System
======================
A Pelican plugin, which allows you to add comments to your articles.
Author: Bernhard Scheirle
"""
from __future__ import unicode_literals
import logging
import os
import copy
logger = logging.getLogger(__name__)
from itertools import chain
from pelican import signals
from pelican.readers import MarkdownReader
from pelican.writers import Writer
from . comment import Comment
from . import avatars
def pelican_initialized(pelican):
from pelican.settings import DEFAULT_CONFIG
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM', False)
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_DIR' 'comments')
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH' 'images/identicon')
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')
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH', 'images/identicon')
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):
avatars.init(
article_generator.settings['OUTPUT_PATH'],
article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH'],
article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_DATA'],
article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE']/3,
article_generator.settings['PELICAN_COMMENT_SYSTEM_AUTHORS'],
)
def add_static_comments(gen, content):
if gen.settings['PELICAN_COMMENT_SYSTEM'] != True:
return
content.comments_count = 0
content.comments = []
#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'], content.slug)
if not os.path.isdir(folder):
logger.debug("No comments found for: " + content.slug)
writer.write_feed( [], context, path)
return
reader = MarkdownReader(gen.settings)
comments = []
replies = []
for file in os.listdir(folder):
name, extension = os.path.splitext(file)
if extension[1:].lower() in reader.file_extensions:
com_content, meta = reader.read(os.path.join(folder, file))
avatar_path = avatars.getAvatarPath(name, meta)
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):
if comment.id == reply.metadata['replyto']:
comment.addReply(reply)
count = 0
for comment in comments:
comment.sortReplies()
count += comment.countReplies()
comments = sorted(comments)
content.comments_count = len(comments) + count
content.comments = comments
def writeIdenticonsToDisk(gen, writer):
avatars.generateAndSaveMissingAvatars()
def register():
signals.initialized.connect(pelican_initialized)
signals.article_generator_init.connect(initialize)
signals.article_generator_write_article.connect(add_static_comments)
signals.article_writer_finalized.connect(writeIdenticonsToDisk)

1
pelican_vimeo Submodule

Submodule pelican_vimeo added at d18f1ddfe9

1
pelican_youtube Submodule

Submodule pelican_youtube added at 045c43dd4d

26
read_more_link/Readme.md Normal file
View File

@@ -0,0 +1,26 @@
Read More Link
===
**Author**: Vuong Nguyen (http://vuongnguyen.com)
This plugin inserts an inline "read more" or "continue" link into the last html element of the object summary.
For more information, please visit: http://vuongnguyen.com/creating-inline-read-more-link-python-pelican-lxml.html
Requirements
---
lxml - for parsing html elements
Settings
---
# This settings indicates that you want to create summary at a certain length
SUMMARY_MAX_LENGTH = 50
# This indicates what goes inside the read more link
READ_MORE_LINK = None (ex: '<span>continue</span>')
# This is the format of the read more link
READ_MORE_LINK_FORMAT = '<a class="read-more" href="/{url}">{text}</a>'

View File

@@ -0,0 +1 @@
from .read_more_link import *

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""
Read More Link
===========================
This plugin inserts an inline "read more" or "continue" link into the last html element of the object summary.
For more information, please visit: http://vuongnguyen.com/creating-inline-read-more-link-python-pelican-lxml.html
"""
from pelican import signals, contents
from pelican.utils import truncate_html_words
try:
from lxml.html import fragment_fromstring, fragments_fromstring, tostring
from lxml.etree import ParserError
except ImportError:
raise Exception("Unable to find lxml. To use READ_MORE_LINK, you need lxml")
def insert_into_last_element(html, element):
"""
function to insert an html element into another html fragment
example:
html = '<p>paragraph1</p><p>paragraph2...</p>'
element = '<a href="/read-more/">read more</a>'
---> '<p>paragraph1</p><p>paragraph2...<a href="/read-more/">read more</a></p>'
"""
try:
item = fragment_fromstring(element)
except ParserError, TypeError:
item = fragment_fromstring('<span></span>')
try:
doc = fragments_fromstring(html)
doc[-1].append(item)
return ''.join(tostring(e) for e in doc)
except ParserError, TypeError:
return ''
def insert_read_more_link(instance):
"""
Insert an inline "read more" link into the last element of the summary
:param instance:
:return:
"""
# only deals with Article type
if type(instance) != contents.Article: return
SUMMARY_MAX_LENGTH = instance.settings.get('SUMMARY_MAX_LENGTH')
READ_MORE_LINK = instance.settings.get('READ_MORE_LINK', None)
READ_MORE_LINK_FORMAT = instance.settings.get('READ_MORE_LINK_FORMAT',
'<a class="read-more" href="/{url}">{text}</a>')
if not (SUMMARY_MAX_LENGTH and READ_MORE_LINK and READ_MORE_LINK_FORMAT): return
if hasattr(instance, '_summary') and instance._summary:
summary = instance._summary
else:
summary = truncate_html_words(instance.content, SUMMARY_MAX_LENGTH)
if summary<instance.content:
read_more_link = READ_MORE_LINK_FORMAT.format(url=instance.url, text=READ_MORE_LINK)
instance._summary = insert_into_last_element(summary, read_more_link)
def register():
signals.content_object_init.connect(insert_read_more_link)

View File

@@ -0,0 +1 @@
lxml>=3.2.1

View File

@@ -17,3 +17,11 @@ For example::
{% endfor %}
</ul>
{% endif %}
Your related posts should share a common tag. You can also use ``related_posts:`` in your post's meta data.
The 'related_posts:' meta data works together with your existing slugs:
related_posts: slug1,slug2,slug3...slugN
N represents the RELATED_POSTS_MAX

View File

@@ -13,23 +13,38 @@ def add_related_posts(generator):
# get the max number of entries from settings
# or fall back to default (5)
numentries = generator.settings.get('RELATED_POSTS_MAX', 5)
for article in generator.articles:
# no tag, no relation
if not hasattr(article, 'tags'):
continue
# set priority in case of forced related posts
if hasattr(article,'related_posts'):
# split slugs
related_posts = article.related_posts.split(',')
posts = []
# get related articles
for slug in related_posts:
i = 0
for a in generator.articles:
if i >= numentries: # break in case there are max related psots
break
if a.slug == slug:
posts.append(a)
i += 1
# score = number of common tags
scores = Counter()
for tag in article.tags:
scores += Counter(generator.tags[tag])
article.related_posts = posts
else:
# no tag, no relation
if not hasattr(article, 'tags'):
continue
# remove itself
scores.pop(article)
# score = number of common tags
scores = Counter()
for tag in article.tags:
scores += Counter(generator.tags[tag])
article.related_posts = [other for other, count
in scores.most_common(numentries)]
# remove itself
scores.pop(article)
article.related_posts = [other for other, count
in scores.most_common(numentries)]
def register():
signals.article_generator_finalized.connect(add_related_posts)

173
render_math/Readme.md Normal file
View File

@@ -0,0 +1,173 @@
Math Render Plugin For Pelican
==============================
This plugin gives pelican the ability to render mathematics. It accomplishes
this by using the [MathJax](http://www.mathjax.org/) javascript engine. Both
[LaTex](http://en.wikipedia.org/wiki/LaTeX) and [MathML](http://en.wikipedia.org/wiki/MathML)
can be rendered within the content.
The plugin also ensures that pelican and recognized math "play" nicely together, by
ensuring [Typogrify](https://github.com/mintchaos/typogrify) does not alter math content
and summaries that get cut off are repaired.
Recognized math in the context of this plugin is either LaTex or MathML as described below.
### LaTex
Anything between `$`...`$` (inline math) and `$$`..`$$` (displayed math) will be recognized as
LaTex. In addition, anything the `\begin` and `\end` LaTex macros will also be
recognized as LaTex. For example, `\begin{equation}`...`\end{equation}` would be used to
render math equations with numbering.
Within recognized LaTex as described above, any supported LaTex macro can be used.
### MathML
Anything between `<math>` and `</math>` tags will be recognized as MathML
Installation
------------
To enable, ensure that `render_math` plugin is accessible.
Then add the following to settings.py:
PLUGINS = ["render_math"]
Your site is now capable of rendering math math using the mathjax JavaScript
engine. No alterations to the template is needed, just use and enjoy!
### Typogrify
In the past, using [Typgogrify](https://github.com/mintchaos/typogrify) would alter the math contents resulting
in math that could not be rendered by MathJax. The only option was to ensure
that Typogrify was disabled in the settings.
The problem has been recitified in this plugin, but it requires [Typogrify version 2.04](https://pypi.python.org/pypi/typogrify)
(or higher). If this version of Typogrify is not present, the plugin will inform that an incorrect
version of Typogrify is not present and disable Typogrify for the entire site
Usage
-----
### Backward Compatibility
This plugin is backward compatible in the sense that it
will render previous setups correctly. This is because those
settings and metadata information is ignored by this version. Therefore
you can remove them to neaten up your site
### Templates
No alteration is needed to a template for this plugin to work. Just install
the plugin and start writing your Math.
If on the other hand, you are template designer and want total control
over the MathJax JavaScript, you can set the `auto_insert` setting to
`False` which will cause no MathJax JavaScript to be added to the content.
If you choose this option, you should really know what you are doing. Therefore
only do this if you are designing your template. There is no real advantage to
to letting template logic handle the insertion of the MathJax JavaScript other
than it being slightly neater.
By setting `auto_insert` to `False`, metadata with `key` value of `mathjax`
will be present in all pages and articles where MathJax should be present.
The template designer can detect this and then use the `MATHJAXSCRIPT` setting
which will contain the user specified MathJax script to insert into the content.
For example, this code could be used:
```
{% if not MATH['auto_insert'] %}
{% if page and page.mathjax or article and article.mathjax %}
{{ MATHJAXSCRIPT }}
{% endif %}
{% endif %}
```
### Settings
Certain MathJax rendering options can be set. These options
are in a dictionary variable called `MATH` in the pelican
settings file.
The dictionary can be set with the following keys:
* `auto_insert`: controls whether plugin should automatically insert
MathJax JavaScript in content that has Math. It is only recommended
to set this to False if you are a template designer and you want
extra control over where the MathJax JavaScript is renderd. **Default Value**:
True
* `wrap_latex`: controls the tags that LaTex math is wrapped with inside the resulting
html. For example, setting `wrap_latex` to `mathjax` would wrap all LaTex math inside
`<mathjax>...</mathjax>` tags. If typogrify is set to True, then math needs
to be wrapped in tags and `wrap_latex` will therefore default to `mathjax` if not
set. `wrap_latex` cannot be set to `'math'` because this tag is reserved for
mathml notation. **Default Value**: None unless Typogrify is enabled in which case,
it defaults to `mathjax`
* `align`: controls how displayed math will be aligned. Can be set to either
`left`, `right` or `center`. **Default Value**: `center`.
* `indent`: if `align` not set to `center`, then this controls the indent
level. **Default Value**: `0em`.
* `show_menu`: a boolean value that controls whether the mathjax contextual
menu is shown. **Default Value**: True
* `process_escapes`: a boolean value that controls whether mathjax processes escape
sequences. **Default Value**: True
* `latex_preview`: controls the preview message users are seen while mathjax is
rendering LaTex. If set to `Tex`, then the TeX code is used as the preview
(which will be visible until it is processed by MathJax). **Default Value**: `Tex`
* `color`: controls the color of the mathjax rendered font. **Default Value**: `black`
* `ssl`: specifies if ssl should be used to load MathJax engine. Can be set to one
of three things
* `auto`: **Default Value** will automatically determine what protodol to use
based on current protocol of the site.
* `force`: will force ssl to be used.
* `off`: will ensure that ssl is not used
For example, in settings.py, the following would make math render in blue and
displaymath align to the left:
MATH = {'color':'blue','align':left}
LaTex Examples
--------------
###Inline
LaTex between `$`..`$`, for example, `$`x^2`$`, will be rendered inline
with respect to the current html block.
###Displayed Math
LaTex between `$$`..`$$`, for example, `$$`x^2`$$`, will be rendered centered in a
new paragraph.
###Equations
LaTex between `\begin` and `\end`, for example, `begin{equation}` x^2 `\end{equation}`,
will be rendered centered in a new paragraph with a right justified equation number
at the top of the paragraph. This equation number can be referenced in the document.
To do this, use a `label` inside of the equation format and then refer to that label
using `ref`. For example: `begin{equation}` `\label{eq}` X^2 `\end{equation}`. Now
refer to that equation number by `$`\ref{eq}`$`.
MathML Examples
---------------
The following will render the Quadratic formula:
```
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
<mrow>
<mi>x</mi>
<mo>=</mo>
<mfrac>
<mrow>
<mo>&#x2212;</mo>
<mi>b</mi>
<mo>&#xB1;</mo>
<msqrt>
<mrow>
<msup>
<mi>b</mi>
<mn>2</mn>
</msup>
<mo>&#x2212;</mo>
<mn>4</mn>
<mi>a</mi>
<mi>c</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mi>a</mi>
</mrow>
</mfrac>
</mrow>
</math>
```

1
render_math/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .math import *

379
render_math/math.py Normal file
View File

@@ -0,0 +1,379 @@
# -*- coding: utf-8 -*-
"""
Math Render Plugin for Pelican
==============================
This plugin allows your site to render Math. It supports both LaTeX and MathML
using the MathJax JavaScript engine.
Typogrify Compatibility
-----------------------
This plugin now plays nicely with Typogrify, but it requires
Typogrify version 2.04 or above.
User Settings
-------------
Users are also able to pass a dictionary of settings in the settings file which
will control how the MathJax library renders things. This could be very useful
for template builders that want to adjust the look and feel of the math.
See README for more details.
"""
import os
import re
from pelican import signals
from pelican import contents
# Global Variables
_TYPOGRIFY = None # if Typogrify is enabled, this is set to the typogrify.filter function
_WRAP_LATEX = None # the tag to wrap LaTeX math in (needed to play nicely with Typogrify or for template designers)
_MATH_REGEX = re.compile(r'(\$\$|\$|\\begin\{(.+?)\}|<(math)(?:\s.*?)?>).*?(\1|\\end\{\2\}|</\3>)', re.DOTALL | re.IGNORECASE) # used to detect math
_MATH_SUMMARY_REGEX = None # used to match math in summary
_MATH_INCOMPLETE_TAG_REGEX = None # used to match math that has been cut off in summary
_MATHJAX_SETTINGS = {} # settings that can be specified by the user, used to control mathjax script settings
with open (os.path.dirname(os.path.realpath(__file__))+'/mathjax_script.txt', 'r') as mathjax_script: # Read the mathjax javascript from file
_MATHJAX_SCRIPT=mathjax_script.read()
# Python standard library for binary search, namely bisect is cool but I need
# specific business logic to evaluate my search predicate, so I am using my
# own version
def binary_search(match_tuple, ignore_within):
"""Determines if t is within tupleList. Using the fact that tupleList is
ordered, binary search can be performed which is O(logn)
"""
ignore = False
if ignore_within == []:
return False
lo = 0
hi = len(ignore_within)-1
# Find first value in array where predicate is False
# predicate function: tupleList[mid][0] < t[index]
while lo < hi:
mid = lo + (hi-lo+1)//2
if ignore_within[mid][0] < match_tuple[0]:
lo = mid
else:
hi = mid-1
if lo >= 0 and lo <= len(ignore_within)-1:
ignore = (ignore_within[lo][0] <= match_tuple[0] and ignore_within[lo][1] >= match_tuple[1])
return ignore
def ignore_content(content):
"""Creates a list of match span tuples for which content should be ignored
e.g. <pre> and <code> tags
"""
ignore_within = []
# used to detect all <pre> and <code> tags. NOTE: Alter this regex should
# additional tags need to be ignored
ignore_regex = re.compile(r'<(pre|code)(?:\s.*?)?>.*?</(\1)>', re.DOTALL | re.IGNORECASE)
for match in ignore_regex.finditer(content):
ignore_within.append(match.span())
return ignore_within
def wrap_math(content, ignore_within):
"""Wraps math in user specified tags.
This is needed for Typogrify to play nicely with math but it can also be
styled by template providers
"""
wrap_math.found_math = False
def math_tag_wrap(match):
"""function for use in re.sub"""
# determine if the tags are within <pre> and <code> blocks
ignore = binary_search(match.span(1), ignore_within) or binary_search(match.span(4), ignore_within)
if ignore or match.group(3) == 'math':
if match.group(3) == 'math':
# Will detect mml, but not wrap anything around it
wrap_math.found_math = True
return match.group(0)
else:
wrap_math.found_math = True
return '<%s>%s</%s>' % (_WRAP_LATEX, match.group(0), _WRAP_LATEX)
return (_MATH_REGEX.sub(math_tag_wrap, content), wrap_math.found_math)
def process_summary(instance, ignore_within):
"""Summaries need special care. If Latex is cut off, it must be restored.
In addition, the mathjax script must be included if necessary thereby
making it independent to the template
"""
process_summary.altered_summary = False
insert_mathjax = False
end_tag = '</%s>' % _WRAP_LATEX if _WRAP_LATEX is not None else ''
# use content's _get_summary method to obtain summary
summary = instance._get_summary()
# Determine if there is any math in the summary which are not within the
# ignore_within tags
math_item = None
for math_item in _MATH_SUMMARY_REGEX.finditer(summary):
ignore = binary_search(math_item.span(2), ignore_within)
if '...' not in math_item.group(5):
ignore = ignore or binary_search(math_item.span(5), ignore_within)
else:
ignore = ignore or binary_search(math_item.span(6), ignore_within)
if ignore:
math_item = None # In <code> or <pre> tags, so ignore
else:
insert_mathjax = True
# Repair the math if it was cut off math_item will be the final math
# code matched that is not within <pre> or <code> tags
if math_item and '...' in math_item.group(5):
if math_item.group(3) is not None:
end = r'\end{%s}' % math_item.group(3)
elif math_item.group(4) is not None:
end = r'</math>'
elif math_item.group(2) is not None:
end = math_item.group(2)
search_regex = r'%s(%s.*?%s)' % (re.escape(instance._content[0:math_item.start(1)]), re.escape(math_item.group(1)), re.escape(end))
math_match = re.search(search_regex, instance._content, re.DOTALL | re.IGNORECASE)
if math_match:
new_summary = summary.replace(math_item.group(0), math_match.group(1)+'%s ...' % end_tag)
if new_summary != summary:
if _MATHJAX_SETTINGS['auto_insert']:
return new_summary+_MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
else:
instance.mathjax = True
return new_summary
def incomplete_end_latex_tag(match):
"""function for use in re.sub"""
if binary_search(match.span(3), ignore_within):
return match.group(0)
process_summary.altered_summary = True
return match.group(1) + match.group(4)
# check for partial math tags at end. These must be removed
summary = _MATH_INCOMPLETE_TAG_REGEX.sub(incomplete_end_latex_tag, summary)
if process_summary.altered_summary or insert_mathjax:
if insert_mathjax:
if _MATHJAX_SETTINGS['auto_insert']:
summary+= _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
else:
instance.mathjax = True
return summary
return None # Making it explicit that summary was not altered
def process_settings(settings):
"""Sets user specified MathJax settings (see README for more details)"""
global _MATHJAX_SETTINGS
# NOTE TO FUTURE DEVELOPERS: Look at the README and what is happening in
# this function if any additional changes to the mathjax settings need to
# be incorporated. Also, please inline comment what the variables
# will be used for
# Default settings
_MATHJAX_SETTINGS['align'] = 'center' # controls alignment of of displayed equations (values can be: left, right, center)
_MATHJAX_SETTINGS['indent'] = '0em' # if above is not set to 'center', then this setting acts as an indent
_MATHJAX_SETTINGS['show_menu'] = 'true' # controls whether to attach mathjax contextual menu
_MATHJAX_SETTINGS['process_escapes'] = 'true' # controls whether escapes are processed
_MATHJAX_SETTINGS['latex_preview'] = 'TeX' # controls what user sees while waiting for LaTex to render
_MATHJAX_SETTINGS['color'] = 'black' # controls color math is rendered in
# Source for MathJax: default (below) is to automatically determine what protocol to use
_MATHJAX_SETTINGS['source'] = """'https:' == document.location.protocol
? 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
: 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"""
# This next setting controls whether the mathjax script should be automatically
# inserted into the content. The mathjax script will not be inserted into
# the content if no math is detected. For summaries that are present in the
# index listings, mathjax script will also be automatically inserted.
# Setting this value to false means the template must be altered if this
# plugin is to work, and so it is only recommended for the template
# designer who wants maximum control.
_MATHJAX_SETTINGS['auto_insert'] = True # controls whether mathjax script is automatically inserted into the content
if not isinstance(settings, dict):
return
# The following mathjax settings can be set via the settings dictionary
# Iterate over dictionary in a way that is compatible with both version 2
# and 3 of python
for key, value in ((key, settings[key]) for key in settings):
if key == 'auto_insert' and isinstance(value, bool):
_MATHJAX_SETTINGS[key] = value
if key == 'align' and isinstance(value, str):
if value == 'left' or value == 'right' or value == 'center':
_MATHJAX_SETTINGS[key] = value
else:
_MATHJAX_SETTINGS[key] = 'center'
if key == 'indent':
_MATHJAX_SETTINGS[key] = value
if key == 'show_menu' and isinstance(value, bool):
_MATHJAX_SETTINGS[key] = 'true' if value else 'false'
if key == 'process_escapes' and isinstance(value, bool):
_MATHJAX_SETTINGS[key] = 'true' if value else 'false'
if key == 'latex_preview' and isinstance(value, str):
_MATHJAX_SETTINGS[key] = value
if key == 'color' and isinstance(value, str):
_MATHJAX_SETTINGS[key] = value
if key == 'ssl' and isinstance(value, str):
if value == 'off':
_MATHJAX_SETTINGS['source'] = "'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"
if value == 'force':
_MATHJAX_SETTINGS['source'] = "'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"
def process_content(instance):
"""Processes content, with logic to ensure that Typogrify does not clash
with math.
In addition, mathjax script is inserted at the end of the content thereby
making it independent of the template
"""
if not instance._content:
return
ignore_within = ignore_content(instance._content)
if _WRAP_LATEX:
instance._content, math = wrap_math(instance._content, ignore_within)
else:
math = True if _MATH_REGEX.search(instance._content) else False
# The user initially set Typogrify to be True, but since it would clash
# with math, we set it to False. This means that the default reader will
# not call Typogrify, so it is called here, where we are able to control
# logic for it ignore math if necessary
if _TYPOGRIFY:
# Tell Typogrify to ignore the tags that math has been wrapped in
# also, Typogrify must always ignore mml (math) tags
ignore_tags = [_WRAP_LATEX,'math'] if _WRAP_LATEX else ['math']
# Exact copy of the logic as found in the default reader
instance._content = _TYPOGRIFY(instance._content, ignore_tags)
instance.metadata['title'] = _TYPOGRIFY(instance.metadata['title'], ignore_tags)
if math:
if _MATHJAX_SETTINGS['auto_insert']:
# Mathjax script added to content automatically. Now it
# does not need to be explicitly added to the template
instance._content += _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
else:
# Place the burden on ensuring mathjax script is available to
# browser on the template designer (see README for more details)
instance.mathjax = True
# The summary needs special care because math math cannot just be cut
# off
summary = process_summary(instance, ignore_within)
if summary is not None:
instance._summary = summary
def pelican_init(pelicanobj):
"""Intialializes certain global variables and sets typogogrify setting to
False should it be set to True.
"""
global _TYPOGRIFY
global _WRAP_LATEX
global _MATH_SUMMARY_REGEX
global _MATH_INCOMPLETE_TAG_REGEX
try:
settings = pelicanobj.settings['MATH']
except:
settings = None
process_settings(settings)
# Allows MathJax script to be accessed from template should it be needed
pelicanobj.settings['MATHJAXSCRIPT'] = _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
# If Typogrify set to True, then we need to handle it manually so it does
# not conflict with LaTeX
try:
if pelicanobj.settings['TYPOGRIFY'] is True:
pelicanobj.settings['TYPOGRIFY'] = False
try:
from typogrify.filters import typogrify
# Determine if this is the correct version of Typogrify to use
import inspect
typogrify_args = inspect.getargspec(typogrify).args
if len(typogrify_args) < 2 or 'ignore_tags' not in typogrify_args:
raise TypeError('Incorrect version of Typogrify')
# At this point, we are happy to use Typogrify, meaning
# it is installed and it is a recent enough version
# that can be used to ignore all math
_TYPOGRIFY = typogrify
_WRAP_LATEX = 'mathjax' # default to wrap mathjax content inside of
except ImportError:
print("\nTypogrify is not installed, so it is being ignored.\nIf you want to use it, please install via: pip install typogrify\n")
except TypeError:
print("\nA more recent version of Typogrify is needed for the render_math module.\nPlease upgrade Typogrify to the latest version (anything above version 2.04 is okay).\nTypogrify will be turned off due to this reason.\n")
except KeyError:
pass
# Set _WRAP_LATEX to the settings tag if defined. The idea behind this is
# to give template designers control over how math would be rendered
try:
if pelicanobj.settings['MATH']['wrap_latex']:
_WRAP_LATEX = pelicanobj.settings['MATH']['wrap_latex']
except (KeyError, TypeError):
pass
# regular expressions that depend on _WRAP_LATEX are set here
tag_start= r'<%s>' % _WRAP_LATEX if not _WRAP_LATEX is None else ''
tag_end = r'</%s>' % _WRAP_LATEX if not _WRAP_LATEX is None else ''
math_summary_regex = r'((\$\$|\$|\\begin\{(.+?)\}|<(math)(?:\s.*?)?>).+?)(\2|\\end\{\3\}|</\4>|\s?\.\.\.)(%s|</\4>)?' % tag_end
# NOTE: The logic in _get_summary will handle <math> correctly because it
# is perceived as an html tag. Therefore we are only interested in handling
# non mml (i.e. LaTex)
incomplete_end_latex_tag = r'(.*)(%s)(\\\S*?|\$)\s*?(\s?\.\.\.)(%s)?$' % (tag_start, tag_end)
_MATH_SUMMARY_REGEX = re.compile(math_summary_regex, re.DOTALL | re.IGNORECASE)
_MATH_INCOMPLETE_TAG_REGEX = re.compile(incomplete_end_latex_tag, re.DOTALL | re.IGNORECASE)
def register():
"""Plugin registration"""
signals.initialized.connect(pelican_init)
signals.content_object_init.connect(process_content)

View File

@@ -0,0 +1,28 @@
<script type= "text/javascript">
if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {{
var mathjaxscript = document.createElement('script');
mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
mathjaxscript.type = 'text/javascript';
mathjaxscript.src = {source};
mathjaxscript[(window.opera ? "innerHTML" : "text")] =
"MathJax.Hub.Config({{" +
" config: ['MMLorHTML.js']," +
" TeX: {{ extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: {{ autoNumber: 'AMS' }} }}," +
" jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
" extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
" displayAlign: '{align}'," +
" displayIndent: '{indent}'," +
" showMathMenu: {show_menu}," +
" tex2jax: {{ " +
" inlineMath: [ ['$','$'] ], " +
" displayMath: [ ['$$','$$'] ]," +
" processEscapes: {process_escapes}," +
" preview: '{latex_preview}'," +
" }}, " +
" 'HTML-CSS': {{ " +
" styles: {{ '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {{color: '{color} ! important'}} }}" +
" }} " +
"}}); ";
(document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}}
</script>

View File

@@ -0,0 +1,8 @@
# Summary
This plugin extracts a representative image (i.e, featured image) from the article's summary or article's content. The image can be access at `article.featured_image`.
The plugin also remove any images from the summary after extraction to avoid duplication.
It allows the flexibility on where and how to display the featured image of an article together with its summary in a template page. For example, the article metadata can be displayed in thumbnail format, in which there is a short summary and an image. The layout of the summary and the image can be varied for aesthetical purpose. It doesn't have to depend on article's content format.

View File

@@ -0,0 +1 @@
from .representative_image import *

View File

@@ -0,0 +1,31 @@
from pelican import signals
from pelican.contents import Content, Article
from bs4 import BeautifulSoup
def images_extraction(instance):
representativeImage = None
if type(instance) == Article:
# Process Summary:
# If summary contains images, extract one to be the representativeImage and remove images from summary
soup = BeautifulSoup(instance.summary, 'html.parser')
images = soup.find_all('img')
for i in images:
if not representativeImage:
representativeImage = i['src']
i.extract()
if len(images) > 0:
# set _summary field which is based on metadata. summary field is only based on article's content and not settable
instance._summary = unicode(soup)
# If there are no image in summary, look for it in the content body
if not representativeImage:
soup = BeautifulSoup(instance.content, 'html.parser')
imageTag = soup.find('img')
if imageTag:
representativeImage = imageTag['src']
# Set the attribute to content instance
instance.featured_image = representativeImage
def register():
signals.content_object_init.connect(images_extraction)

View File

@@ -0,0 +1,54 @@
#!/bin/sh
import unittest
from jinja2.utils import generate_lorem_ipsum
# Generate content with image
TEST_CONTENT_IMAGE_URL = 'https://testimage.com/test.jpg'
TEST_CONTENT = str(generate_lorem_ipsum(n=3, html=True)) + '<img src="' + TEST_CONTENT_IMAGE_URL + '"/>'+ str(generate_lorem_ipsum(n=2,html=True))
TEST_SUMMARY_IMAGE_URL = 'https://testimage.com/summary.jpg'
TEST_SUMMARY_WITHOUTIMAGE = str(generate_lorem_ipsum(n=1, html=True))
TEST_SUMMARY_WITHIMAGE = TEST_SUMMARY_WITHOUTIMAGE + '<img src="' + TEST_SUMMARY_IMAGE_URL + '"/>'
from pelican.contents import Article
import representative_image
class TestRepresentativeImage(unittest.TestCase):
def setUp(self):
super(TestRepresentativeImage, self).setUp()
representative_image.register()
def test_extract_image_from_content(self):
args = {
'content': TEST_CONTENT,
'metadata': {
'summary': TEST_SUMMARY_WITHOUTIMAGE,
},
}
article = Article(**args)
self.assertEqual(article.featured_image, TEST_CONTENT_IMAGE_URL)
def test_extract_image_from_summary(self):
args = {
'content': TEST_CONTENT,
'metadata': {
'summary': TEST_SUMMARY_WITHIMAGE,
},
}
article = Article(**args)
self.assertEqual(article.featured_image, TEST_SUMMARY_IMAGE_URL)
self.assertEqual(article.summary, TEST_SUMMARY_WITHOUTIMAGE)
if __name__ == '__main__':
unittest.main()

65
share_post/README.md Normal file
View File

@@ -0,0 +1,65 @@
Share Post
==========
A Pelican plugin to create share URLs of article
Copyright (c) Talha Mansoor
Author | Talha Mansoor
----------------|-----
Author Email | talha131@gmail.com
Author Homepage | http://onCrashReboot.com
Github Account | https://github.com/talha131
Why do you need it?
===================
Almost all website have share widgets to let readers share posts on social
networks. Most of these widgets are used by vendors for online tracking. These
widgets are also visual which quite often become a distraction and negatively
affect readers attention.
`share_post` creates old school URLs for some popular sites which your theme
can use. These links do not have the ability to track the users. They can also
be unobtrusive depending on how Pelican theme uses them.
Requirements
============
`share_post` requires BeautifulSoup
```bash
pip install beautifulsoup4
```
How to Use
==========
`share_post` adds a dictionary attribute to `article` which can be accessed via
`article.share_post`. Keys of the dictionary are as follows,
1. `facebook`
1. `google-plus`
1. `email`
1. `twitter`
Template Example
================
```python
{% if article.share_post and article.status != 'draft' %}
<section>
<p id="post-share-links">
Share on:
<a href="{{article.share_post['twitter']}}" target="_blank" title="Share on Twitter">Twitter</a>
<a href="{{article.share_post['facebook']}}" target="_blank" title="Share on Facebook">Facebook</a>
<a href="{{article.share_post['google-plus']}}" target="_blank" title="Share on Google Plus">Google+</a>
<a href="{{article.share_post['email']}}" target="_blank" title="Share via Email">Email</a>
</p>
</section>
{% endif %}
```

1
share_post/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .share_post import *

58
share_post/share_post.py Normal file
View File

@@ -0,0 +1,58 @@
"""
Share Post
==========
This plugin adds share URL to article. These links are textual which means no
online tracking of your readers.
"""
from bs4 import BeautifulSoup
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from pelican import signals, contents
def article_title(content):
main_title = BeautifulSoup(content.title, 'html.parser').prettify().strip()
sub_title = ''
if hasattr(content, 'subtitle'):
sub_title = BeautifulSoup(content.subtitle, 'html.parser').prettify().strip()
return quote(('%s %s' % (main_title, sub_title)).encode('utf-8'))
def article_url(content):
site_url = content.settings['SITEURL']
return quote(('%s/%s' % (site_url, content.url)).encode('utf-8'))
def article_summary(content):
return quote(content.summary.encode('utf-8'))
def share_post(content):
if isinstance(content, contents.Static):
return
title = article_title(content)
url = article_url(content)
summary = article_summary(content)
tweet = '%s %s' % (title, url)
facebook_link = 'http://www.facebook.com/sharer/sharer.php?s=100' \
'&p[url]=%s&p[images][0]=&p[title]=%s&p[summary]=%s' \
% (url, title, summary)
gplus_link = 'https://plus.google.com/share?url=%s' % url
twitter_link = 'http://twitter.com/home?status=%s' % tweet
mail_link = 'mailto:?subject=%s&body=%s' % (title, url)
share_links = {'twitter': twitter_link,
'facebook': facebook_link,
'google-plus': gplus_link,
'email': mail_link
}
content.share_post = share_links
def register():
signals.content_object_init.connect(share_post)

Some files were not shown because too many files have changed in this diff Show More