Merge branch '12.0-one-doc-edi' of github.com:odoo/documentation-user into 12.0-one-doc-edi

This commit is contained in:
Victor Feyens 2021-02-22 16:58:31 +01:00
commit 6091147656
14 changed files with 231 additions and 113 deletions

View File

@ -30,13 +30,16 @@ clean:
$(RM_CMD) extensions/odoo_theme/static/style.css
@echo "Cleaning finished."
edi: SPHINXOPTS += -A collapse_menu=True
#edi: SPHINXOPTS += -A collapse_menu=True # If needed, comment rather than setting False
edi: VERSIONS += 12.0,13.0,14.0
edi: CANONICAL_VERSION += 14.0
edi: LANGUAGES += en,fr,es
edi: CURRENT_LANG += fr
edi: clean html
static: extensions/odoo_theme/static extensions/odoo_theme/static/style.css
cp -r extensions/odoo_theme/static/* _build/html/_static/
html: extensions/odoo_theme/static/style.css
@echo "Starting build..."
$(SPHINX_BUILD) -c $(CONFIG_DIR) -b html $(SPHINXOPTS) $(SOURCE_DIR) $(BUILD_DIR)/html

View File

@ -1,3 +1,5 @@
:show_content:
============
Applications
============

View File

@ -1,5 +1,5 @@
:empty_page:
:show_content:
=================================
Contributing to the documentation

View File

@ -1,32 +1,27 @@
from . import pygments_override
from . import translator
import sphinx.builders.html
from docutils import nodes
from sphinx import addnodes
from sphinx.environment.adapters import toctree
from docutils import nodes
from . import pygments_override, translator
def setup(app):
app.set_translator('html', translator.BootstrapTranslator)
# VFE TODO check if default meta initialization is necessary.
# If not, remove update_meta method
app.connect('html-page-context', update_meta)
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')
def update_meta(app, pagename, templatename, context, doctree):
meta = context.get('meta')
if meta is None:
meta = context['meta'] = {}
# TODO VFE detailed explanation of the patch logic and use.
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(object):
""" 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):
@ -36,41 +31,67 @@ class Monkey(object):
@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):
"""
docname = a/b/c/the_page_being_rendered
_ref = ../../contributing/documentation
_path_parts = ['..', '..', 'contributing', 'documentation']
_res = ['a', 'contributing', 'documentation']
_docname = a/contributing/documentation
"""
_ref = _node['refuri'].replace('.html', '')
_parent_directory_occurrences = _ref.count('..')
if not _parent_directory_occurrences: # 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:
# Not sure set_class really does what we want.
_toctree_add_empty_class(tree, resolved_toc, docname)
resolved_toc['classes'].append('testtesttest')
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 _toctree_add_empty_class(tree, node, docname) -> None:
for subnode in node.children:
if isinstance(subnode, (
addnodes.compact_paragraph,
nodes.list_item,
nodes.bullet_list
)):
# for <p>, <li> and <ul> just recurse
_toctree_add_empty_class(tree, subnode, docname)
elif isinstance(subnode, nodes.reference):
toc_ref = get_reference(subnode, docname)
if toc_ref and 'empty_page' in tree.env.metadata[toc_ref]:
subnode['classes'].append('o_empty_page')
def get_reference(node, docname):
ref = node['refuri'].replace('.html', '') # applications.html
if ref.find('..') < 0:
# direct reference
return ref
splitted_refuri = ref.split('/')
count = 0 # Number of ../ in refuri
for split in splitted_refuri:
if split == "..":
count += 1
# ref = ../../../contributing/documentation
# docname = services/legal/terms/enterprise
# res = contributing/documentation
res = docname.split('/')[:-(count+1)] + splitted_refuri[count:]
return "/".join(
res
)

View File

@ -64,11 +64,11 @@
{% set main_classes = [] %}
{% if pagename == master_doc %} {# The current page is the homepage #}
{% set main_classes = main_classes + ['index'] %}
{% set main_classes = main_classes + ['index'] %} {# TODO ANVFE should be 'o_index' #}
{% endif %}
{% if 'code-column' in meta %} {# The page contains a 'memento' (side dynamic block) #}
{% set main_classes = main_classes + ['has_code_col'] %}
{% set main_classes = main_classes + ['has_code_col'] %} {# TODO ANVFE see #}
{% endif %}
{% if 'classes' in meta %} {# The page source defines custom classes #}

View File

@ -1,6 +1,5 @@
<div class="o_logo_wrapper">
<a href="{{ pathto(master_doc) }}" class="o_logo">
{# TODO EDI/design make one unique image for Odoo docs s.t. when clicking on docs you are also redirected #}
<img src="{{ pathto('_static/img/logos/odoo_logo.svg', 1) }}"
height="30" alt="Odoo"/>
<span class="text-dark fw_extralight">docs</span>

View File

@ -3,7 +3,7 @@
<button class="btn border dropdown-toggle" id="languages" data-bs-toggle="dropdown">
{{ language }} {# The current language #}
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languages">
<ul class="dropdown-menu" aria-labelledby="languages">
{% for alternate_language, language_code, url in alternate_languages %}
<li><a class="dropdown-item" href="{{ url }}">{{ alternate_language }}</a></li>
{% endfor %}

View File

@ -5,5 +5,8 @@
title_only: Whether menu items for content pages (without toctree) should be hidden
includehidden: Whether menu items of pages inside a hidden toctree should be rendered
#}
{{ toctree(collapse=collapse_menu, titles_only=True, includehidden=True)}}
{# TODO replace `collapse_menu` by `True` and remove Makefile entry 'light' #}
{#
`collapse_menu` is passed directly to Jinja with sphinx-build's option `-A collapse_menu=True`.
It it evaluated as a string, so what we're really evaluating here is `collapse_menu != None`.
#}
{{ toctree(collapse=collapse_menu, titles_only=True, includehidden=False)}}

View File

@ -27,6 +27,9 @@ class OdooStyle(Style):
Keyword.Constant: 'bold',
Name.Builtin: '#2c2cff',
Name.Function: 'bold italic',
Name.Class: "bold #0000FF",
Name.Namespace: "bold #0000FF",
Name.Exception: 'bg:#e3d2d2 #a61717',
Name.Variable: 'bold #2c2cff',
Name.Attribute: '#2c2cff',
Name.Tag: "bold #008000",

View File

@ -1,26 +1,42 @@
(function ($) {
document.addEventListener('DOMContentLoaded', () => {
const navigationMenu = document.getElementById('o_main_toctree');
this.navigationMenu = document.getElementById('o_main_toctree');
// Add a class with the name of the file to each corresponding menu item
_flagMenuItemsWithFileName(navigationMenu);
// Allow to automatically collapse and expand TOC entries
_prepareAccordion(this.navigationMenu);
// Allow to respectively highlight and expand the TOC entries and their related TOC
// entry list whose page is displayed.
_flagActiveTocEntriesAndLists();
});
/**
* Add the name of the file as class of the corresponding menu item.
* Add the relevant classes on the TOC entries (and lists) whose page is displayed.
*
* @param {HTMLElement} navigationMenu - The navigation menu containing the global TOC
* TOC entries (<li> elements) that are on the path of the displayed page receive the
* `o_active_toc_entry` class, and their related (parent) TOC entry list (<ul> elements) receive
* the `show` (Bootstrap) class. The child TOC entry list of the deepest TOC entry also
* receives the `show` class.
*/
const _flagMenuItemsWithFileName = (navigationMenu) => {
navigationMenu.querySelectorAll('li').forEach(menuItem => {
let href = menuItem.querySelector('a').href;
if (href === '#') { // Selected nodes don't have their file name in the href
href = window.location.href; // Get it from the current window location
const _flagActiveTocEntriesAndLists = () => {
let deepestTocEntry = undefined;
this.navigationMenu.querySelectorAll('.current').forEach(element => {
if (element.tagName === 'UL') {
// Expand all related <ul>
element.classList.add('show');
} else if (element.tagName === 'LI') {
// Highlight all <li> in the active hierarchy
element.classList.add('o_active_toc_entry');
deepestTocEntry = element;
}
const fileName = href.substring(href.lastIndexOf('/') + 1, href.lastIndexOf('.html'));
menuItem.classList.add(`o_menu_${fileName}`);
});
})
if (deepestTocEntry) {
const childTocEntryList = deepestTocEntry.querySelector('ul');
if (childTocEntryList) {
childTocEntryList.classList.add('show');
}
}
};
})();

