inspect: add button to toggle invisible spaces before/after strings (#41598) #1346

Merged
fpeters merged 1 commits from wip/41598-inspect-invisible-spaces into main 2024-04-12 07:37:47 +02:00
5 changed files with 81 additions and 3 deletions

View File

@ -27,6 +27,7 @@ from wcs.qommon.misc import (
ellipsize,
format_time,
get_as_datetime,
mark_spaces,
normalize_geolocation,
parse_decimal,
parse_isotime,
@ -759,3 +760,19 @@ def test_parse_decimal_keep_none(value, expected):
def test_parse_decimal_do_raise(value, exception):
with pytest.raises(exception):
parse_decimal(value, do_raise=True)
def test_mark_spaces():
assert mark_spaces('test') == 'test'
assert str(mark_spaces('<b>test</b>')) == '&lt;b&gt;test&lt;/b&gt;'
button_code = (
'<button class="toggle-escape-button" role="button" '
'title="This line contains invisible characters."></button>'
)
space = '<span class="escaped-code-point" data-escaped="[U+0020]"><span class="char">&nbsp;</span></span>'
tab = '<span class="escaped-code-point" data-escaped="[U+0009]"><span class="char">&nbsp;</span></span>'
assert str(mark_spaces(' test ')) == button_code + space + 'test' + space
assert str(mark_spaces(' test ')) == button_code + space + 'test' + space + space
assert str(mark_spaces('test\t ')) == button_code + 'test' + tab + space
assert str(mark_spaces(' <b>test</b>')) == button_code + space + '&lt;b&gt;test&lt;/b&gt;'

View File

@ -4062,7 +4062,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
if field_url:
r += htmltext(' <a title="%s" href="%s"></a>' % (v._field.label, field_url))
r += htmltext('</code>')
r += htmltext(' <div class="value"><span>%s</span>') % v
r += htmltext(' <div class="value"><span>%s</span>') % misc.mark_spaces(v)
if isinstance(v, NoneFieldVar):
r += htmltext(' <span class="type">(%s)</span>') % _('no value')
elif isinstance(v, (types.FunctionType, types.MethodType)):
@ -4103,7 +4103,13 @@ class FormBackOfficeStatusPage(FormStatusPage):
r += ', '.join(custom_repr(x) for x in v)
r += htmltext(']</span>')
else:
r += htmltext(' <div class="value"><span>%s</span>') % ellipsize(safe(v), 10000)
if k in ('form_details', 'form_evolution'):
# do not mark spaces in those variables
r += htmltext(' <div class="value"><span>%s</span>') % ellipsize(safe(v), 10000)
else:
r += htmltext(' <div class="value"><span>%s</span>') % misc.mark_spaces(
ellipsize(safe(v), 10000)
)
if not isinstance(v, str):
r += htmltext(' <span class="type">(%s)</span>') % get_type_name(v)
r += htmltext('</div></li>')

View File

@ -48,7 +48,7 @@ from django.utils.timezone import is_aware, make_naive
from PIL import Image
from quixote import get_publisher, get_request, get_response, redirect
from quixote.errors import RequestError
from quixote.html import htmltext
from quixote.html import htmlescape, htmltext
from requests.adapters import HTTPAdapter
from . import _, ezt, force_str, get_cfg, get_logger
@ -1365,3 +1365,26 @@ def parse_decimal(value, do_raise=False, keep_none=False):
if do_raise:
raise
return decimal.Decimal(0)
def mark_spaces(s):
s = str(htmlescape(str(s)))
got_sub = False
def get_sub(match):
nonlocal got_sub
got_sub = True
return ''.join(
f'<span class="escaped-code-point" data-escaped="[U+{ord(x):04X}]"><span class="char">&nbsp;</span></span>'
for x in match.group()
)
s = re.sub(r'^(\s+)', get_sub, s)
s = re.sub(r'(\s+)$', get_sub, s)
s = htmltext(s)
if got_sub:
s = htmltext(
'<button class="toggle-escape-button" role="button" title="%s"></button>%s'
% (_('This line contains invisible characters.'), s)
)
return s

View File

@ -1887,6 +1887,31 @@ ul.form-inspector li {
}
}
.display-codepoints .escaped-code-point[data-escaped] {
&::before {
content: attr(data-escaped);
color: var(--red);
}
.char {
display: none;
}
}
button.toggle-escape-button {
border: 0;
padding: 0;
display: inline-block;
width: 2em;
margin-left: -2em;
&::before {
content: "⚠️";
}
&:active, &:focus, &:hover {
border: inherit;
background: inherit;
outline: inherit;
}
}
div#inspect-test-tools form + br {
display: none;

View File

@ -512,4 +512,11 @@ $(function() {
compact_table_dataview_switch.dispatchEvent(new Event('change'))
}
}
document.querySelectorAll('.toggle-escape-button').forEach(
el => el.addEventListener('click', (event) => {
event.preventDefault()
el.parentNode.classList.toggle('display-codepoints')
})
)
});