Skip to content

Commit

Permalink
Allow users to select shown contact details
Browse files Browse the repository at this point in the history
  • Loading branch information
charludo committed Jan 11, 2025
1 parent 5ab3a33 commit a2dc458
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 55 deletions.
28 changes: 28 additions & 0 deletions integreat_cms/cms/models/contact/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,34 @@ def referring_objects(self) -> Generator[AbstractContentTranslation]:
for link in Link.objects.filter(url__url=self.absolute_url)
)

@cached_property
def available_details(self) -> dict:
"""
Returns the available details and their human-readable representation
:return: key-value pairs of available detail and human-readable representation
"""
details = {
"address": _("show address"),
}

if self.point_of_contact_for:
details["point_of_contact_for"] = _("show point of contact")

if self.name:
details["name"] = _("show name")

if self.email:
details["email"] = _("show email")

if self.phone_number:
details["phone_number"] = _("show phone number")

if self.website:
details["website"] = _("show website")

return details

def archive(self) -> None:
"""
Archives the contact
Expand Down
80 changes: 41 additions & 39 deletions integreat_cms/cms/templates/contacts/contact_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,56 @@
{% spaceless %}
<div contenteditable="false"
data-contact-id="{{ contact.pk }}"
data-contact-url="{{ contact.absolute_url }}"
data-contact-url="{{ contact.absolute_url }}?details={{ wanted|join:"," }}"
class="contact-card notranslate"
dir="ltr"
translate="no">
{# djlint:off H021 #}
<a href="{{ contact.absolute_url }}" style="display: none">Contact</a>
<a href="{{ contact.absolute_url }}?details={{ wanted|join:"," }}"
style="display: none">Contact</a>
{# djlint:on #}
{% if contact %}
{% if contact.name or contact.point_of_contact_for %}
<h4>
{% if contact.name %}
{{ contact.name }}
{% endif %}
{% if contact.name and contact.point_of_contact_for %}
|
{% endif %}
{% if contact.point_of_contact_for %}
{{ contact.point_of_contact_for }}
{% endif %}
</h4>
{% endif %}
{# "and" takes precedent: https://docs.djangoproject.com/en/4.2/ref/templates/builtins/#boolean-operators #}
{% if contact.name and "name" in wanted or contact.point_of_contact_for and "point_of_contact_for" in wanted %}
<h4>
{% if contact.name and "name" in wanted %}
{{ contact.name }}
{% endif %}
{% if contact.name and "name" in wanted and contact.point_of_contact_for and "point_of_contact_for" in wanted %}
|
{% endif %}
{% if contact.point_of_contact_for and "point_of_contact_for" in wanted %}
{{ contact.point_of_contact_for }}
{% endif %}
</h4>
{% endif %}
{% if "address" in wanted %}
<p>
<img src="{% get_svg_icon "pin" %}" alt="Address: " width="15" height="15" />
&nbsp; <a href="{{ contact.location.map_url }}">{{ contact.location.short_address }}</a>
</p>
{% if contact.email %}
<p>
<img src="{% get_svg_icon "email" %}" alt="Email: " width="15" height="15" />
&nbsp;
<a href="mailto:{{ contact.email }}">{{ contact.email }}</a>
</p>
{% endif %}
{% if contact.phone_number %}
<p>
<img src="{% get_svg_icon "call" %}"
alt="Phone Number: "
width="15"
height="15" />
&nbsp; <a href="tel:{{ contact.phone_number }}">{{ contact.phone_number }}</a>
</p>
{% endif %}
{% if contact.website %}
<p>
<img src="{% get_svg_icon "www" %}" alt="Website: " width="15" height="15" />
&nbsp;
<a href="{{ contact.website }}">{{ contact.website }}</a>
</p>
{% endif %}
{% endif %}
{% if contact.email and "email" in wanted %}
<p>
<img src="{% get_svg_icon "email" %}" alt="Email: " width="15" height="15" />
&nbsp;
<a href="mailto:{{ contact.email }}">{{ contact.email }}</a>
</p>
{% endif %}
{% if contact.phone_number and "phone_number" in wanted %}
<p>
<img src="{% get_svg_icon "call" %}"
alt="Phone Number: "
width="15"
height="15" />
&nbsp; <a href="tel:{{ contact.phone_number }}">{{ contact.phone_number }}</a>
</p>
{% endif %}
{% if contact.website and "website" in wanted %}
<p>
<img src="{% get_svg_icon "www" %}" alt="Website: " width="15" height="15" />
&nbsp;
<a href="{{ contact.website }}">{{ contact.website }}</a>
</p>
{% endif %}
</div>
{% endspaceless %}
16 changes: 13 additions & 3 deletions integreat_cms/cms/utils/content_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,18 @@ def update_internal_links(link: HtmlElement, language_slug: str) -> None:
link.append(new_html)


def render_contact_card(contact_id: int) -> HtmlElement:
def render_contact_card(contact_id: int, wanted_details: list[str]) -> HtmlElement:
"""
Produces a rendered html element for the contact.
:param contact_id: The id of the contact to render the card for
:param wanted_details: list of details to be shown in the rendered card
"""
template = loader.get_template("contacts/contact_card.html")
try:
context = {
"contact": Contact.objects.get(pk=contact_id),
"wanted": wanted_details,
}
raw_element = template.render(context)
return fromstring(raw_element)
Expand All @@ -175,9 +177,17 @@ def update_contacts(content: HtmlElement) -> None:
"""
contact_cards = content.xpath("//div[@data-contact-id]")
contact_ids = [int(card.get("data-contact-id")) for card in contact_cards]
contact_urls = [card.get("data-contact-url") for card in contact_cards]

