touchable-templates 2026.2.3a0__py3-none-any.whl → 2026.2.5a0__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,112 @@
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 _get_setting_or_env(name):
32
+ # Prefer Django setting, fall back to environment variable
33
+ return getattr(settings, name, None) or os.environ.get(name)
34
+
35
+
36
+ def _inject_ide_link(source_content, template_name, filename):
37
+ touchable_templates_ide = _get_setting_or_env("TOUCHABLE_TEMPLATES_IDE")
38
+ custom_uri = IDE_TO_URI_MAPPER.get(touchable_templates_ide)
39
+ touchable_templates_root = _get_setting_or_env("TOUCHABLE_TEMPLATES_ROOT") or ""
40
+ touchable_templates_remove_prefix = _get_setting_or_env("TOUCHABLE_TEMPLATES_REMOVE_PREFIX") or ""
41
+
42
+ try:
43
+ # compute a relative path portion if a remove-prefix was provided
44
+ rel_path = filename
45
+ if touchable_templates_remove_prefix and filename.startswith(touchable_templates_remove_prefix):
46
+ rel_path = filename[len(touchable_templates_remove_prefix):]
47
+
48
+ absolute_template_path = (custom_uri or "") + touchable_templates_root + rel_path
49
+ except Exception:
50
+ absolute_template_path = filename
51
+
52
+ # If template extends another template, we avoid wrapping the child; Django
53
+ # templates are compiled in processor order so this is a best-effort approach.
54
+
55
+ bordered = (
56
+ f'<div class="touchable-templates-container" '
57
+ f'data-template-name="{template_name}" '
58
+ f'data-template-path="{absolute_template_path}" '
59
+ f'style="display: block;">{source_content}</div>'
60
+ )
61
+
62
+ return bordered
63
+
64
+
65
+ class TouchableTemplatesLoader(FileSystemLoader):
66
+ """A drop-in replacement for Django's filesystem.Loader that wraps
67
+ template source in a touchable-templates container when enabled.
68
+
69
+ To use: in `TEMPLATES[...]['OPTIONS']['loaders']` reference
70
+ `touchable_templates.django.loader.TouchableTemplatesLoader` instead of
71
+ `django.template.loaders.filesystem.Loader`.
72
+ """
73
+
74
+ def get_contents(self, origin):
75
+ source = super().get_contents(origin)
76
+
77
+ enabled = _get_setting_or_env("TOUCHABLE_TEMPLATES_ENABLE")
78
+ if not enabled:
79
+ return source
80
+
81
+ try:
82
+ template_name = getattr(origin, "template_name", None) or getattr(origin, "name", "")
83
+ filename = getattr(origin, "name", "")
84
+ return _inject_ide_link(source, template_name, filename)
85
+ except Exception:
86
+ logger.exception("touchable_templates: failed to inject IDE link into Django template")
87
+ return source
88
+
89
+
90
+ class TouchableTemplatesAppDirectoriesLoader(AppDirectoriesLoader):
91
+ """A drop-in replacement for Django's app_directories.Loader that wraps
92
+ template source in a touchable-templates container when enabled.
93
+
94
+ To use: in `TEMPLATES[...]['OPTIONS']['loaders']` reference
95
+ `touchable_templates.django.loader.TouchableTemplatesAppDirectoriesLoader` instead of
96
+ `django.template.loaders.app_directories.Loader`.
97
+ """
98
+
99
+ def get_contents(self, origin):
100
+ source = super().get_contents(origin)
101
+
102
+ enabled = _get_setting_or_env("TOUCHABLE_TEMPLATES_ENABLE")
103
+ if not enabled:
104
+ return source
105
+
106
+ try:
107
+ template_name = getattr(origin, "template_name", None) or getattr(origin, "name", "")
108
+ filename = getattr(origin, "name", "")
109
+ return _inject_ide_link(source, template_name, filename)
110
+ except Exception:
111
+ logger.exception("touchable_templates: failed to inject IDE link into Django app template")
112
+ 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.5a0
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
@@ -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=vrATDKg4kyKTcbv3E3xuwQURmeNWdPDSBkILydQqtcU,4415
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.5a0.dist-info/METADATA,sha256=z3toGVwRbsElaTrnfWWBvPs0p9o3N_2pSOc89WZW6XM,3557
7
+ touchable_templates-2026.2.5a0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
+ touchable_templates-2026.2.5a0.dist-info/licenses/LICENSE,sha256=5XdcGaWnUE-XZdjP6bSx-Ad0XTkc6heUI3KckeaRZng,1068
9
+ touchable_templates-2026.2.5a0.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,,