documentation/extensions/odoo_theme/__init__.py
Sam Lieber (sali) f0b25461c7 [FIX] extensions: icon_role fa icons
closes odoo/documentation#9383

X-original-commit: d71c19b7be
Signed-off-by: Samuel Lieber (sali) <sali@odoo.com>
2024-05-17 20:48:14 +00:00

132 lines
5.2 KiB
Python

from docutils import nodes
from docutils.parsers.rst import roles
from sphinx import addnodes
from sphinx.environment.adapters import toctree
from . import pygments_override, translator
def setup(app):
app.set_translator('html', translator.BootstrapTranslator)
app.connect('html-page-context', set_missing_meta)
app.add_js_file('js/utils.js') # Keep in first position
app.add_js_file('js/layout.js')
app.add_js_file('js/menu.js')
app.add_js_file('js/page_toc.js')
app.add_js_file('js/switchers.js')
roles.register_canonical_role('icon', icon_role)
return {
'parallel_read_safe': True,
'parallel_write_safe': True
}
def set_missing_meta(app, pagename, templatename, context, doctree):
if context.get('meta') is None: # Pages without title (used with `include::`) have no meta
context['meta'] = {}
class Monkey:
""" Replace patched method of an object by a new method receiving the old one in argument. """
def __init__(self, obj):
self.obj = obj
def __call__(self, fn):
name = fn.__name__
old = getattr(self.obj, name)
setattr(self.obj, name, lambda self_, *args, **kwargs: fn(old, self_, *args, **kwargs))
@Monkey(toctree.TocTree)
def resolve(old_resolve, tree, docname, *args, **kwargs):
def _update_toctree_nodes(_node) -> None:
""" Make necessary changes to Docutils' nodes of the toc.
Internal structure of toc nodes:
<ul>
<li>
<p><a/></p>
<ul>
...
</ul>
</li>
<li/>
<ul/>
"""
if isinstance(_node, nodes.reference): # The node is a reference (<a/>)
_node_docname = _get_docname(_node)
_clear_reference_if_empty_page(_node, _node_docname)
_set_docname_as_class(_node, _node_docname)
elif isinstance(_node, (addnodes.compact_paragraph, nodes.bullet_list, nodes.list_item)):
for _subnode in _node.children:
_update_toctree_nodes(_subnode)
def _get_docname(_node):
""" Return the docname of the targeted document.
docname = some_common_root/foo/bar/the_page_being_rendered
_ref = ../../contributing/documentation
_path_parts = ['..', '..', 'contributing', 'documentation']
_res = ['some_common_root', 'contributing', 'documentation']
_docname = some_common_root/contributing/documentation
:return: The docname of the document targeted by `_node`, i.e. the relative path from the
documentation source directory (the `content/` directory)
:rtype: str
"""
_ref = _node['refuri'].replace('.html', '')
_parent_directory_occurrences = _ref.count('..')
if not _parent_directory_occurrences and '/' not in docname:
# The current document is at the root of the documentation source directory
# (e.g. docname == 'index'|'applications'|...). i.e., the ref is already the docname.
_docname = _ref
else:
_path_parts = _ref.split('/')
_res = docname.split('/')[:-(_parent_directory_occurrences+1)] \
+ _path_parts[_parent_directory_occurrences:]
_docname = '/'.join(_res)
return _docname
def _clear_reference_if_empty_page(_reference_node, _node_docname):
""" Clear reference of 'empty' toctree pages.
Inspect parent node's siblings to determine whether the node references a toc and, if so,
clear its reference URL. (<a href="#"/>)
If the page has the `show-content` metadata, don't clear the reference.
"""
if _node_docname and any(
isinstance(_subnode, nodes.bullet_list)
for _subnode in _reference_node.parent.parent.children
): # The node references a toc
if 'show-content' not in tree.env.metadata[_node_docname]:
_reference_node['refuri'] = '#' # The page must not be accessible
def _set_docname_as_class(_reference_node, _node_docname):
_node_docname = _node_docname or docname # refuri==None <-> href="#"
_reference_node.parent.parent['classes'].append(f'o_menu_{_node_docname.replace("/", "_")}')
resolved_toc = old_resolve(tree, docname, *args, **kwargs)
if resolved_toc: # `resolve` returns None if the depth of the TOC to resolve is too high
_update_toctree_nodes(resolved_toc)
return resolved_toc
def icon_role(name, rawtext, text, lineno, inliner, options=None, content=None):
""" Implement an `icon` role for Odoo and Font Awesome icons. """
for icon_class in text.split():
if not (icon_class.startswith('fa-') or icon_class.startswith('oi-')):
report_error = inliner.reporter.error(
f"'{icon_class}' is not a valid icon formatting class.", lineno=lineno
)
error_node = inliner.problematic(rawtext, rawtext, report_error)
return [error_node], [report_error]
if text.startswith('oi-'):
icon_html = f'<i class="oi {text}"></i>'
elif text.startswith('fa-'):
icon_html = f'<i class="fa {text}"></i>'
else:
icon_html = f'<i class="{text}"></i>'
node = nodes.raw('', icon_html, format='html')
return [node], []