for contact_id, contact_card in zip(contact_ids, contact_cards):
contact_card_new = render_contact_card(contact_id)
for contact_id, contact_url, contact_card in zip(
contact_ids, contact_urls, contact_cards
):
try:
wanted_details = contact_url.split("details=", 1)[1].split(",")
except IndexError:
wanted_details = []

contact_card_new = render_contact_card(contact_id, wanted_details)
contact_card.getparent().replace(contact_card, contact_card_new)


Expand Down
19 changes: 16 additions & 3 deletions integreat_cms/cms/views/utils/contact_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import TYPE_CHECKING

from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, JsonResponse
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, render
from django.views.decorators.http import require_POST

Expand Down Expand Up @@ -56,6 +56,7 @@ def search_contact_ajax(
{
"url": result.absolute_url,
"name": result.get_repr_short,
"details": result.available_details,
}
for result in results
]
Expand All @@ -77,7 +78,11 @@ def get_contact(
"""
# pylint: disable=unused-argument
contact = get_object_or_404(Contact, pk=contact_id)
return render(request, "contacts/contact_card.html", {"contact": contact})

wanted = request.GET.get("details", "").split(",")
return render(
request, "contacts/contact_card.html", {"contact": contact, "wanted": wanted}
)


@permission_required("cms.view_contact")
Expand All @@ -95,4 +100,12 @@ def get_contact_raw(
"""
# pylint: disable=unused-argument
contact = get_object_or_404(Contact, pk=contact_id)
return HttpResponse(contact.get_repr_short)
return JsonResponse(
{
"data": {
"url": contact.full_url,
"name": contact.get_repr_short,
"details": contact.available_details,
}
}
)
24 changes: 24 additions & 0 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -2817,6 +2817,30 @@ msgstr "mit Website: {}"
msgid "General contact information"
msgstr "Allgemeine Kontaktinformationen"

#: cms/models/contact/contact.py
msgid "show address"
msgstr "Adresse anzeigen"

#: cms/models/contact/contact.py
msgid "show point of contact"
msgstr "Ansprechperson anzeigen"

#: cms/models/contact/contact.py
msgid "show name"
msgstr "Name anzeigen"

#: cms/models/contact/contact.py
msgid "show email"
msgstr "Email anzeigen"

#: cms/models/contact/contact.py
msgid "show phone number"
msgstr "Telefonnummer anzeigen"

#: cms/models/contact/contact.py
msgid "show website"
msgstr "Webseite anzeigen"

#: cms/models/contact/contact.py
msgid "(Copy)"
msgstr "(Kopie)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,21 @@ import { getCsrfToken } from "../../utils/csrf-token";
return data;
};

const getContactRaw = async (url) => getContactHtml(`${url}raw/`);
const getContactRaw = async (url) => {
const response = await fetch(`${url.split("?")[0]}raw/`, {
method: "GET",
headers: {
"X-CSRFToken": getCsrfToken(),
},
});

if (response.status !== HTTP_STATUS_OK) {
return {};
}

const data = await response.json();
return data.data;
};

