Skip to content

Commit f9975d8

Browse files
authored
Add badge and link-badge roles (#27)
1 parent 5e0a433 commit f9975d8

8 files changed

Lines changed: 287 additions & 48 deletions

File tree

docs/index.rst

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ A sphinx extension for creating panels in a grid layout or as drop-downs.
2222
2323
Content of the top-right panel
2424
25+
:badge:`example,badge-primary`
26+
2527
---
2628
2729
.. dropdown:: :fa:`eye,mr-1` Bottom-left panel
@@ -42,6 +44,8 @@ A sphinx extension for creating panels in a grid layout or as drop-downs.
4244

4345
Content of the top-right panel
4446

47+
:badge:`example,badge-primary`
48+
4549
---
4650

4751
.. dropdown:: :fa:`eye,mr-1` Bottom-left panel
@@ -463,6 +467,60 @@ to make the entire panel clickable:
463467
:text: Go To Reference
464468
:classes: btn-outline-primary btn-block stretched-link
465469

470+
Link Badges
471+
===========
472+
473+
Badges are inline text with special formatting. Use the ``badge`` role to assign
474+
`Bootstrap badge formatting <https://getbootstrap.com/docs/4.0/components/badge/>`_.
475+
Text and classes are delimited by a comma:
476+
477+
.. code-block:: rst
478+
479+
:badge:`primary,badge-primary`
480+
481+
:badge:`primary,badge-primary badge-pill`
482+
483+
:badge:`primary,badge-primary`
484+
:badge:`secondary,badge-secondary`
485+
:badge:`info,badge-info`
486+
:badge:`success,badge-success`
487+
:badge:`danger,badge-danger`
488+
:badge:`warning,badge-warning`
489+
:badge:`light,badge-light`
490+
:badge:`dark,badge-dark`
491+
492+
:badge:`primary,badge-primary badge-pill`
493+
:badge:`secondary,badge-secondary badge-pill`
494+
:badge:`info,badge-info badge-pill`
495+
:badge:`success,badge-success badge-pill`
496+
:badge:`danger,badge-danger badge-pill`
497+
:badge:`warning,badge-warning badge-pill`
498+
:badge:`light,badge-light badge-pill`
499+
:badge:`dark,badge-dark badge-pill`
500+
501+
The ``link-badge`` also adds the ability to use a link to a URI or reference:
502+
503+
.. code-block:: rst
504+
505+
:link-badge:`https://example.com,cls=badge-primary text-white,tooltip=a tooltip`
506+
:link-badge:`https://example.com,"my, text",cls=badge-dark text-white`
507+
:link-badge:`panels/usage,my reference,ref,badge-success text-white,hallo`
508+
509+
:link-badge:`https://example.com,cls=badge-primary text-white,tooltip=a tooltip`
510+
:link-badge:`https://example.com,"my, text",cls=badge-dark text-white`
511+
:link-badge:`panels/usage,my reference,ref,badge-success text-white`
512+
513+
Note the inputs are parsed by the following functions. The role text therefore uses these
514+
function signatures, except you don't need to use quoted strings,
515+
unless the string contains a comma.
516+
517+
.. code-block:: python
518+
519+
def get_badge_inputs(text, cls: str = ""):
520+
return text, cls.split()
521+
522+
def get_link_badge_inputs(link, text=None, type="link", cls: str = "", tooltip=None):
523+
return link, text or link, type, cls.split(), tooltip
466524
467525
Dropdown Usage
468526
==============

setup.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@
2525
packages=find_packages(),
2626
package_data={
2727
"sphinx_panels": [
28-
"css/bs-cards.css",
29-
"css/bs-grids.css",
30-
"css/bs-borders.css",
31-
"css/bs-buttons.css",
3228
"css/panels-bootstrap.min.css",
3329
"css/sphinx-dropdown.css",
3430
"data/opticons.json",

sphinx_panels/button.py

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,44 @@
11
from urllib.parse import unquote
22

33
from docutils import nodes
4+
from docutils.utils import unescape
45
from docutils.parsers.rst import directives
56
from sphinx import addnodes
67
from sphinx.util.docutils import SphinxDirective
78

9+
from .utils import string_to_func_inputs
10+
811

912
def setup_link_button(app):
1013
app.add_directive("link-button", LinkButton)
14+
# TODO hide badges in non-HTML?
15+
app.add_role("badge", badge_role)
16+
app.add_role("link-badge", link_badge_role)
17+
18+
19+
def create_ref_node(link_type, uri, text, tooltip):
20+
innernode = nodes.inline(text, text)
21+
if link_type == "ref":
22+
ref_node = addnodes.pending_xref(
23+
reftarget=unquote(uri),
24+
reftype="any",
25+
# refdoc=self.env.docname,
26+
refdomain="",
27+
refexplicit=True,
28+
refwarn=True,
29+
)
30+
innernode["classes"] = ["xref", "any"]
31+
# if tooltip:
32+
# ref_node["reftitle"] = tooltip
33+
# ref_node["title"] = tooltip
34+
# TODO this doesn't work
35+
else:
36+
ref_node = nodes.reference()
37+
ref_node["refuri"] = uri
38+
if tooltip:
39+
ref_node["reftitle"] = tooltip
40+
ref_node += innernode
41+
return ref_node
1142

1243

1344
class LinkButton(SphinxDirective):
@@ -28,34 +59,56 @@ def run(self):
2859
link_type = self.options.get("type", "url")
2960

3061
text = self.options.get("text", uri)
31-
innernode = nodes.inline("", text)
32-
33-
if link_type == "ref":
34-
ref_node = addnodes.pending_xref(
35-
reftarget=unquote(uri),
36-
reftype="any",
37-
# refdoc=self.env.docname,
38-
refdomain="",
39-
refexplicit=True,
40-
refwarn=True,
41-
)
42-
innernode["classes"] = ["xref", "any"]
43-
if "tooltip" in self.options:
44-
ref_node["title"] = self.options["tooltip"]
45-
else:
46-
ref_node = nodes.reference()
47-
ref_node["refuri"] = uri
48-
if "tooltip" in self.options:
49-
ref_node["reftitle"] = self.options["tooltip"]
5062

63+
ref_node = create_ref_node(
64+
link_type, uri, text, self.options.get("tooltip", None)
65+
)
5166
self.set_source_info(ref_node)
52-
5367
ref_node["classes"] = ["sphinx-bs", "btn", "text-wrap"] + self.options.get(
5468
"classes", ""
5569
).split()
56-
ref_node += innernode
70+
5771
# sphinx requires that a reference be inside a block element
5872
container = nodes.paragraph()
5973
container += ref_node
6074

6175
return [container]
76+
77+
78+
def get_badge_inputs(text, cls: str = ""):
79+
return text, cls.split()
80+
81+
82+
def badge_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
83+
try:
84+
args, kwargs = string_to_func_inputs(text)
85+
text, classes = get_badge_inputs(*args, **kwargs)
86+
except Exception as err:
87+
msg = inliner.reporter.error(f"badge input is invalid: {err}", line=lineno)
88+
prb = inliner.problematic(rawtext, rawtext, msg)
89+
return [prb], [msg]
90+
node = nodes.inline(
91+
rawtext, unescape(text), classes=["sphinx-bs", "badge"] + classes
92+
)
93+
# textnodes, messages = inliner.parse(text, lineno, node, memo)
94+
# TODO this would require the memo with reporter, document and language
95+
return [node], []
96+
97+
98+
def get_link_badge_inputs(link, text=None, type="link", cls: str = "", tooltip=None):
99+
return link, text or link, type, cls.split(), tooltip
100+
101+
102+
def link_badge_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
103+
try:
104+
args, kwargs = string_to_func_inputs(text)
105+
uri, text, link_type, classes, tooltip = get_link_badge_inputs(*args, **kwargs)
106+
except Exception as err:
107+
msg = inliner.reporter.error(f"badge input is invalid: {err}", line=lineno)
108+
prb = inliner.problematic(rawtext, rawtext, msg)
109+
return [prb], [msg]
110+
ref_node = create_ref_node(link_type, uri, text, tooltip)
111+
if lineno is not None:
112+
ref_node.source, ref_node.line = inliner.reporter.get_source_and_line(lineno)
113+
ref_node["classes"] = ["sphinx-bs", "badge"] + classes
114+
return [ref_node], []

sphinx_panels/css/bs-badge.css

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
.badge {
2+
display:inline-block;
3+
padding:.25em .4em;
4+
font-size:75%;
5+
font-weight:700;
6+
line-height:1;
7+
text-align:center;
8+
white-space:nowrap;
9+
vertical-align:baseline;
10+
border-radius:.25rem
11+
}
12+
.badge:empty {
13+
display:none
14+
}
15+
.btn .badge {
16+
position:relative;
17+
top:-1px
18+
}
19+
.badge-pill {
20+
padding-right:.6em;
21+
padding-left:.6em;
22+
border-radius:10rem
23+
}
24+
.badge-primary {
25+
color:#fff;
26+
background-color:#007bff
27+
}
28+
.badge-primary[href]:focus,
29+
.badge-primary[href]:hover {
30+
color:#fff;
31+
text-decoration:none;
32+
background-color:#0062cc
33+
}
34+
.badge-secondary {
35+
color:#fff;
36+
background-color:#6c757d
37+
}
38+
.badge-secondary[href]:focus,
39+
.badge-secondary[href]:hover {
40+
color:#fff;
41+
text-decoration:none;
42+
background-color:#545b62
43+
}
44+
.badge-success {
45+
color:#fff;
46+
background-color:#28a745
47+
}
48+
.badge-success[href]:focus,
49+
.badge-success[href]:hover {
50+
color:#fff;
51+
text-decoration:none;
52+
background-color:#1e7e34
53+
}
54+
.badge-info {
55+
color:#fff;
56+
background-color:#17a2b8
57+
}
58+
.badge-info[href]:focus,
59+
.badge-info[href]:hover {
60+
color:#fff;
61+
text-decoration:none;
62+
background-color:#117a8b
63+
}
64+
.badge-warning {
65+
color:#212529;
66+
background-color:#ffc107
67+
}
68+
.badge-warning[href]:focus,
69+
.badge-warning[href]:hover {
70+
color:#212529;
71+
text-decoration:none;
72+
background-color:#d39e00
73+
}
74+
.badge-danger {
75+
color:#fff;
76+
background-color:#dc3545
77+
}
78+
.badge-danger[href]:focus,
79+
.badge-danger[href]:hover {
80+
color:#fff;
81+
text-decoration:none;
82+
background-color:#bd2130
83+
}
84+
.badge-light {
85+
color:#212529;
86+
background-color:#f8f9fa
87+
}
88+
.badge-light[href]:focus,
89+
.badge-light[href]:hover {
90+
color:#212529;
91+
text-decoration:none;
92+
background-color:#dae0e5
93+
}
94+
.badge-dark {
95+
color:#fff;
96+
background-color:#343a40
97+
}
98+
.badge-dark[href]:focus,
99+
.badge-dark[href]:hover {
100+
color:#fff;
101+
text-decoration:none;
102+
background-color:#1d2124
103+
}

sphinx_panels/css/panels-bootstrap.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sphinx_panels/icons.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from ast import literal_eval
21
from functools import lru_cache
32
import json
43
from pathlib import Path
54

65
from docutils import nodes
76

7+
from .utils import string_to_func_inputs
8+
89

910
OPTICON_VERSION = "0.0.0-dd899ea"
1011

@@ -71,30 +72,11 @@ def get_opticon(
7172
return f"<svg {opt_string}>{content}</svg>"
7273

7374

74-
def string_to_func_inputs(text):
75-
# TODO is there a better way to do this?
76-
args = []
77-
kwargs = {}
78-
for opt in text.split(","):
79-
if "=" not in opt:
80-
try:
81-
args.append(literal_eval(opt))
82-
except Exception:
83-
args.append(opt)
84-
else:
85-
key, val = opt.split("=", maxsplit=1)
86-
try:
87-
kwargs[key.strip()] = literal_eval(val)
88-
except Exception:
89-
kwargs[key.strip()] = val
90-
return args, kwargs
91-
92-
9375
def opticon_role(
9476
role, rawtext: str, text: str, lineno, inliner, options={}, content=[]
9577
):
96-
args, kwargs = string_to_func_inputs(text)
9778
try:
79+
args, kwargs = string_to_func_inputs(text)
9880
svg = get_opticon(*args, **kwargs)
9981
except Exception as err:
10082
msg = inliner.reporter.error(f"Opticon input is invalid: {err}", line=lineno)
@@ -117,8 +99,8 @@ def create_fa_node(name, classes: str = None, style="fa"):
11799

118100

119101
def fontawesome_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
120-
args, kwargs = string_to_func_inputs(text)
121102
try:
103+
args, kwargs = string_to_func_inputs(text)
122104
node = create_fa_node(*args, **kwargs)
123105
except Exception as err:
124106
msg = inliner.reporter.error(

0 commit comments

Comments
 (0)