From: Vladimír Vondruš Date: Sat, 16 Dec 2017 16:31:21 +0000 (+0100) Subject: theme: ability to set global social meta tags. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=c174ed77f83d352058da73261b7b0c233861f0fd;p=blog.git theme: ability to set global social meta tags. --- diff --git a/doc/pelican/theme.rst b/doc/pelican/theme.rst index 6257a1f5..ddcc699c 100644 --- a/doc/pelican/theme.rst +++ b/doc/pelican/theme.rst @@ -34,6 +34,7 @@ Theme .. role:: rst(code) :language: rst +.. |x| unicode:: U+2715 .. nicer multiply sign The second largest offering of m.css is a full-featured theme for the `Pelican static site generator `_. The theme is @@ -223,6 +224,85 @@ documentation, populating the last column implicitly: reserved. """ +`(Social) meta tags`_ +--------------------- + +The :rst:`M_BLOG_DESCRIPTION` setting, if available, is used to populate +:html:`` on the index / archive page, which can be +then shown in search engine results. For sharing pages on Twitter, Facebook and +elsewhere, it's possible to configure site-wide `Open Graph `_ +and `Twitter Card `_ +:html:`` tags: + +- ``og:site_name`` is set to :py:`M_SOCIAL_SITE_NAME`, if available +- ``twitter:site`` / ``twitter:site:id`` is set to :py:`M_SOCIAL_TWITTER_SITE` + / :py:`M_SOCIAL_TWITTER_SITE_ID``, if available +- Global ``og:title`` / ``twitter:title`` is set to :py:`M_BLOG_NAME` on + index and archive pages and to category/author/tag name on particular + filtering pages. This is overriden by particular pages and articles. +- Global ``og:url`` is set to :py:`M_BLOG_URL` on index and archive pages and + to category/author/tag URL on particular filtering pages. Pagination is + *not* included in the URL. This is overriden by particular pages and + articles. +- Global ``og:image`` / ``twitter:image`` is set to the + :py:`M_SOCIAL_IMAGE` setting, if available. The image is expected to be + smaller and square; Pelican internal linking capabilities are *not* + supported in this setting. This can be overriden by particular pages and + articles. +- Global ``twitter:card`` is set to ``summary``. This is further affected by + metadata of particular pages and articles. +- Global ``og:description`` / ``twitter:description`` is set to + :py:`M_SOCIAL_BLOG_SUMMARY` on index and archive pages. +- Global ``og:type`` is set to ``website``. This is overriden by particular + pages and articles. + +See `(Social) meta tags for pages`_ and `(Social) meta tags for articles`_ +sections below for page- and article-specific :html:`` tags. + +.. note-danger:: + + The :html:`` tag is not supported, as it doesn't + have any effect on search engine results at all. + +Example configuration to give sane defaults to all social meta tags: + +.. code:: py + + M_BLOG_NAME = "Your Brand Blog" + M_BLOG_URL = 'http://blog.your.brand/' + M_BLOG_DESCRIPTION = "Your Brand is the brand that provides all that\'s needed." + + M_SOCIAL_TWITTER_SITE = '@your.brand' + M_SOCIAL_TWITTER_SITE_ID = 1234567890 + M_SOCIAL_IMAGE = 'http://your.brand/static/site.png' + M_SOCIAL_BLOG_SUMMARY = "This is the brand you need" + +.. block-success:: Recommended sizes for global site image + + The theme assumes that the global site image is smaller and square in order + to appear just as a small thumbnail next to a link, not as large cover + image above it --- the reasoning beind is that there's no point in annoying + the users by decorating the global site links with the exact same large + image. + + For Twitter, this is controlled explicitly by setting ``twitter:card`` + to ``summary`` instead of ``summary_large_image``, but in case of Facebook, + it's needed to rely on their autodetection. + `Their documentation `_ + says that images smaller than 600\ |x|\ 315 px are displayed as small + thumbnails. Square image of size 256\ |x|\ 256 px is known to work well. + + Note that the assumptions are different for pages and articles with + explicit cover images, see `(Social) meta tags for pages`_ below for + details. + +.. note-info:: + + You can see how links for default pages will display by pasting + URL of the `article listing page <{category}examples>`_ into either + `Facebook Debugger `_ or + `Twitter Card Validator `_. + `Pages`_ ======== @@ -386,10 +466,13 @@ above: `(Social) meta tags for pages`_ ------------------------------- -You can use :rst:`:description:` field to populate :html:``, -which can be then shown in search engine results. Other than that, the field -does not appear anywhere on the rendered page. It's recommended to add it to -:py:`FORMATTED_FIELDS` so you can make use of the +Every page has :html:`` pointing to its URL to avoid +duplicates in search engines when using GET parameters. In addition to the +global meta tags described in `(Social) meta tags`_ above, you can use the +:rst:`:description:` field to populate :html:``. Other +than that, the field does not appear anywhere on the rendered page. If such +field is not set, the description :html:`` tag is not rendered at all. +It's recommended to add it to :py:`FORMATTED_FIELDS` so you can make use of the `advanced typography features <{filename}/plugins/htmlsanity.rst#typography>`_ like smart quotes etc. in it: @@ -397,9 +480,8 @@ like smart quotes etc. in it: FORMATTED_FIELDS += ['description'] -For sharing pages on Twitter, Facebook and elsewhere, both `Open Graph `_ -and `Twitter Card `_ -:html:`` tags are supported: +The global `Open Graph`_ and `Twitter Card`_ :html:`` tags are +specialized for pages like this: - Page title is mapped to ``og:title`` / ``twitter:title`` - Page URL is mapped to ``og:url`` @@ -409,10 +491,10 @@ and `Twitter Card `_), - if present, is mapped to ``og:image`` / ``twitter:image``. The exact same - file is used without any resizing or cropping and is assumed to be in - landscape. +- The :rst:`:cover:` field (e.g. the one used on `landing pages`_), if + present, is mapped to ``og:image`` / ``twitter:image``, overriding the + global :py:`M_SOCIAL_IMAGE` setting. The exact same file is used without + any resizing or cropping and is assumed to be in landscape. - ``twitter:card`` is set to ``summary_large_image`` if :rst:`:cover:` is present and to ``summary`` otherwise - ``og:type`` is set to ``page`` @@ -430,12 +512,26 @@ social links: :cover: {filename}/static/cover.jpg :summary: This is the brand you need. -.. note-success:: +.. block-success:: Recommended sizes for cover images - You can see how page links will display by pasting - URL of the `index page <{filename}/index.rst>`_ into either - `Facebook Debugger `_ or - `Twitter Card Validator `_. + Unlike the global site image described in `(Social) meta tags`_, + page-specific cover images are assumed to be larger and in landscape to + display large on top of the link, as they should act to promote the + particular content instead of being just a decoration. + + `Facebook recommendations for the cover image `_ + say that the image should have 1.91:1 aspect ratio and be ideally at least + 1200\ |x|\ 630 px large, while `Twitter recommends `_ 2:1 aspect ratio and at + most 4096\ |x|\ 4096 px. In case of Twitter, the large image display is + controlled explicitly by having ``twitter:card`` set to ``summary_large_image``, + but for Facebook one needs to rely on their autodetection. Make sure the + image is at least 600\ |x|\ 315 px to avoid fallback to a small thumbnail. + +.. note-info:: + + You can see how page links will display by pasting URL of the + `index page <{filename}/index.rst>`_ into either `Facebook Debugger`_ or + `Twitter Card Validator`_. `Articles`_ =========== @@ -494,11 +590,16 @@ invert text color on cover, add a :rst:`:class:` field containing the `(Social) meta tags for articles`_ ---------------------------------- -Like with pages, you can use :rst:`:description:` field to populate -:html:``, which can be then shown in search engine -results. Other than that, the field doesn't appear anywhere in the rendered -article. `Open Graph`_ and `Twitter Card`_ :html:`` tags are also -supported in a similar way: +Every article has :html:`` pointing to its URL to avoid +duplicates in search engines when using GET parameters. In addition to the +global meta tags described in `(Social) meta tags`_ above, you can use the +:rst:`:description:` field to populate :html:``. Other +than that, the field doesn't appear anywhere in the rendered article. If such +field is not set, the description :html:`` tag is not rendered at all. +Again, it's recommended to add it to :py:`FORMATTED_FIELDS`. + +The global `Open Graph`_ and `Twitter Card`_ :html:`` tags are +specialized for articles like this: - Article title is mapped to ``og:title`` / ``twitter:title`` - Article URL is mapped to ``og:url`` @@ -507,15 +608,16 @@ supported in a similar way: summary, Pelican takes it from the first few sentences of the content and that may not be what you want. This is also different from the :rst:`:description:` field mentioned above. -- The :rst:`:cover:` field from `jumbo articles <#jumbo-articles>`_, if - present, is mapped to ``og:image`` / ``twitter:image``. The exact same - file is used without any resizing or cropping and is assumed to be in - landscape. +- The :rst:`:cover:` field from `jumbo articles`_, if present, is mapped to + ``og:image`` / ``twitter:image``, overriding the global :py:`M_SOCIAL_IMAGE` + setting. The exact same file is used without any resizing or cropping and + is assumed to be in landscape. See `(Social) meta tags for pages`_ above + for image size recommendations. - ``twitter:card`` is set to ``summary_large_image`` if :rst:`:cover:` is present and to ``summary`` otherwise - ``og:type`` is set to ``article`` -.. note-success:: +.. note-info:: You can see how article links will display by pasting URL of e.g. the `jumbo article`_ into either `Facebook Debugger`_ or diff --git a/pelican-theme/templates/archives.html b/pelican-theme/templates/archives.html index 08fe337a..7de533a5 100644 --- a/pelican-theme/templates/archives.html +++ b/pelican-theme/templates/archives.html @@ -11,6 +11,12 @@ {% endif %} {% endblock %} +{% block meta %} + {% if M_BLOG_DESCRIPTION %} + + {% endif %} +{% endblock %} + {% block social %} {{- super() -}} {# this has to be here otherwise the spacing is all wrong. fuck. #} @@ -19,6 +25,14 @@ {% block social_url %} {% endblock %} + {% if M_SOCIAL_BLOG_SUMMARY %} + + + {% endif %} + {% if M_SOCIAL_IMAGE %} + + + {% endif %} {% endblock %} diff --git a/pelican-theme/templates/article.html b/pelican-theme/templates/article.html index fe65e904..801aa3f0 100644 --- a/pelican-theme/templates/article.html +++ b/pelican-theme/templates/article.html @@ -26,7 +26,11 @@ - {% else %} + {% elif M_SOCIAL_IMAGE %} + + + {% endif %} + {% if not article.cover %} {% endif %} diff --git a/pelican-theme/templates/base.html b/pelican-theme/templates/base.html index 90fa8a40..146c9db7 100644 --- a/pelican-theme/templates/base.html +++ b/pelican-theme/templates/base.html @@ -20,6 +20,12 @@ {% endif %} {% block meta %} {% endblock meta %} + {% if M_SOCIAL_TWITTER_SITE %} + + {% endif %} + {% if M_SOCIAL_TWITTER_SITE_ID %} + + {% endif %} {% block social %} {% endblock social %} diff --git a/pelican-theme/templates/base_blog_section.html b/pelican-theme/templates/base_blog_section.html index fb02ccdb..f25dbf24 100644 --- a/pelican-theme/templates/base_blog_section.html +++ b/pelican-theme/templates/base_blog_section.html @@ -13,6 +13,10 @@ {{- super() -}} {% block social_title_url %} {% endblock social_title_url %} + {% if M_SOCIAL_IMAGE %} + + + {% endif %} {% endblock %} diff --git a/pelican-theme/templates/page.html b/pelican-theme/templates/page.html index cf5c6233..737119c2 100644 --- a/pelican-theme/templates/page.html +++ b/pelican-theme/templates/page.html @@ -42,7 +42,11 @@ - {% else %} + {% elif M_SOCIAL_IMAGE %} + + + {% endif %} + {% if not page.cover %} {% endif %} diff --git a/pelican-theme/test/blog_global_social_meta/article.html b/pelican-theme/test/blog_global_social_meta/article.html new file mode 100644 index 00000000..ffaad899 --- /dev/null +++ b/pelican-theme/test/blog_global_social_meta/article.html @@ -0,0 +1,60 @@ + + + + + An article | A Pelican Blog + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+ + diff --git a/pelican-theme/test/blog_global_social_meta/article.rst b/pelican-theme/test/blog_global_social_meta/article.rst new file mode 100644 index 00000000..516096aa --- /dev/null +++ b/pelican-theme/test/blog_global_social_meta/article.rst @@ -0,0 +1,7 @@ +An article +########## + +:date: 2017-12-16 +:category: A category +:tags: A tag +:author: The Author diff --git a/pelican-theme/test/blog_global_social_meta/author-the-author.html b/pelican-theme/test/blog_global_social_meta/author-the-author.html new file mode 100644 index 00000000..093066ac --- /dev/null +++ b/pelican-theme/test/blog_global_social_meta/author-the-author.html @@ -0,0 +1,64 @@ + + + + + Posts by The Author | A Pelican Blog + + + + + + + + + + + + + + + +
+
+
+
+
+
+ Showing only posts by The Author. Show all posts. +
+ +
+ +
+
+
+ + diff --git a/pelican-theme/test/blog_global_social_meta/category-a-category.html b/pelican-theme/test/blog_global_social_meta/category-a-category.html new file mode 100644 index 00000000..d63db063 --- /dev/null +++ b/pelican-theme/test/blog_global_social_meta/category-a-category.html @@ -0,0 +1,64 @@ + + + + + A category | A Pelican Blog + + + + + + + + + + + + + + + +
+
+
+
+
+
+ Showing only posts in A category. Show all posts. +
+ +
+ +
+
+
+ + diff --git a/pelican-theme/test/blog_global_social_meta/tag-a-tag.html b/pelican-theme/test/blog_global_social_meta/tag-a-tag.html new file mode 100644 index 00000000..332a5943 --- /dev/null +++ b/pelican-theme/test/blog_global_social_meta/tag-a-tag.html @@ -0,0 +1,64 @@ + + + + + Posts tagged A tag | A Pelican Blog + + + + + + + + + + + + + + + +
+
+
+
+
+
+ Showing only posts tagged A tag. Show all posts. +
+ +
+ +
+
+
+ + diff --git a/pelican-theme/test/layout_global_social_meta/archives.html b/pelican-theme/test/layout_global_social_meta/archives.html new file mode 100644 index 00000000..0b57a3a9 --- /dev/null +++ b/pelican-theme/test/layout_global_social_meta/archives.html @@ -0,0 +1,59 @@ + + + + + Your Brand Blog + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+

Congratulations!

+ The m.css theme is alive and kicking! Now, feed it some articles so it doesn't feel so empty :) +
+
+ +
+
+
+
+ + diff --git a/pelican-theme/test/layout_global_social_meta/index.html b/pelican-theme/test/layout_global_social_meta/index.html new file mode 100644 index 00000000..ec23f20f --- /dev/null +++ b/pelican-theme/test/layout_global_social_meta/index.html @@ -0,0 +1,59 @@ + + + + + Your Brand Blog + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+

Congratulations!

+ The m.css theme is alive and kicking! Now, feed it some articles so it doesn't feel so empty :) +
+
+ +
+
+
+
+ + diff --git a/pelican-theme/test/page_global_social_meta/page.html b/pelican-theme/test/page_global_social_meta/page.html new file mode 100644 index 00000000..5607fa54 --- /dev/null +++ b/pelican-theme/test/page_global_social_meta/page.html @@ -0,0 +1,41 @@ + + + + + A page | A Pelican Blog + + + + + + + + + + + + + + + + +
+
+
+
+
+
+

A page

+
+
+
+
+
+ + diff --git a/pelican-theme/test/page_global_social_meta/page.rst b/pelican-theme/test/page_global_social_meta/page.rst new file mode 100644 index 00000000..58c8b8ca --- /dev/null +++ b/pelican-theme/test/page_global_social_meta/page.rst @@ -0,0 +1,2 @@ +A page +###### diff --git a/pelican-theme/test/test_blog.py b/pelican-theme/test/test_blog.py index f1870c55..2f01cc25 100644 --- a/pelican-theme/test/test_blog.py +++ b/pelican-theme/test/test_blog.py @@ -463,3 +463,22 @@ class HtmlEscape(BlogTestCase): self.assertEqual(*self.actual_expected_contents('article.html')) self.assertEqual(*self.actual_expected_contents('article-jumbo.html')) + +class GlobalSocialMeta(BlogTestCase): + def __init__(self, *args, **kwargs): + super().__init__(__file__, 'global_social_meta', *args, **kwargs) + + def test(self): + self.run_pelican({ + 'M_BLOG_DESCRIPTION': 'This is not displayed anywhere.', + 'M_SOCIAL_TWITTER_SITE': '@czmosra', + 'M_SOCIAL_TWITTER_SITE_ID': '1537427036', + 'M_SOCIAL_IMAGE': 'http://your.brand/static/site.png', + 'M_SOCIAL_BLOG_SUMMARY': 'This is also not displayed anywhere.' + }) + + # Verify that the social meta tags are present in all pages + self.assertEqual(*self.actual_expected_contents('article.html')) + self.assertEqual(*self.actual_expected_contents('category-a-category.html')) + self.assertEqual(*self.actual_expected_contents('author-the-author.html')) + self.assertEqual(*self.actual_expected_contents('tag-a-tag.html')) diff --git a/pelican-theme/test/test_layout.py b/pelican-theme/test/test_layout.py index fdd8e48f..108f5644 100644 --- a/pelican-theme/test/test_layout.py +++ b/pelican-theme/test/test_layout.py @@ -213,3 +213,24 @@ class HtmlEscape(BaseTestCase): # Verify that everything is properly escaped everywhere self.assertEqual(*self.actual_expected_contents('index.html')) self.assertEqual(*self.actual_expected_contents('archives.html', 'index.html')) + +class GlobalSocialMeta(BaseTestCase): + def __init__(self, *args, **kwargs): + super().__init__(__file__, 'global_social_meta', *args, **kwargs) + + def test(self): + self.run_pelican({ + 'SITEURL': 'http://your.brand', + 'M_BLOG_NAME': 'Your Brand Blog', + 'M_BLOG_URL': 'http://blog.your.brand/', + 'M_BLOG_DESCRIPTION': 'Your Brand provides everything you\'ll ever need.', + 'M_SOCIAL_TWITTER_SITE': '@czmosra', + 'M_SOCIAL_TWITTER_SITE_ID': '1537427036', + 'M_SOCIAL_IMAGE': 'http://your.brand/static/site.png', + 'M_SOCIAL_BLOG_SUMMARY': 'This is The Brand.' + }) + + # Verify that the social meta tags are present. Archives should have a + # different og:url but nothing else. + self.assertEqual(*self.actual_expected_contents('index.html')) + self.assertEqual(*self.actual_expected_contents('archives.html')) diff --git a/pelican-theme/test/test_page.py b/pelican-theme/test/test_page.py index d11ba894..160ebe0c 100644 --- a/pelican-theme/test/test_page.py +++ b/pelican-theme/test/test_page.py @@ -144,3 +144,19 @@ class HtmlEscape(PageTestCase): # Verify that also the Pelican-produced content has correctly escaped # everything. self.assertEqual(*self.actual_expected_contents('content.html')) + +class GlobalSocialMeta(PageTestCase): + def __init__(self, *args, **kwargs): + super().__init__(__file__, 'global_social_meta', *args, **kwargs) + + def test(self): + self.run_pelican({ + 'M_BLOG_DESCRIPTION': 'This is not displayed anywhere.', + 'M_SOCIAL_TWITTER_SITE': '@czmosra', + 'M_SOCIAL_TWITTER_SITE_ID': '1537427036', + 'M_SOCIAL_IMAGE': 'http://your.brand/static/site.png', + 'M_SOCIAL_BLOG_SUMMARY': 'This is also not displayed anywhere.' + }) + + # Verify that the social meta tags are present + self.assertEqual(*self.actual_expected_contents('page.html'))