tinymce.PluginManager.add("custom_contact_input", (editor, _url) => {
const isContact = (node) => "contactId" in node.dataset;
Expand All @@ -58,7 +72,7 @@ import { getCsrfToken } from "../../utils/csrf-token";

let tomSelectInstance;

const openDialog = (_button, initialSelection) => {
const openDialog = (_button, initialSelection, selectedDetails) => {
const contact = getContact();

const dialogConfig = {
Expand All @@ -72,6 +86,7 @@ import { getCsrfToken } from "../../utils/csrf-token";
// so we have to do with an htmlpanel and initializing TomSelect separately
html: `<select id="completions" name="completions">`,
},
{ type: "htmlpanel", html: `<div id="details-area" class="details-area"></div>` },
],
},
buttons: [
Expand Down Expand Up @@ -99,8 +114,13 @@ import { getCsrfToken } from "../../utils/csrf-token";
if (!url) {
return;
}
const details = Array.from(
document.getElementById("details-area").querySelectorAll('input[type="checkbox"]:checked')
)
.map((cb) => cb.value)
.join(",");
api.close();
getContactHtml(url).then((html) => {
getContactHtml(`${url}?details=${details}`).then((html) => {
if (!contact) {
editor.insertContent(html);
} else {
Expand All @@ -112,6 +132,7 @@ import { getCsrfToken } from "../../utils/csrf-token";

setTimeout(() => {
const selectElement = document.getElementById("completions");
const detailsArea = document.getElementById("details-area");
const submitElement = document.querySelector(
".tox-dialog:has(#completions) .tox-dialog__footer .tox-button:not(.tox-button--secondary)"
);
Expand All @@ -125,9 +146,47 @@ import { getCsrfToken } from "../../utils/csrf-token";
submitElement.focus();
}
};
const updateDetailSelection = (value) => {
detailsArea.innerHTML = "";

// removing the currently selected option calls this function with empty string
if (value === "") {
return;
}

const availableDetails = tomSelectInstance.options[value].details;

for (const [key, value] of Object.entries(availableDetails)) {
const wrapper = document.createElement("div");

const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = selectedDetails?.includes(key) || !selectedDetails;
checkbox.value = key;
checkbox.id = key;
checkbox.style.border = "1px solid";
checkbox.style.width = "1em";
checkbox.style.height = "1em";
checkbox.style.margin = "0 5px 0 5px";
checkbox.style.verticalAlign = "middle";

const label = document.createElement("label");
label.htmlFor = key;
label.textContent = value;
label.style.verticalAlign = "middle";

wrapper.append(checkbox);
wrapper.append(label);
detailsArea.append(wrapper);
}
};

if (initialSelection) {
selectElement.add(initialSelection);
const updatedContactOption = document.createElement("option");
updatedContactOption.value = initialSelection.url;
updatedContactOption.text = initialSelection.name;

selectElement.add(updatedContactOption);
}

tomSelectInstance = new TomSelect(selectElement, {
Expand All @@ -142,11 +201,19 @@ import { getCsrfToken } from "../../utils/csrf-token";
});
},
onDropdownClose: setSubmitDisableStatus,
onChange: updateDetailSelection,
});

selectElement.classList.add("hidden");
tomSelectInstance.control_input.parentElement.classList.add("tox-textfield");
tomSelectInstance.control_input.focus();
if (initialSelection) {
tomSelectInstance.options[initialSelection.url] = initialSelection;
tomSelectInstance.setValue(initialSelection.url);
} else {
// when initial selection is given, do not focus the input,
// in order to avoid an additional click to see the full details selection
tomSelectInstance.control_input.focus();
}
setSubmitDisableStatus(tomSelectInstance.getValue());
}, 0);

Expand All @@ -168,12 +235,9 @@ import { getCsrfToken } from "../../utils/csrf-token";
onAction: async () => {
const contactUrl = getContact().dataset.contactUrl;
const updatedContact = await getContactRaw(contactUrl);
const selectedDetails = contactUrl.split("?details=")[1]?.split(",");

const updatedContactOption = document.createElement("option");
updatedContactOption.value = `${contactUrl}`;
updatedContactOption.text = updatedContact;

openDialog(null, updatedContactOption);
openDialog(null, updatedContact, selectedDetails);
closeContextToolbar();
},
});
Expand Down

0 comments on commit a2dc458

Please sign in to comment.