inspect: add button to toggle invisible spaces before/after strings (#41598) #1346
|
@ -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>')) == '<b>test</b>'
|
||||
|
||||
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"> </span></span>'
|
||||
tab = '<span class="escaped-code-point" data-escaped="[U+0009]"><span class="char"> </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 + '<b>test</b>'
|
||||
|
|
|
@ -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>')
|
||||
|
|
|
@ -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"> </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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
)
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue