From 6fef111390d3fd0e0ef2ae1218e191d82e171a3d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 27 Jun 2022 18:27:12 +0200 Subject: [PATCH 1/3] Prefix all CSS classes with `hxr-` Avoid collisioning with other CSS frameworks/themes/etc. See https://github.com/executablebooks/sphinx-book-theme/issues/577 Closes #180 --- hoverxref/_static/css/tooltipster.custom.css | 2 +- hoverxref/_static/js/hoverxref.js_t | 6 +++--- hoverxref/domains.py | 13 ++++++++----- hoverxref/extension.py | 3 ++- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/hoverxref/_static/css/tooltipster.custom.css b/hoverxref/_static/css/tooltipster.custom.css index 45914bec..6eb49e8f 100644 --- a/hoverxref/_static/css/tooltipster.custom.css +++ b/hoverxref/_static/css/tooltipster.custom.css @@ -1,4 +1,4 @@ -.hoverxref { +.hxr-hoverxref { border-bottom: 1px dotted; border-color: gray; } diff --git a/hoverxref/_static/js/hoverxref.js_t b/hoverxref/_static/js/hoverxref.js_t index 348d95e9..49e045e8 100644 --- a/hoverxref/_static/js/hoverxref.js_t +++ b/hoverxref/_static/js/hoverxref.js_t @@ -96,9 +96,9 @@ $(document).ready(function() { // Remove ``title=`` attribute for intersphinx nodes that have hoverxref enabled. // It doesn't make sense the browser shows the default tooltip (browser's built-in) // and immediately after that our tooltip was shown. - $('.hoverxref.external').each(function () { $(this).removeAttr('title') }); + $('.hxr-hoverxref.external').each(function () { $(this).removeAttr('title') }); - $('.hoverxref.tooltip').tooltipster({ + $('.hxr-hoverxref.hxr-tooltip').tooltipster({ theme: {{ hoverxref_tooltip_theme }}, interactive: {{ 'true' if hoverxref_tooltip_interactive else 'false' }}, maxWidth: {{ hoverxref_tooltip_maxwidth }}, @@ -235,7 +235,7 @@ $(document).ready(function() { }; var delay = {{ hoverxref_modal_hover_delay }}, setTimeoutConst; - $('.hoverxref.modal').hover(function(event) { + $('.hxr-hoverxref.hxr-modal').hover(function(event) { var element = $(this); console.debug('Event: ' + event + ' Element: ' + element); event.preventDefault(); diff --git a/hoverxref/domains.py b/hoverxref/domains.py index 20e12c7c..f79b07a5 100644 --- a/hoverxref/domains.py +++ b/hoverxref/domains.py @@ -7,6 +7,7 @@ class HoverXRefBaseDomain: + css_class_prefix = 'hxr-' hoverxref_types = ( 'hoverxref', 'hoverxreftooltip', @@ -14,13 +15,13 @@ class HoverXRefBaseDomain: ) def _inject_hoverxref_data(self, env, refnode, typ): - classes = ['hoverxref'] + classes = ['hxr-hoverxref'] type_class = None if typ == 'hoverxreftooltip': - type_class = 'tooltip' + type_class = 'hxr-tooltip' classes.append(type_class) elif typ == 'hoverxrefmodal': - type_class = 'modal' + type_class = 'hxr-modal' classes.append(type_class) if not type_class: type_class = env.config.hoverxref_role_types.get(typ) @@ -33,11 +34,13 @@ def _inject_hoverxref_data(self, env, refnode, typ): default, typ, ) - classes.append(type_class) + + # Examples: hxr-tooltip, hxr-modal + classes.append(f'{self.css_class_prefix}{type_class}') refnode.replace_attr('classes', classes) # TODO: log something else here, so we can unique identify this node - logger.debug( + logger.info( ':%s: _hoverxref injected. classes=%s', typ, classes, diff --git a/hoverxref/extension.py b/hoverxref/extension.py index 0dc27af8..28596333 100644 --- a/hoverxref/extension.py +++ b/hoverxref/extension.py @@ -19,6 +19,7 @@ logger = logging.getLogger(__name__) +CSS_CLASS_PREFIX = 'hxr-' HOVERXREF_ASSETS_FILES = [ 'js/hoverxref.js_t', # ``_t`` tells Sphinx this is a template @@ -264,7 +265,7 @@ def missing_reference(app, env, node, contnode): hoverxref_type = hoverxref_type or app.config.hoverxref_default_type classes = newnode.get('classes') - classes.extend(['hoverxref', hoverxref_type]) + classes.extend(['hxr-hoverxref', f'{CSS_CLASS_PREFIX}{hoverxref_type}']) newnode.replace_attr('classes', classes) return newnode From 4b7b72f4ede0a9746ce25131c475cd74d9031720 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 28 Jun 2022 11:14:25 +0200 Subject: [PATCH 2/3] Update tests to use the prefix `hxr-` --- tests/test_htmltag.py | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_htmltag.py b/tests/test_htmltag.py index 9097041d..c89bcd29 100644 --- a/tests/test_htmltag.py +++ b/tests/test_htmltag.py @@ -20,7 +20,7 @@ def test_default_settings(app, status, warning): chunks = [ 'This a :ref: to Chapter I', - 'This a :hoverxref: to Chapter I, Section I', + 'This a :hoverxref: to Chapter I, Section I', ] for chunk in chunks: @@ -75,7 +75,7 @@ def test_autosectionlabel_project_version_settings(app, status, warning): chunks = [ 'This a :ref: to Chapter I.', - 'This a :hoverxref: to Chapter I', + 'This a :hoverxref: to Chapter I', ] for chunk in chunks: @@ -93,9 +93,9 @@ def test_custom_object(app, status, warning): content = open(path).read() chunks = [ - 'This is a :confval: to conf-title', - 'This is a :hoverxref: to Configuration document', - 'This is a :numref: to a Python code block (PyExample)' + 'This is a :confval: to conf-title', + 'This is a :hoverxref: to Configuration document', + 'This is a :numref: to a Python code block (PyExample)' ] for chunk in chunks: @@ -115,9 +115,9 @@ def test_python_domain(app, status, warning): content = open(path).read() chunks = [ - 'This is a :py:class: role to a Python object', - 'hoverxref.extension', - 'hoverxref.extension.setup()', + 'This is a :py:class: role to a Python object', + 'hoverxref.extension', + 'hoverxref.extension.setup()', 'Constant', ] @@ -146,9 +146,9 @@ def test_python_domain_intersphinx(app, status, warning): content = open(path).read() chunks = [ - 'This is a :py:class: role to a Python object', - 'hoverxref.extension', - 'hoverxref.extension.setup()', + 'This is a :py:class: role to a Python object', + 'hoverxref.extension', + 'hoverxref.extension.setup()', 'Constant', ] @@ -173,7 +173,7 @@ def test_bibtex_domain(app, status, warning): content = open(path).read() chunks = [ - '