View File

@ -12,23 +12,28 @@
return;
}
// Allow to hide the TOC entry referring the title (<h1> heading)
_flagFirstHeadingRef();
// Allow to automatically collapse and expand TOC entries
_prepareAccordion();
_prepareAccordion(this.pageToc);
// Allow to respectively highlight and expand the TOC entries and their related TOC
// entry list whose section is focused.
_flagActiveTocEntriesAndLists();
// Allow to hide the TOC entry referring the title (<h1> heading)
_flagFirstHeadingRef();
}
});
/**
* Entirely hide the local tree of contents.
*/
const _hidePageToc = () => this.pageToc.style.visibility = 'hidden';
/**
* Add the relevant classes on the TOC entries (and lists) whose section is focused.
*
* TOC entries whose section is focused (<li> elements) receive the `active` class and their
* related TOC entry list (<ul> elements) receive the `show` class.
* TOC entries whose section is focused (<li> elements) receive the `o_active_toc_entry` class
* and their related TOC entry list (<ul> elements) receive the `show` (Bootstrap) class.
*/
const _flagActiveTocEntriesAndLists = () => {
@ -70,7 +75,7 @@
const _unflagAll = () => {
this.pageToc.querySelectorAll('li,ul').forEach(element => {
element.classList.remove('active', 'show');
element.classList.remove('o_active_toc_entry', 'show');
});
};
@ -78,10 +83,13 @@
let tocEntry = headingRef.parentElement;
while (tocEntry !== this.pageToc) {
if (tocEntry.tagName === 'LI') {
tocEntry.classList.add('active'); // Highlight all <li> in the active hierarchy
// Highlight all <li> in the active hierarchy
tocEntry.classList.add('o_active_toc_entry');
// Expand all related <ul>
const relatedTocEntryList = tocEntry.querySelector('ul');
if (relatedTocEntryList) {
relatedTocEntryList.classList.add('show'); // Expand all related <ul>
relatedTocEntryList.classList.add('show');
}
}
tocEntry = tocEntry.parentElement;
@ -94,10 +102,10 @@
});
let timeoutId = undefined;
document.addEventListener('scroll', () => {
clearTimeout(timeoutId); // For each scroll event, cancel the previous timeout callback
timeoutId = setTimeout(() => {
clickedHeadingRef = undefined; // Go back to highlighting the heading ref in view
}, 100);
clearTimeout(timeoutId); // For each scroll event, cancel the previous timeout callback
_updateFlags();
});
@ -108,30 +116,8 @@
/**
* Add the class `o_page_toc_title` on the first heading reference.
*/
const _flagFirstHeadingRef = () => this.headingRefs[0].classList.add('o_page_toc_title');
/**
* Entirely hide the local tree of contents.
*/
const _hidePageToc = () => this.pageToc.style.visibility = 'hidden';
/**
* Update the page TOC entries and heading references to allow collapsing them.
*/
const _prepareAccordion = () => {
// Start at the second TOC entry list (<ul>) to avoid collapsing the entire TOC
const pageTocRoot = this.pageToc.querySelectorAll('ul')[1];
pageTocRoot.querySelectorAll('ul').forEach(tocEntryList => {
const relatedHeadingRef = tocEntryList.previousSibling; // The preceding <a> element
tocEntryList.id = `o_target_${relatedHeadingRef.getAttribute('href').replace('#', '')}`
tocEntryList.classList.add('collapse');
relatedHeadingRef.setAttribute('data-bs-target', `#${tocEntryList.id}`);
relatedHeadingRef.setAttribute('data-bs-toggle', 'collapse');
});
// TODO [ANV]
// current output: <a class="reference internal collapsed" href="#text" data-bs-target="#o_target_text" data-bs-toggle="collapse" aria-expanded="false">Text</a>
// desired output: <a class="reference internal" href="#text"><i class="i-chevron-right collapsed" data-bs-target="#o_target_text" data-bs-toggle="collapse" aria-expanded="false"></i> Text</a>
};
const _flagFirstHeadingRef = () => {
this.headingRefs[0].parentNode.classList.add('o_page_toc_title');
}
})();

