From 0aba09cae61b279a0559df374b33a93d4568592e Mon Sep 17 00:00:00 2001 From: Wes Appler Date: Mon, 11 Aug 2025 15:20:43 -0400 Subject: [PATCH 1/4] Reverted the revisions compare view to side by side, using difflab --- hypha/apply/funds/differ.py | 66 +++++++++++++++++ .../templates/funds/revisions_compare.html | 74 ++++++++++++++++++- hypha/apply/funds/views/revisions.py | 45 +++++++++-- .../tailwind/components/html-diff.css | 9 ++- 4 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 hypha/apply/funds/differ.py diff --git a/hypha/apply/funds/differ.py b/hypha/apply/funds/differ.py new file mode 100644 index 0000000000..646abfa5a2 --- /dev/null +++ b/hypha/apply/funds/differ.py @@ -0,0 +1,66 @@ +import re +from difflib import SequenceMatcher +from typing import Tuple + +import nh3 +from django.utils.html import format_html +from django.utils.safestring import mark_safe + + +def wrap_deleted(text): + return format_html("{}", mark_safe(text)) + + +def wrap_added(text): + return format_html("{}", mark_safe(text)) + + +def compare(answer_a: str, answer_b: str, should_clean: bool = True) -> Tuple[str, str]: + """Compare two strings, populate diff HTML and insert it, and return a tuple of the given strings. + + Args: + answer_a: + The original string + answer_b: + The string to compare to the original + should_clean: + Optional boolean to determine if the string should be sanitized with NH3 (default=True) + + Returns: + A tuple of the original strings with diff HTML inserted. + """ + + if should_clean: + answer_a = re.sub("(]*>)", r"\1◦ ", answer_a) + answer_b = re.sub("(]*>)", r"\1◦ ", answer_b) + answer_a = nh3.clean(answer_a, tags=set(), attributes={}) + answer_b = nh3.clean(answer_b, tags=set(), attributes={}) + + diff = SequenceMatcher(None, answer_a, answer_b) + from_diff = [] + to_diff = [] + for opcode, a0, a1, b0, b1 in diff.get_opcodes(): + if opcode == "equal": + from_diff.append(mark_safe(diff.a[a0:a1])) + to_diff.append(mark_safe(diff.b[b0:b1])) + elif opcode == "insert": + from_diff.append(mark_safe(diff.a[a0:a1])) + to_diff.append(wrap_added(diff.b[b0:b1])) + elif opcode == "delete": + from_diff.append(wrap_deleted(diff.a[a0:a1])) + to_diff.append(mark_safe(diff.b[b0:b1])) + elif opcode == "replace": + from_diff.append(wrap_deleted(diff.a[a0:a1])) + to_diff.append(wrap_added(diff.b[b0:b1])) + + from_display = "".join(from_diff) + + to_display = "".join(to_diff) + from_display = re.sub("(\\.\n)", r"\1

", from_display) + to_display = re.sub("(\\.\n)", r"\1

", to_display) + from_display = re.sub(r"([◦])", r"
\1", from_display) + to_display = re.sub(r"([◦])", r"
\1", to_display) + from_display = mark_safe(from_display) + to_display = mark_safe(to_display) + + return (from_display, to_display) diff --git a/hypha/apply/funds/templates/funds/revisions_compare.html b/hypha/apply/funds/templates/funds/revisions_compare.html index 4667487b27..cda3630739 100644 --- a/hypha/apply/funds/templates/funds/revisions_compare.html +++ b/hypha/apply/funds/templates/funds/revisions_compare.html @@ -13,9 +13,9 @@ {% endblock %} {% block content %} -
+
-
+ {% comment %}

{% trans "Changes" %}

@@ -77,7 +77,75 @@

{% t {% endfor %}

-
+
{% endcomment %} +
+
+

{% trans "Changes" %}

+
+ + + {{ to_revision.timestamp|date:'SHORT_DATETIME_FORMAT' }} + + + + {% if to_revision.id != from_revision.id %} + {% trans "↔" %} + + + {{ to_revision.timestamp|date:'SHORT_DATETIME_FORMAT' }} + + + {% endif %} +
+
+ + + {% for from_field, to_field in required_fields %} + {% if forloop.first %} + + + + + {% elif forloop.counter == 2 %} + + + + + {% elif forloop.counter == 3 %} + + + + + {% elif forloop.counter == 4 %} + + + + + {% elif forloop.counter == 5 %} + + + + + {% elif forloop.counter == 6 %} + + + + + {% else %} + + + + + {% endif %} + {% endfor %} + {% for from_field, to_field in stream_fields %} + + + + + {% endfor %} +

{% trans "Title" %}

{{ from_field }}

{% trans "Title" %}

{{ to_field }}

{% trans "Legal Name" %}

{{ from_field }}

{% trans "Legal Name" %}

{{ to_field }}

{% trans "E-mail" %}

{{ from_field }}

{% trans "E-mail" %}

{{ to_field }}

{% trans "Address" %}

{{ from_field }}

{% trans "Address" %}

{{ to_field }}

{% trans "Project Duration" %}

{{ from_field }}

{% trans "Project Duration" %}

{{ to_field }}

{% trans "Requested Funding" %}

{{ from_field }}

{% trans "Requested Funding" %}

{{ to_field }}
{{ from_field }}{{ to_field }}
{{ from_field }}{{ to_field }}
+