See Nelson [Nel87] for an introduction to non-standard analysis.\nNon-standard analysis is fun [Nel87].

', + '

See Nelson [Nel87] for an introduction to non-standard analysis.\nNon-standard analysis is fun [Nel87].

', ] for chunk in chunks: @@ -193,7 +193,7 @@ def test_glossary_term_domain(app, status, warning): content = open(path).read() chunks = [ - '

See definition builder for more information.

', + '

See definition builder for more information.

', ] for chunk in chunks: @@ -214,7 +214,7 @@ def test_default_type(app, status, warning): chunks = [ 'This a :ref: to Chapter I', - 'This a :hoverxref: to Chapter I, Section I', + 'This a :hoverxref: to Chapter I, Section I', ] for chunk in chunks: @@ -244,7 +244,7 @@ def test_ignore_refs(app, status, warning): assert chunk in content ignored_chunks = [ - 'This a :hoverxref: to Chapter I, Section I', + 'This a :hoverxref: to Chapter I, Section I', ] for chunk in ignored_chunks: assert chunk not in content @@ -303,9 +303,9 @@ def test_intersphinx_python_mapping(app, status, warning): chunks_regex = [ # Python's links do have hoverxref enabled - r'This a :ref: to The Python Tutorial using intersphinx', - r'This a :ref: to datetime.datetime Python’s function using intersphinx', - r'float', + r'This a :ref: to The Python Tutorial using intersphinx', + r'This a :ref: to datetime.datetime Python’s function using intersphinx', + r'float', # Read the Docs' link does not have hoverxref enabled r'This a :ref: to Config File v2 Read the Docs’ page using intersphinx', @@ -349,21 +349,21 @@ def test_intersphinx_all_mappings(app, status, warning): chunks_regex = [ # Python's links do have hoverxref enabled - r'This a :ref: to The Python Tutorial using intersphinx', - r'This a :ref: to datetime.datetime Python’s function using intersphinx', - r'float', + r'This a :ref: to The Python Tutorial using intersphinx', + r'This a :ref: to datetime.datetime Python’s function using intersphinx', + r'float', # Read the Docs' link does have hoverxref enabled - r'This a :ref: to Config File v2 Read the Docs’ page using intersphinx', + r'This a :ref: to Config File v2 Read the Docs’ page using intersphinx', # Using `default_role = 'obj'` # Note the difference with the same `float` line previouly. Here it uses `py-obj` instead of `py-class`. - 'float' + r'float' ] chunks = [ # Python domain's link does have hoverxref enabled - 'hoverxref.extension.setup()', + 'hoverxref.extension.setup()', ] for chunk in chunks: From 447e662a5f9516e2445724f4226f54102a87c4ba Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 28 Jun 2022 11:46:09 +0200 Subject: [PATCH 3/3] Refactor code to use CSS prefix variable --- ...er.custom.css => tooltipster.custom.css_t} | 2 +- hoverxref/_static/js/hoverxref.js_t | 6 +++--- hoverxref/domains.py | 18 ++++++++-------- hoverxref/extension.py | 21 ++++++++++++++----- 4 files changed, 29 insertions(+), 18 deletions(-) rename hoverxref/_static/css/{tooltipster.custom.css => tooltipster.custom.css_t} (91%) diff --git a/hoverxref/_static/css/tooltipster.custom.css b/hoverxref/_static/css/tooltipster.custom.css_t similarity index 91% rename from hoverxref/_static/css/tooltipster.custom.css rename to hoverxref/_static/css/tooltipster.custom.css_t index 6eb49e8f..8bc54d66 100644 --- a/hoverxref/_static/css/tooltipster.custom.css +++ b/hoverxref/_static/css/tooltipster.custom.css_t @@ -1,4 +1,4 @@ -.hxr-hoverxref { +.{{ hoverxref_css_class_prefix }}hoverxref { border-bottom: 1px dotted; border-color: gray; } diff --git a/hoverxref/_static/js/hoverxref.js_t b/hoverxref/_static/js/hoverxref.js_t index 49e045e8..fe9286b8 100644 --- a/hoverxref/_static/js/hoverxref.js_t +++ b/hoverxref/_static/js/hoverxref.js_t @@ -96,9 +96,9 @@ $(document).ready(function() { // Remove ``title=`` attribute for intersphinx nodes that have hoverxref enabled. // It doesn't make sense the browser shows the default tooltip (browser's built-in) // and immediately after that our tooltip was shown. - $('.hxr-hoverxref.external').each(function () { $(this).removeAttr('title') }); + $('.{{ hoverxref_css_class_prefix }}hoverxref.external').each(function () { $(this).removeAttr('title') }); - $('.hxr-hoverxref.hxr-tooltip').tooltipster({ + $('.{{ hoverxref_css_class_prefix }}hoverxref.{{ hoverxref_css_class_prefix }}tooltip').tooltipster({ theme: {{ hoverxref_tooltip_theme }}, interactive: {{ 'true' if hoverxref_tooltip_interactive else 'false' }}, maxWidth: {{ hoverxref_tooltip_maxwidth }}, @@ -235,7 +235,7 @@ $(document).ready(function() { }; var delay = {{ hoverxref_modal_hover_delay }}, setTimeoutConst; - $('.hxr-hoverxref.hxr-modal').hover(function(event) { + $('.{{ hoverxref_css_class_prefix }}hoverxref.{{ hoverxref_css_class_prefix }}modal').hover(function(event) { var element = $(this); console.debug('Event: ' + event + ' Element: ' + element); event.preventDefault(); diff --git a/hoverxref/domains.py b/hoverxref/domains.py index f79b07a5..03360d77 100644 --- a/hoverxref/domains.py +++ b/hoverxref/domains.py @@ -7,7 +7,6 @@ class HoverXRefBaseDomain: - css_class_prefix = 'hxr-' hoverxref_types = ( 'hoverxref', 'hoverxreftooltip', @@ -15,14 +14,15 @@ class HoverXRefBaseDomain: ) def _inject_hoverxref_data(self, env, refnode, typ): - classes = ['hxr-hoverxref'] + from .extension import CSS_CLASSES, CSS_DEFAULT_CLASS + + classes = [CSS_DEFAULT_CLASS] type_class = None if typ == 'hoverxreftooltip': - type_class = 'hxr-tooltip' - classes.append(type_class) + type_class = 'tooltip' elif typ == 'hoverxrefmodal': - type_class = 'hxr-modal' - classes.append(type_class) + type_class = 'modal' + if not type_class: type_class = env.config.hoverxref_role_types.get(typ) if not type_class: @@ -35,12 +35,12 @@ def _inject_hoverxref_data(self, env, refnode, typ): typ, ) - # Examples: hxr-tooltip, hxr-modal - classes.append(f'{self.css_class_prefix}{type_class}') + # Examples: hxr-tooltip, hxr-modal + classes.append(CSS_CLASSES[type_class]) refnode.replace_attr('classes', classes) # TODO: log something else here, so we can unique identify this node - logger.info( + logger.debug( ':%s: _hoverxref injected. classes=%s', typ, classes, diff --git a/hoverxref/extension.py b/hoverxref/extension.py index 28596333..a3ce46c9 100644 --- a/hoverxref/extension.py +++ b/hoverxref/extension.py @@ -20,6 +20,11 @@ logger = logging.getLogger(__name__) CSS_CLASS_PREFIX = 'hxr-' +CSS_DEFAULT_CLASS = f'{CSS_CLASS_PREFIX}hoverxref' +CSS_CLASSES = { + 'tooltip': f'{CSS_CLASS_PREFIX}tooltip', + 'modal': f'{CSS_CLASS_PREFIX}modal', +} HOVERXREF_ASSETS_FILES = [ 'js/hoverxref.js_t', # ``_t`` tells Sphinx this is a template @@ -28,7 +33,7 @@ TOOLTIP_ASSETS_FILES = [ # Tooltipster's Styles 'js/tooltipster.bundle.min.js', - 'css/tooltipster.custom.css', + 'css/tooltipster.custom.css_t', 'css/tooltipster.bundle.min.css', # Tooltipster's Themes @@ -66,6 +71,7 @@ def copy_asset_files(app, exception): # Then, add the values that the user overrides context[attr] = getattr(app.config, attr) + context['hoverxref_css_class_prefix'] = CSS_CLASS_PREFIX context['http_hoverxref_version'] = __version__ # Finally, add some non-hoverxref extra configs @@ -74,10 +80,13 @@ def copy_asset_files(app, exception): context[attr] = getattr(app.config, attr) for f in ASSETS_FILES: + # Example: "./_static/js/hoverxref.js_t" path = os.path.join(os.path.dirname(__file__), '_static', f) + # Example: "/_static/css" or "/_static/js" + output = os.path.join(app.outdir, '_static', f.split('/')[0]) copy_asset( path, - os.path.join(app.outdir, '_static', f.split('.')[-1].replace('js_t', 'js')), + output, context=context, ) @@ -265,7 +274,7 @@ def missing_reference(app, env, node, contnode): hoverxref_type = hoverxref_type or app.config.hoverxref_default_type classes = newnode.get('classes') - classes.extend(['hxr-hoverxref', f'{CSS_CLASS_PREFIX}{hoverxref_type}']) + classes.extend([CSS_DEFAULT_CLASS, CSS_CLASSES[hoverxref_type]]) newnode.replace_attr('classes', classes) return newnode @@ -375,11 +384,13 @@ def setup(app): app.connect('missing-reference', missing_reference) + # Include all assets previously copied/rendered by ``copy_asset_files`` as + # Javascript and CSS files into the Sphinx application for f in ASSETS_FILES: if f.endswith('.js') or f.endswith('.js_t'): app.add_js_file(f.replace('.js_t', '.js')) - if f.endswith('.css'): - app.add_css_file(f) + if f.endswith('.css') or f.endswith('.css_t'): + app.add_css_file(f.replace('.css_t', '.css')) return { 'version': __version__,