Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to select shown contact details #3320

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
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) -> List[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
76 changes: 39 additions & 37 deletions integreat_cms/cms/templates/contacts/contact_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,54 @@
{% 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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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
Loading