touchable-templates 2026.2.3a0__py3-none-any.whl → 2026.2.6a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ """Touchable Templates package initializer."""
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class TouchableTemplatesConfig(AppConfig):
5
+ name = "touchable_templates"
@@ -0,0 +1,124 @@
1
+ from django.template.loaders.filesystem import Loader as FileSystemLoader
2
+ from django.template.loaders.app_directories import Loader as AppDirectoriesLoader
3
+ from django.conf import settings
4
+ import os
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ IDE_TO_URI_MAPPER = {
10
+ "atom": "atom://core/open/file?filename=",
11
+ "codelite": "codelite://open?file=",
12
+ "cursor": "cursor://file/",
13
+ "emacs": "emacs://open?url=",
14
+ "espresso": "x-espresso://open?filepath=",
15
+ "idea": "idea://open?file=",
16
+ "macvim": "mvim://open/?url=",
17
+ "netbeans": "netbeans://open/?f=",
18
+ "nova": "nova://open?path=",
19
+ "pycharm": "pycharm://open?file=",
20
+ "sublime": "subl://open?url=",
21
+ "textmate": "txmt://open?url=",
22
+ "vscode": "vscode://file/",
23
+ "vscode-insiders": "vscode-insiders://file/",
24
+ "vscode-insiders-remote": "vscode-insiders://vscode-remote/",
25
+ "vscode-remote": "vscode://vscode-remote/",
26
+ "vscodium": "vscodium://file/",
27
+ "xdebug": "xdebug://",
28
+ }
29
+
30
+
31
+ def _is_third_party(filename: str) -> bool:
32
+ return "site-packages" in filename or "dist-packages" in filename
33
+
34
+
35
+ def _get_setting_or_env(name):
36
+ # Prefer Django setting, fall back to environment variable
37
+ return getattr(settings, name, None) or os.environ.get(name)
38
+
39
+
40
+ def _inject_ide_link(source_content, template_name, filename):
41
+ touchable_templates_ide = _get_setting_or_env("TOUCHABLE_TEMPLATES_IDE")
42
+ custom_uri = IDE_TO_URI_MAPPER.get(touchable_templates_ide)
43
+ touchable_templates_root = _get_setting_or_env("TOUCHABLE_TEMPLATES_ROOT") or ""
44
+ touchable_templates_remove_prefix = _get_setting_or_env("TOUCHABLE_TEMPLATES_REMOVE_PREFIX") or ""
45
+
46
+ try:
47
+ # compute a relative path portion if a remove-prefix was provided
48
+ rel_path = filename
49
+ if touchable_templates_remove_prefix and filename.startswith(touchable_templates_remove_prefix):
50
+ rel_path = filename[len(touchable_templates_remove_prefix):]
51
+
52
+ absolute_template_path = (custom_uri or "") + touchable_templates_root + rel_path
53
+ except Exception:
54
+ absolute_template_path = filename
55
+
56
+ # If template extends another template, we avoid wrapping the child; Django
57
+ # templates are compiled in processor order so this is a best-effort approach.
58
+
59
+ bordered = (
60
+ f'<div class="touchable-templates-container" '
61
+ f'data-template-name="{template_name}" '
62
+ f'data-template-path="{absolute_template_path}" '
63
+ f'style="display: block;">{source_content}</div>'
64
+ )
65
+
66
+ return bordered
67
+
68
+
69
+ class TouchableTemplatesLoader(FileSystemLoader):
70
+ """A drop-in replacement for Django's filesystem.Loader that wraps
71
+ template source in a touchable-templates container when enabled.
72
+
73
+ To use: in `TEMPLATES[...]['OPTIONS']['loaders']` reference
74
+ `touchable_templates.django.loader.TouchableTemplatesLoader` instead of
75
+ `django.template.loaders.filesystem.Loader`.
76
+ """
77
+
78
+ def get_contents(self, origin):
79
+ source = super().get_contents(origin)
80
+
81
+ enabled = _get_setting_or_env("TOUCHABLE_TEMPLATES_ENABLE")
82
+ if not enabled:
83
+ return source
84
+
85
+ try:
86
+ template_name = getattr(origin, "template_name", None) or getattr(origin, "name", "")
87
+ filename = getattr(origin, "name", "")
88
+
89
+ if _is_third_party(filename):
90
+ return source
91
+
92
+ return _inject_ide_link(source, template_name, filename)
93
+ except Exception:
94
+ logger.exception("touchable_templates: failed to inject IDE link into Django template")
95
+ return source
96
+
97
+
98
+ class TouchableTemplatesAppDirectoriesLoader(AppDirectoriesLoader):
99
+ """A drop-in replacement for Django's app_directories.Loader that wraps
100
+ template source in a touchable-templates container when enabled.
101
+
102
+ To use: in `TEMPLATES[...]['OPTIONS']['loaders']` reference
103
+ `touchable_templates.django.loader.TouchableTemplatesAppDirectoriesLoader` instead of
104
+ `django.template.loaders.app_directories.Loader`.
105
+ """
106
+
107
+ def get_contents(self, origin):
108
+ source = super().get_contents(origin)
109
+
110
+ enabled = _get_setting_or_env("TOUCHABLE_TEMPLATES_ENABLE")
111
+ if not enabled:
112
+ return source
113
+
114
+ try:
115
+ template_name = getattr(origin, "template_name", None) or getattr(origin, "name", "")
116
+ filename = getattr(origin, "name", "")
117
+
118
+ if _is_third_party(filename):
119
+ return source
120
+
121
+ return _inject_ide_link(source, template_name, filename)
122
+ except Exception:
123
+ logger.exception("touchable_templates: failed to inject IDE link into Django app template")
124
+ return source
@@ -0,0 +1,52 @@
1
+ from django.utils.deprecation import MiddlewareMixin
2
+ from django.conf import settings
3
+ from bs4 import BeautifulSoup
4
+
5
+
6
+ class TouchableTemplatesMiddleware(MiddlewareMixin):
7
+ """
8
+ Middleware to post-process Django template responses and mark children with
9
+ the `touchable-templates` class so the client script can provide IDE links.
10
+ """
11
+
12
+ def process_response(self, request, response):
13
+ if not getattr(settings, "TOUCHABLE_TEMPLATES_ENABLE", False):
14
+ return response
15
+
16
+ content_type = response.get('Content-Type', '').split(';')[0]
17
+ if content_type != 'text/html':
18
+ return response
19
+
20
+ # Decode the response content
21
+ content = response.content.decode(response.charset)
22
+ soup = BeautifulSoup(content, 'html.parser')
23
+
24
+ # Find all elements with class 'touchable-templates-container'
25
+ containers = soup.find_all(class_='touchable-templates-container')
26
+ for container in containers:
27
+ # Get data attributes from the container
28
+ data_template_name = container.get('data-template-name')
29
+ data_template_path = container.get('data-template-path')
30
+
31
+ # Iterate over direct children of the container
32
+ for child in container.find_all(recursive=False):
33
+ # Add 'touchable-templates' class to the child
34
+ existing_classes = child.get('class', [])
35
+ if 'touchable-templates' not in existing_classes:
36
+ child['class'] = existing_classes + ['touchable-templates']
37
+
38
+ # Copy data attributes to the child
39
+ if data_template_name:
40
+ child['data-template-name'] = data_template_name
41
+ if data_template_path:
42
+ child['data-template-path'] = data_template_path
43
+
44
+ # Remove the container but keep its children
45
+ container.unwrap()
46
+
47
+ # Update the response content
48
+ updated_content = str(soup).encode(response.charset)
49
+ response.content = updated_content
50
+ response['Content-Length'] = len(response.content)
51
+
52
+ return response
@@ -0,0 +1,66 @@
1
+ let altToggled = false;
2
+
3
+ // Append the element to the DOM, for example, to the body
4
+ var elemDiv = document.createElement('div');
5
+ elemDiv.id = 'floating-template-name';
6
+ elemDiv.style.cssText = "display: none; position: absolute; background: white; border: 1px solid black; padding: 5px; z-index: 9999;"
7
+ document.body.appendChild(elemDiv);
8
+ const floatingElement = document.getElementById("floating-template-name");
9
+
10
+ document.addEventListener("DOMContentLoaded", function(event) {
11
+
12
+ document.addEventListener('keydown', function(event) {
13
+ if (event.key === 'Alt') {
14
+ altToggled = !altToggled;
15
+ if (!altToggled) {
16
+ floatingElement.style.display = 'none';
17
+ }
18
+ }
19
+ });
20
+
21
+ document.addEventListener('click', function(event) {
22
+ if (altToggled) {
23
+ let closestElement = event.target.closest('.touchable-templates');
24
+ if (closestElement) {
25
+ let templatePath = closestElement.getAttribute('data-template-path');
26
+ if (templatePath) {
27
+ window.open(templatePath, '_blank');
28
+ event.stopPropagation();
29
+ }
30
+ }
31
+ }
32
+ }, true);
33
+
34
+ document.addEventListener('mousemove', (e) => {
35
+ if (!altToggled) return;
36
+ const el = e.target.closest('.touchable-templates');
37
+ if (!el) return floatingElement.style.display = 'none';
38
+
39
+ const name = el.getAttribute('data-template-name');
40
+ if (!name) return;
41
+
42
+ floatingElement.textContent = name;
43
+ floatingElement.style.display = 'block';
44
+ const { pageX: x, pageY: y } = e;
45
+ const { offsetWidth: w, offsetHeight: h } = floatingElement;
46
+ const { innerWidth: vw, innerHeight: vh } = window;
47
+ floatingElement.style.left = `${x + w + 20 > vw ? x - w - 5 : x + 10}px`;
48
+ floatingElement.style.top = `${y + h + 20 > vh ? y - h - 5 : y + 10}px`;
49
+ });
50
+
51
+ document.addEventListener('mouseleave', () => {
52
+ floatingElement.style.display = 'none';
53
+ });
54
+
55
+ document.addEventListener('mouseenter', (e) => {
56
+ if (altToggled) {
57
+ const el = e.target.closest('.touchable-templates');
58
+ if (el) {
59
+ const name = el.getAttribute('data-template-name');
60
+ floatingElement.textContent = name;
61
+ floatingElement.style.display = 'block';
62
+ }
63
+ }
64
+ });
65
+
66
+ });
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: touchable-templates
3
- Version: 2026.2.3a0
3
+ Version: 2026.2.6a0
4
4
  Summary: A dev tool that magically opens templates and partials in your IDE
