sphinx-doc / sphinx

The Sphinx documentation generator
https://www.sphinx-doc.org/
Other
6.55k stars 2.12k forks source link

Let user provide default values of html metatags #6089

Open TormodLandet opened 5 years ago

TormodLandet commented 5 years ago

Sphinx and docutils provide the meta directive where arbitrary HMTL metadata can be set, such as description, og:image, og:title etc. This can be used to set per-document metadata, but there is no facility (that I have been able to find) to provide default values for these fields.

Best solution: Provide a dictionary in the conf file that gives default values for the html meta data that can be overridden by the meta directive for specific documents

Alternative solution: make the metatags template variable available as a dictionary, it is a pre-rendered string. Right now I parse this string to figure out if there have been any meta overrides defined in the local document. This is of course an ugly hack. The best would be to provide this only as a dictionary, but that is probably not very backwards compatible with any existing theme ... It could be that making metatags a special dict that implements __str__ could maybe work? Anyway, providing overridable default values would be preferable.

For context: Providing proper html metadata is good for search engines, and providing OpenGraph og:* metadata lets web pages make links to the documentation that looks nice and attractive for sharing on Twitter, Facebook, LinkedIn and other pages that provide OpenGraph support.

tk0miya commented 5 years ago

About OGP, we are waiting for the next release of docutils (see #2645).

Note: As a workaround, you can add meta tags as you like with custom template.

# in _template/layout.html
{% extends "alabaster/layout.html" %}
{% block extrahead %}
    <meta property="og:title" content="ogp title">
{% endblock %}
TormodLandet commented 5 years ago

Yeah, I use meta tags directly in the template, but it is rather ugly to be able to override this from the meta directive.

First I need to provide a function in conf.pyto check if the rst file overrides the description using a meta directive:

def get_meta_tag_content(name, html_string):
    """
    Parse pre-rendered meta tags to find name->content mappings

    Given input arguments
        name='myname',
        html_string='<... name="myname" ... content="a" ...>',
    Return the string 'a'.
    If the requested name is not found, return the empty string
    """
    if not html_string or '<' not in html_string:
        return ''
    for tag in html_string.split('<')[1:]:
        # Check that we can expect to be able to parse this tag
        if 'name="' not in tag:
            print('ERROR: metatag without name found: %r' % tag)
            print('ERROR: html_string=%r' % html_string)
            continue
        if 'content="' not in tag:
            print('ERROR: metatag without content found: %r' % tag)
            print('ERROR: html_string=%r' % html_string)
            continue

        # Parse the generated HTML and check for the correct tag name
        tag_name = tag.split('name="')[1].split('"')[0]
        tag_content = tag.split('content="')[1].split('"')[0]
        if tag_name == name:
            return tag_content
    return ''

html_context = {
    'default_description': 'This is the default description',
    'get_meta_tag_content': get_meta_tag_content,
}

And then the template uses this to either show the default description, or the meta directive description:

{# Make sure the metatags variable is present, also if the meta directive has not been used #}
{% if metatags is not defined %}
{% set metatags = '' %}
{% endif %}

{# Use the meta RST directive description if defined, otherwise use the default description #}
{% set page_description = get_meta_tag_content('description', metatags) %}
{% if not page_description %}
{% set page_description = default_description %}
{% set metatags = metatags + '<meta name="description" content="%s">' % page_description %}
{% endif %}

...

<meta property="og:title" content="{{ title|striptags|e }}">
<meta property="og:description" content="{{ page_description }}">

This would be much easier if the meta directive could be configured with default values.

PS: The whole thing can be seen in context here https://bitbucket.org/ocellarisproject/ocellaris/src/master/documentation/_templates/layout.html which is rendered here: https://www.ocellaris.org/

tk0miya commented 5 years ago

As a workaround, you can use following extension. It adds og_title and og_description config variables. And they can be overridden via bibliographic fields (refs)

import html

def on_html_page_context(app, pagename, templatename, ctx, doctree):
    if doctree:
        metadata = app.env.metadata.get(pagename, {})

        title = metadata.get('og:title', app.config.og_title)
        if not title:
            title = ctx['title']

        if title:
            ctx['metatags'] += ('\n    <meta property="og:title" content="%s">' % html.escape(title))

        description = metadata.get('og:description', app.config.og_description)
        if description:
            ctx['metatags'] += ('\n    <meta property="og:description" content="%s">' % html.escape(description))

def setup(app):
    app.add_config_value('og_title', None, 'html')
    app.add_config_value('og_description', None, 'html')
    app.connect('html-page-context', on_html_page_context)

This is a scribbling code. So I can't promise you that this will be merged into Sphinx core in future. (At least, I know this will conflicts with meta directive.) But this will help you at this moment.

Thanks,