View File

@ -0,0 +1,64 @@
let tocEntryListId = 0; // Used to generate IDs of toc entry lists for both the menu and page TOC
/**
* Update the provided TOC to allow collapsing its entries with Bootstrap's accordion.
*
* The typical structure of a TOC menu is a follows:
* <ul><li>
* <a href="#"/>
* <ul>
* <li><a href="#heading_without_child"/></li>
* <li>
* <a href="#heading_with_children"/>
* <ul>...</ul>
* </li>
* </ul>
* </li></ul>
*
* Since a <ul> is always preceded by a <a>, and since we only need to make change to <a>
* elements followed by a <ul>, we simply loop on <ul> elements to access all parts of the DOM
* that need to be modified.
*
* The final structure must look like this:
* <ul><li>
* <!-- Only <a> element with empty href must expand/collapse on click -->
* <a href="#" data-bs-target="#o_target_{id}>" data-bs-toggle="collapse"/>
* <ul>
* <li><a href="#heading_without_child"/></li>
* <li>
* <div class="o_toc_entry_wrapper">
* <i class="i-chevron-right" data-bs-target="#o_target_{id}" data-bs-toggle="collapse"/>
* <a href="#heading_with_children"/>
* </div>
* <ul id="o_target_{id}" class="collapse">...</ul>
* </li>
* </ul>
* </li></ul>
*
* @param {HTMLElement} tocElement - The element containing the TOC
*/
const _prepareAccordion = (tocElement) => {
// Start at the second TOC entry list (<ul>) to avoid collapsing the entire TOC
const tocRoot = tocElement.querySelector('ul');
tocRoot.querySelectorAll('ul').forEach(tocEntryList => {
// Modify the <ul> element
tocEntryList.id = `o_target_${tocEntryListId++}`
tocEntryList.classList.add('collapse');
// Create and configure an <i> element
const arrowButton = document.createElement('I');
arrowButton.setAttribute('data-bs-target', `#${tocEntryList.id}`);
arrowButton.setAttribute('data-bs-toggle', 'collapse');
arrowButton.classList.add('i-chevron-right');
// Modify the <a> element (only if it has no href, otherwise let the redirection happen)
const relatedHeadingRef = tocEntryList.previousSibling;
if (relatedHeadingRef.getAttribute('href') === '#') {
relatedHeadingRef.setAttribute('data-bs-target', `#${tocEntryList.id}`);
relatedHeadingRef.setAttribute('data-bs-toggle', 'collapse');
}
// Create a <div> element
const tocEntryWrapper = document.createElement('DIV');
tocEntryWrapper.classList.add('o_toc_entry_wrapper');
// Insert the <i> and <a> elements inside the <div> and prepend the <div> to the <ul>
tocEntryWrapper.append(arrowButton, relatedHeadingRef);
tocEntryList.parentNode.insertBefore(tocEntryWrapper, tocEntryList);
});
};

View File

@ -59,6 +59,7 @@ $display-line-height: 1;
// For each of Bootstrap's buttons, define text, background, and border color.
$btn-font-weight: $fw_semibold;
$btn-focus-box-shadow: 0 0 0 transparent;
// Badges
@ -106,3 +107,6 @@ $list-group-border-color: $card-border-color;
$nav-pills-link-active-color: #fff;
$nav-pills-link-active-bg: o-color('o-color-2');
// Dropdowns
$dropdown-min-width: 4.5rem;

View File

@ -115,25 +115,42 @@ header.o_main_header{
}
> .toctree-l1 {
> a:before {
&[class^="o_menu_"] > .o_toc_entry_wrapper > i:before {
@include inline-icomoon($i-doc-apps, 0 1rem 0 -30px);
}
&.o_menu_applications > a:before{
&.o_menu_applications > .o_toc_entry_wrapper > i:before{
content:'#{$i-doc-apps}';
}
&.o_menu_administration > a:before{
&.o_menu_administration > .o_toc_entry_wrapper > i:before{
content:'#{$i-doc-admin}';
}
&.o_menu_developer > a:before{
&.o_menu_developer > .o_toc_entry_wrapper > i:before{
content:'#{$i-doc-dev}';
}
&.o_menu_services > a:before{
&.o_menu_services > .o_toc_entry_wrapper > i:before{
content:'#{$i-doc-services}';
}
&.o_menu_contributing > a:before{
&.o_menu_contributing > .o_toc_entry_wrapper > i:before{
content:'#{$i-doc-contribute}';
}
.o_toc_entry_wrapper {
display: flex;
align-items: center;
}
}
> .toctree-l2 {
.o_toc_entry_wrapper {
> i[class^="i-"] {
transition: rotate .3s;
&[aria-expanded="true"]{
transform: rotate(90deg);
}
}
}
}
}
@ -620,7 +637,7 @@ header.o_main_header{
}
}
}
.active > a{
.o_active_toc_entry > a {
font-weight: $fw_bold;
}
}