5
5
  Author: Brady Whitby
6
6
  License-Expression: MIT
@@ -114,6 +114,8 @@ Click on any element on the page to open the corresponding template in your conf
114
114
  - Use `TOUCHABLE_TEMPLATES_REMOVE_PREFIX` to strip any leading path segments that are
115
115
  not part of the local filesystem path (e.g. if your project runs in a Docker container
116
116
  with a different root path).
117
+ - Touchable-templates only processes templates that are part of your project.
118
+ It skips templates that are part of third-party packages (e.g. in `site-packages` or `dist-packages`).
117
119
 
118
120
  ## Troubleshooting
119
121
 
@@ -0,0 +1,9 @@
1
+ touchable_templates/__init__.py,sha256=wuVP6KxqX0TnmQegZGdHALFdKInO22IOx2lIbhObvQw,47
2
+ touchable_templates/apps.py,sha256=3qlRtprhZRI5wPnYLEz6QAWHwACeZvKO7RV-C8Gv1Uc,112
3
+ touchable_templates/django/loader.py,sha256=ePcYi4TnJjcx2c_TGJ6lAzKJ2X6k0BREK1hzjg9y5lk,4679
4
+ touchable_templates/django/middleware.py,sha256=LYKs-WRO9q_wpQVzFlDxxL5Z2E6Ny49EQwWf2L-Si0o,2147
5
+ touchable_templates/static/js/touchable_templates.js,sha256=9vxxTAWqbN0V5l7UJqjCc5lkNZ00AGn3nYo35Vkr79c,2436
6
+ touchable_templates-2026.2.6a0.dist-info/METADATA,sha256=2LKQzLRmD5pAsObqdmiOphfRLDBfXDg_Ijz2bvz7vkg,3740
7
+ touchable_templates-2026.2.6a0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
+ touchable_templates-2026.2.6a0.dist-info/licenses/LICENSE,sha256=5XdcGaWnUE-XZdjP6bSx-Ad0XTkc6heUI3KckeaRZng,1068
9
+ touchable_templates-2026.2.6a0.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- touchable_templates-2026.2.3a0.dist-info/METADATA,sha256=v19J3Ixvm2hFhxbhUeWrWNns9ikAQH6U6hzz2XZze0I,3557
2
- touchable_templates-2026.2.3a0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
3
- touchable_templates-2026.2.3a0.dist-info/licenses/LICENSE,sha256=5XdcGaWnUE-XZdjP6bSx-Ad0XTkc6heUI3KckeaRZng,1068
4
- touchable_templates-2026.2.3a0.dist-info/RECORD,,