Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/sentry/templates/sentry/emails/_group.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ <h3>
{% endif %}
<br />
{% if metadata.value %}
<small>{{ metadata.value|truncatechars:100|soft_break:40 }}</small>
<small>{{ metadata.value|truncatechars:100|soft_break_html:40 }}</small>
{% endif %}
{% else %}
{%if rule %}
Expand Down Expand Up @@ -57,7 +57,7 @@ <h3>
{% endif %}
<br />
{% if metadata.value %}
<small>{{ metadata.value|truncatechars:100|soft_break:40 }}</small>
<small>{{ metadata.value|truncatechars:100|soft_break_html:40 }}</small>
{% endif %}
</h3>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/templates/sentry/emails/issue_alert_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ <h3>
{% endif %}
<br />
{% if metadata.value %}
<small>{{ metadata.value|truncatechars:100|soft_break:40 }}</small>
<small>{{ metadata.value|truncatechars:100|soft_break_html:40 }}</small>
{% endif %}
{% else %}
<a href="{% absolute_uri link %}">{{ metadata.value|truncatechars:40 }}</a><br />
Expand Down Expand Up @@ -151,7 +151,7 @@ <h3>
{% endif %}
<br />
{% if subtitle and not is_new_design %}
<small>{{ subtitle|truncatechars:40|soft_break:40 }}</small>
<small>{{ subtitle|truncatechars:40|soft_break_html:40 }}</small>
{% endif %}
</h3>
</div>
Expand Down
28 changes: 27 additions & 1 deletion src/sentry/templatetags/sentry_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ def small_count(v, precision=1):

@register.filter
def as_tag_alias(v):
return {"sentry:release": "release", "sentry:dist": "dist", "sentry:user": "user"}.get(v, v)
return {
"sentry:release": "release",
"sentry:dist": "dist",
"sentry:user": "user",
}.get(v, v)


@register.simple_tag(takes_context=True)
Expand Down Expand Up @@ -295,6 +299,28 @@ def soft_break(value, length):
)


@register.filter(is_safe=True)
def soft_break_html(value, length):
"""HTML-safe variant of ``soft_break``.

Uses ``<wbr>`` tags instead of Unicode soft-hyphens (``\\u00ad``) and
zero-width spaces (``\\u200b``). This provides identical line-break
hints for email clients while ensuring that copied text contains no
invisible characters that break searches in editors and tools.
"""
from django.utils.html import escape

escaped = escape(value)
return mark_safe(
_soft_break(
escaped,
length,
functools.partial(soft_hyphenate, length=max(length // 10, 10), hyphen="<wbr>"),
delimiter_break="<wbr>",
)
)


@register.simple_tag
def random_int(a, b=None):
if b is None:
Expand Down
20 changes: 17 additions & 3 deletions src/sentry/utils/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,25 @@ def soft_hyphenate(value: str, length: int, hyphen: str = "\u00ad") -> str:
return hyphen.join([value[i : (i + length)] for i in range(0, len(value), length)])


def soft_break(value: str, length: int, process: Callable[[str], str] = lambda chunk: chunk) -> str:
def soft_break(
value: str,
length: int,
process: Callable[[str], str] = lambda chunk: chunk,
delimiter_break: str = "\u200b",
) -> str:
"""
Encourages soft breaking of text values above a maximum length by adding
zero-width spaces after common delimiters, as well as soft-hyphenating long
identifiers.

Args:
value: The text to process.
length: Minimum token length before soft-breaking kicks in.
process: Callable applied to non-delimiter chunks (e.g. ``soft_hyphenate``).
delimiter_break: Character inserted after each delimiter to hint a line
break. Defaults to ``\\u200b`` (zero-width space). Pass ``<wbr>``
for HTML contexts so that copied text contains no invisible
characters.
"""
delimiters = re.compile(r"([{}]+)".format("".join(map(re.escape, ",.$:/+@!?()<>[]{}"))))

Expand All @@ -103,11 +117,11 @@ def soft_break_delimiter(match: re.Match[str]) -> str:
chunks = delimiters.split(value)
for i, chunk in enumerate(chunks):
if i % 2 == 1: # check if this is this a delimiter
results.extend([chunk, "\u200b"])
results.extend([chunk, delimiter_break])
else:
results.append(process(chunk))

return "".join(results).rstrip("\u200b")
return "".join(results).rstrip(delimiter_break)

return re.sub(rf"\S{{{length},}}", soft_break_delimiter, value)

Expand Down
Loading