test bkp
This commit is contained in:
1 parent
6ccab0347b
commit
954a2fd6d6
18684 files changed
+601012
-190
No files matched your search
@@ -0,0 +1,303 @@
|
||||
"""Tabs."""
|
||||
import xml.etree.ElementTree as etree
|
||||
from markdown.extensions import toc
|
||||
from markdown.treeprocessors import Treeprocessor
|
||||
from .block import Block, type_boolean
|
||||
from ..blocks import BlocksExtension
|
||||
import html
|
||||
|
||||
HEADERS = {'h1', 'h2', 'h3', 'h4', 'h5', 'h6'}
|
||||
|
||||
|
||||
class TabbedTreeprocessor(Treeprocessor):
|
||||
"""Tab tree processor."""
|
||||
|
||||
def __init__(self, md, config):
|
||||
"""Initialize."""
|
||||
|
||||
super().__init__(md)
|
||||
|
||||
self.alternate = config['alternate_style']
|
||||
self.slugify = config['slugify']
|
||||
self.combine_header_slug = config['combine_header_slug']
|
||||
self.sep = config["separator"]
|
||||
|
||||
def get_parent_header_slug(self, root, header_map, parent_map, el):
|
||||
"""Attempt retrieval of parent header slug."""
|
||||
|
||||
parent = el
|
||||
last_parent = parent
|
||||
while parent is not root:
|
||||
last_parent = parent
|
||||
parent = parent_map[parent]
|
||||
if parent in header_map:
|
||||
headers = header_map[parent]
|
||||
header = None
|
||||
for i in list(parent):
|
||||
if i is el and header is None:
|
||||
break
|
||||
if i is last_parent and header is not None:
|
||||
return header.attrib.get("id", '')
|
||||
if i in headers:
|
||||
header = i
|
||||
return ''
|
||||
|
||||
def run(self, doc):
|
||||
"""Update tab IDs."""
|
||||
|
||||
# Get a list of id attributes
|
||||
used_ids = set()
|
||||
parent_map = {}
|
||||
header_map = {}
|
||||
|
||||
if self.combine_header_slug:
|
||||
parent_map = {c: p for p in doc.iter() for c in p}
|
||||
|
||||
for el in doc.iter():
|
||||
if "id" in el.attrib:
|
||||
if self.combine_header_slug and el.tag in HEADERS:
|
||||
parent = parent_map[el]
|
||||
if parent in header_map:
|
||||
header_map[parent].append(el)
|
||||
else:
|
||||
header_map[parent] = [el]
|
||||
used_ids.add(el.attrib["id"])
|
||||
|
||||
for el in doc.iter():
|
||||
if isinstance(el.tag, str) and el.tag.lower() == 'div':
|
||||
classes = el.attrib.get('class', '').split()
|
||||
if 'tabbed-set' in classes and (not self.alternate or 'tabbed-alternate' in classes):
|
||||
inputs = []
|
||||
labels = []
|
||||
if self.alternate:
|
||||
for i in list(el):
|
||||
if i.tag == 'input':
|
||||
inputs.append(i)
|
||||
if i.tag == 'div' and i.attrib.get('class', '') == 'tabbed-labels':
|
||||
labels = [j for j in list(i) if j.tag == 'label']
|
||||
else:
|
||||
for i in list(el):
|
||||
if i.tag == 'input':
|
||||
inputs.append(i)
|
||||
if i.tag == 'label':
|
||||
labels.append(i)
|
||||
|
||||
# Generate slugged IDs
|
||||
for inpt, label in zip(inputs, labels):
|
||||
innerhtml = toc.render_inner_html(toc.remove_fnrefs(label), self.md)
|
||||
innertext = html.unescape(toc.strip_tags(innerhtml))
|
||||
if self.combine_header_slug:
|
||||
parent_slug = self.get_parent_header_slug(doc, header_map, parent_map, el)
|
||||
else:
|
||||
parent_slug = ''
|
||||
slug = self.slugify(innertext, self.sep)
|
||||
if parent_slug:
|
||||
slug = parent_slug + self.sep + slug
|
||||
slug = toc.unique(slug, used_ids)
|
||||
inpt.attrib["id"] = slug
|
||||
label.attrib["for"] = slug
|
||||
|
||||
|
||||
class Tab(Block):
|
||||
"""
|
||||
Tabbed container.
|
||||
|
||||
Arguments (1 required):
|
||||
- A tab title.
|
||||
|
||||
Options:
|
||||
- `new` (boolean): since consecutive tabs are automatically grouped, `new` can force a tab
|
||||
to start a new tab container.
|
||||
|
||||
Content:
|
||||
Detail body.
|
||||
"""
|
||||
|
||||
NAME = 'tab'
|
||||
|
||||
ARGUMENT = True
|
||||
OPTIONS = {
|
||||
'new': (False, type_boolean),
|
||||
'select': (False, type_boolean)
|
||||
}
|
||||
|
||||
def on_init(self):
|
||||
"""Handle initialization."""
|
||||
|
||||
self.alternate_style = self.config['alternate_style']
|
||||
self.slugify = callable(self.config['slugify'])
|
||||
|
||||
# Track tab group count across the entire page.
|
||||
if 'tab_group_count' not in self.tracker:
|
||||
self.tracker['tab_group_count'] = 0
|
||||
|
||||
self.tab_content = None
|
||||
|
||||
def last_child(self, parent):
|
||||
"""Return the last child of an `etree` element."""
|
||||
|
||||
if len(parent):
|
||||
return parent[-1]
|
||||
else:
|
||||
return None
|
||||
|
||||
def on_add(self, block):
|
||||
"""Adjust where the content is added."""
|
||||
|
||||
if self.tab_content is None:
|
||||
if self.alternate_style:
|
||||
for d in block.findall('div'):
|
||||
c = d.attrib['class']
|
||||
if c == 'tabbed-content' or c.startswith('tabbed-content '):
|
||||
self.tab_content = list(d)[-1]
|
||||
break
|
||||
else:
|
||||
self.tab_content = list(block)[-1]
|
||||
|
||||
return self.tab_content
|
||||
|
||||
def on_create(self, parent):
|
||||
"""Create the element."""
|
||||
|
||||
new_group = self.options['new']
|
||||
select = self.options['select']
|
||||
title = self.argument
|
||||
sibling = self.last_child(parent)
|
||||
tabbed_set = 'tabbed-set' if not self.alternate_style else 'tabbed-set tabbed-alternate'
|
||||
index = 0
|
||||
labels = None
|
||||
content = None
|
||||
|
||||
if (
|
||||
sibling is not None and sibling.tag.lower() == 'div' and
|
||||
sibling.attrib.get('class', '') == tabbed_set and
|
||||
not new_group
|
||||
):
|
||||
first = False
|
||||
tab_group = sibling
|
||||
|
||||
if self.alternate_style:
|
||||
index = [index for index, _ in enumerate(tab_group.findall('input'), 1)][-1]
|
||||
for d in tab_group.findall('div'):
|
||||
if d.attrib['class'] == 'tabbed-labels':
|
||||
labels = d
|
||||
elif d.attrib['class'] == 'tabbed-content':
|
||||
content = d
|
||||
if labels is not None and content is not None:
|
||||
break
|
||||
else:
|
||||
first = True
|
||||
self.tracker['tab_group_count'] += 1
|
||||
tab_group = etree.SubElement(
|
||||
parent,
|
||||
'div',
|
||||
{'class': tabbed_set, 'data-tabs': '%d:0' % self.tracker['tab_group_count']}
|
||||
)
|
||||
|
||||
if self.alternate_style:
|
||||
labels = etree.SubElement(
|
||||
tab_group,
|
||||
'div',
|
||||
{'class': 'tabbed-labels'}
|
||||
)
|
||||
content = etree.SubElement(
|
||||
tab_group,
|
||||
'div',
|
||||
{'class': 'tabbed-content'}
|
||||
)
|
||||
|
||||
data = tab_group.attrib['data-tabs'].split(':')
|
||||
tab_set = int(data[0])
|
||||
tab_count = int(data[1]) + 1
|
||||
|
||||
attributes = {
|
||||
"name": "__tabbed_%d" % tab_set,
|
||||
"type": "radio"
|
||||
}
|
||||
|
||||
if not self.slugify:
|
||||
attributes['id'] = "__tabbed_%d_%d" % (tab_set, tab_count)
|
||||
|
||||
attributes2 = {"for": "__tabbed_%d_%d" % (tab_set, tab_count)} if not self.slugify else {}
|
||||
|
||||
if first or select:
|
||||
attributes['checked'] = 'checked'
|
||||
# Remove any previously assigned "checked states" to siblings
|
||||
for i in tab_group.findall('input'):
|
||||
if i.attrib.get('name', '') == f'__tabbed_{tab_set}':
|
||||
if 'checked' in i.attrib:
|
||||
del i.attrib['checked']
|
||||
|
||||
if self.alternate_style:
|
||||
input_el = etree.Element(
|
||||
'input',
|
||||
attributes
|
||||
)
|
||||
tab_group.insert(index, input_el)
|
||||
lab = etree.SubElement(
|
||||
labels,
|
||||
"label",
|
||||
attributes2
|
||||
)
|
||||
lab.text = title
|
||||
|
||||
attrib = {'class': 'tabbed-block'}
|
||||
etree.SubElement(
|
||||
content,
|
||||
"div",
|
||||
attrib
|
||||
)
|
||||
else:
|
||||
etree.SubElement(
|
||||
tab_group,
|
||||
'input',
|
||||
attributes
|
||||
)
|
||||
lab = etree.SubElement(
|
||||
tab_group,
|
||||
"label",
|
||||
attributes2
|
||||
)
|
||||
lab.text = title
|
||||
|
||||
etree.SubElement(
|
||||
tab_group,
|
||||
"div",
|
||||
{
|
||||
"class": "tabbed-content"
|
||||
}
|
||||
)
|
||||
|
||||
tab_group.attrib['data-tabs'] = '%d:%d' % (tab_set, tab_count)
|
||||
|
||||
return tab_group
|
||||
|
||||
|
||||
class TabExtension(BlocksExtension):
|
||||
"""Admonition Blocks Extension."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize."""
|
||||
|
||||
self.config = {
|
||||
'alternate_style': [False, "Use alternate style - Default: False"],
|
||||
'slugify': [0, "Slugify function used to create tab specific IDs - Default: None"],
|
||||
'combine_header_slug': [False, "Combine the tab slug with the slug of the parent header - Default: False"],
|
||||
'separator': ['-', "Slug separator - Default: '-'"]
|
||||
}
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def extendMarkdownBlocks(self, md, block_mgr):
|
||||
"""Extend Markdown blocks."""
|
||||
|
||||
block_mgr.register(Tab, self.getConfigs())
|
||||
if callable(self.getConfig('slugify')):
|
||||
slugs = TabbedTreeprocessor(md, self.getConfigs())
|
||||
md.treeprocessors.register(slugs, 'tab_slugs', 4)
|
||||
|
||||
|
||||
def makeExtension(*args, **kwargs):
|
||||
"""Return extension."""
|
||||
|
||||
return TabExtension(*args, **kwargs)
|
||||
Reference in new issue
Block a user