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..804d6aeafb 100644 --- a/hypha/apply/funds/templates/funds/revisions_compare.html +++ b/hypha/apply/funds/templates/funds/revisions_compare.html @@ -13,102 +13,123 @@ {% endblock %} {% block content %} -
- -
+
+

{% trans "Changes" %}

- {{ from_revision.timestamp|date:"SHORT_DATETIME_FORMAT" }} + + + {{ from_revision.timestamp|date:'SHORT_DATETIME_FORMAT' }} + + {% if to_revision.id != from_revision.id %} {% trans "↔" %} - {{ to_revision.timestamp|date:"SHORT_DATETIME_FORMAT" }} + + + {{ to_revision.timestamp|date:'SHORT_DATETIME_FORMAT' }} + + {% endif %}
+ -
-
- {% for diff in required_fields %} - {% if forloop.first %} -
-

{% trans "Title" %}

-
{{ diff }}
-
- {% elif forloop.counter == 2 %} -
-

{% trans "Legal Name" %}

-
{{ diff }}
-
- {% elif forloop.counter == 3 %} -
-

{% trans "E-mail" %}

-
{{ diff }}
-
- {% elif forloop.counter == 4 %} -
-

{% trans "Address" %}

-
{{ diff }}
-
- {% elif forloop.counter == 5 %} -
-

{% trans "Project Duration" %}

-
{{ diff }}
-
- {% elif forloop.counter == 6 %} -
-

{% trans "Requested Funding" %}

-
{{ diff }}
-
- {% elif forloop.counter == 7 %} -
-

{% trans "Organization" %}

-
{{ diff }}
-
- {% else %} -
-
{{ diff }}
-
- {% endif %} - {% endfor %} - {% for diff in stream_fields %} - {{ diff }} - {% endfor %} -
-
- + {% 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 }}
+