Add support for phone links in rich text

Add support for phone links in rich text
This commit is contained in:
Kalob Taulien 2019-08-21 19:46:09 -06:00 committed by GitHub
commit 7b2e499af0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 165 additions and 2 deletions

View file

@ -12,6 +12,7 @@ const BROKEN_LINK_ICON = <Icon name="warning" />;
const MAIL_ICON = <Icon name="mail" />;
const getEmailAddress = mailto => mailto.replace('mailto:', '').split('?')[0];
const getPhoneNumber = tel => tel.replace('tel:', '').split('?')[0];
const getDomainName = url => url.replace(/(^\w+:|^)\/\//, '').split('/')[0];
// Determines how to display the link based on its type: page, mail, anchor or external.
@ -29,6 +30,9 @@ export const getLinkAttributes = (data) => {
} else if (url.startsWith('mailto:')) {
icon = MAIL_ICON;
label = getEmailAddress(url);
} else if (url.startsWith('tel:')) {
icon = LINK_ICON;
label = getPhoneNumber(url);
} else if (url.startsWith('#')) {
icon = LINK_ICON;
label = url;

View file

@ -56,6 +56,13 @@ describe('Link', () => {
});
});
it('phone', () => {
expect(getLinkAttributes({ url: 'tel:+46700000000' })).toMatchObject({
url: 'tel:+46700000000',
label: '+46700000000',
});
});
it('anchor', () => {
expect(getLinkAttributes({ url: '#testanchor' })).toMatchObject({
url: '#testanchor',

View file

@ -42,6 +42,7 @@ export const getChooserConfig = (entityType, entity, selectedText) => {
page_type: 'wagtailcore.page',
allow_external_link: true,
allow_email_link: true,
allow_phone_link: true,
allow_anchor_link: true,
link_text: selectedText,
};
@ -58,6 +59,9 @@ export const getChooserConfig = (entityType, entity, selectedText) => {
} else if (data.url.startsWith('mailto:')) {
url = global.chooserUrls.emailLinkChooser;
urlParams.link_url = data.url.replace('mailto:', '');
} else if (data.url.startsWith('tel:')) {
url = global.chooserUrls.phoneLinkChooser;
urlParams.link_url = data.url.replace('tel:', '');
} else if (data.url.startsWith('#')) {
url = global.chooserUrls.anchorLinkChooser;
urlParams.link_url = data.url.replace('#', '');

View file

@ -64,6 +64,7 @@ Object {
"allow_anchor_link": true,
"allow_email_link": true,
"allow_external_link": true,
"allow_phone_link": true,
"link_text": "",
"link_url": "https://www.example.com/",
"page_type": "wagtailcore.page",
@ -81,6 +82,7 @@ Object {
"allow_anchor_link": true,
"allow_email_link": true,
"allow_external_link": true,
"allow_phone_link": true,
"link_text": "",
"link_url": "test@example.com",
"page_type": "wagtailcore.page",
@ -98,6 +100,7 @@ Object {
"allow_anchor_link": true,
"allow_email_link": true,
"allow_external_link": true,
"allow_phone_link": true,
"link_text": "",
"page_type": "wagtailcore.page",
},
@ -114,6 +117,7 @@ Object {
"allow_anchor_link": true,
"allow_email_link": true,
"allow_external_link": true,
"allow_phone_link": true,
"link_text": "",
"page_type": "wagtailcore.page",
},
@ -130,6 +134,7 @@ Object {
"allow_anchor_link": true,
"allow_email_link": true,
"allow_external_link": true,
"allow_phone_link": true,
"link_text": "",
"page_type": "wagtailcore.page",
},

View file

@ -15,6 +15,7 @@ Whichever way you insert a link, you will be presented with the form displayed b
* Internal link: A link to an existing page within your website.
* External link: A link to a page on another website.
* Email link: A link that will open the user's default email client with the email address prepopulated.
* Phone link: A link that will open the user's default client for initiating audio calls, with the phone number prepopulated.
* You can also navigate through the website to find an internal link via the explorer.

View file

@ -39,3 +39,8 @@ class AnchorLinkChooserForm(forms.Form):
class EmailLinkChooserForm(forms.Form):
email_address = forms.EmailField(required=True)
link_text = forms.CharField(required=False)
class PhoneLinkChooserForm(forms.Form):
phone_number = forms.CharField(required=True)
link_text = forms.CharField(required=False)

View file

@ -38,6 +38,7 @@
urlParams = {
'allow_external_link': true,
'allow_email_link': true,
'allow_phone_link': true,
'allow_anchor_link': true,
};
@ -57,6 +58,10 @@
url = window.chooserUrls.emailLinkChooser;
href = href.replace('mailto:', '');
urlParams['link_url'] = href;
} else if (href.startsWith('tel:')) {
url = window.chooserUrls.phoneLinkChooser;
href = href.replace('tel:', '');
urlParams['link_url'] = href;
} else if (href.startsWith('#')) {
url = window.chooserUrls.anchorLinkChooser;
href = href.replace('#', '');

View file

@ -133,6 +133,17 @@ PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = {
return false;
});
},
'phone_link': function(modal, jsonData) {
$('p.link-types a', modal.body).on('click', function() {
modal.loadUrl(this.href);
return false;
});
$('form', modal.body).on('submit', function() {
modal.postForm(this.action, $(this).serialize());
return false;
});
},
'external_link': function(modal, jsonData) {
$('p.link-types a', modal.body).on('click', function() {
modal.loadUrl(this.href);

View file

@ -1,5 +1,5 @@
{% load i18n wagtailadmin_tags %}
{% if allow_external_link or allow_email_link or allow_anchor_link or current == 'external' or current == 'email' or current == 'anchor' %}
{% if allow_external_link or allow_email_link or allow_phone_link or allow_anchor_link or current == 'external' or current == 'email' or current == 'phone' or current == 'anchor' %}
<p class="link-types">
{% if current == 'internal' %}
<b>{% trans "Internal link" %}</b>
@ -23,6 +23,12 @@
| <a href="{% url 'wagtailadmin_choose_page_email_link' %}{% querystring p=None parent_page_id=parent_page_id %}">{% trans "Email link" %}</a>
{% endif %}
{% if current == 'phone' %}
| <b>{% trans "Phone link" %}</b>
{% elif allow_phone_link %}
| <a href="{% url 'wagtailadmin_choose_page_phone_link' %}{% querystring p=None parent_page_id=parent_page_id %}">{% trans "Phone link" %}</a>
{% endif %}
{% if current == 'anchor' %}
| <b>{% trans "Anchor link" %}</b>
{% elif allow_anchor_link %}

View file

@ -0,0 +1,17 @@
{% load i18n wagtailadmin_tags %}
{% trans "Add a phone link" as phone_str %}
{% include "wagtailadmin/shared/header.html" with title=phone_str %}
<div class="nice-padding">
{% include 'wagtailadmin/chooser/_link_types.html' with current='phone' %}
<form action="{% url 'wagtailadmin_choose_page_phone_link' %}{% querystring %}" method="post" novalidate>
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% include "wagtailadmin/shared/field_as_li.html" %}
{% endfor %}
<li><input type="submit" value="{% trans 'Insert link' %}" class="button" /></li>
</ul>
</form>
</div>

View file

@ -10,6 +10,7 @@
'pageChooser': '{% url "wagtailadmin_choose_page" %}',
'externalLinkChooser': '{% url "wagtailadmin_choose_page_external_link" %}',
'emailLinkChooser': '{% url "wagtailadmin_choose_page_email_link" %}',
'phoneLinkChooser': '{% url "wagtailadmin_choose_page_phone_link" %}',
'anchorLinkChooser': '{% url "wagtailadmin_choose_page_anchor_link" %}',
};
window.unicodeSlugsEnabled = {% if unicode_slugs_enabled %}true{% else %}false{% endif %};

View file

@ -692,6 +692,66 @@ class TestChooserEmailLink(TestCase, WagtailTestUtils):
self.assertEqual(result['prefer_this_title_as_link_text'], True)
class TestChooserPhoneLink(TestCase, WagtailTestUtils):
def setUp(self):
self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailadmin_choose_page_phone_link'), params)
def post(self, post_data={}, url_params={}):
url = reverse('wagtailadmin_choose_page_phone_link')
if url_params:
url += '?' + urlencode(url_params)
return self.client.post(url, post_data)
def test_simple(self):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailadmin/chooser/phone_link.html')
def test_prepopulated_form(self):
response = self.get({'link_text': 'Example', 'link_url': '+123456789'})
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Example')
self.assertContains(response, '+123456789')
def test_create_link(self):
response = self.post({'phone-link-chooser-phone_number': '+123456789', 'phone-link-chooser-link_text': 'call'})
result = json.loads(response.content.decode())['result']
self.assertEqual(result['url'], "tel:+123456789")
self.assertEqual(result['title'], "call")
self.assertEqual(result['prefer_this_title_as_link_text'], True)
def test_create_link_without_text(self):
response = self.post({'phone-link-chooser-phone_number': '+123456789'})
result = json.loads(response.content.decode())['result']
self.assertEqual(result['url'], "tel:+123456789")
self.assertEqual(result['title'], "+123456789") # When no link text is given, it uses the phone number
self.assertEqual(result['prefer_this_title_as_link_text'], False)
def test_notice_changes_to_link_text(self):
response = self.post(
{'phone-link-chooser-phone_number': '+222222222', 'phone-link-chooser-link_text': 'example'}, # POST data
{'link_url': '+111111111', 'link_text': 'example'} # GET params - initial data
)
result = json.loads(response.content.decode())['result']
self.assertEqual(result['url'], "tel:+222222222")
self.assertEqual(result['title'], "example")
# no change to link text, so prefer the existing link/selection content where available
self.assertEqual(result['prefer_this_title_as_link_text'], False)
response = self.post(
{'phone-link-chooser-phone_number': '+222222222', 'phone-link-chooser-link_text': 'new example'}, # POST data
{'link_url': '+111111111', 'link_text': 'example'} # GET params - initial data
)
result = json.loads(response.content.decode())['result']
self.assertEqual(result['url'], "tel:+222222222")
self.assertEqual(result['title'], "new example")
# link text has changed, so tell the caller to use it
self.assertEqual(result['prefer_this_title_as_link_text'], True)
class TestCanChoosePage(TestCase, WagtailTestUtils):
fixtures = ['test.json']

View file

@ -37,6 +37,7 @@ urlpatterns = [
url(r'^choose-page/search/$', chooser.search, name='wagtailadmin_choose_page_search'),
url(r'^choose-external-link/$', chooser.external_link, name='wagtailadmin_choose_page_external_link'),
url(r'^choose-email-link/$', chooser.email_link, name='wagtailadmin_choose_page_email_link'),
url(r'^choose-phone-link/$', chooser.phone_link, name='wagtailadmin_choose_page_phone_link'),
url(r'^choose-anchor-link/$', chooser.anchor_link, name='wagtailadmin_choose_page_anchor_link'),
url(r'^tag-autocomplete/$', tags.autocomplete, name='wagtailadmin_tag_autocomplete'),

View file

@ -3,7 +3,8 @@ from django.http import Http404
from django.shortcuts import get_object_or_404, render
from wagtail.admin.forms.choosers import (
AnchorLinkChooserForm, EmailLinkChooserForm, ExternalLinkChooserForm)
AnchorLinkChooserForm, EmailLinkChooserForm, ExternalLinkChooserForm, PhoneLinkChooserForm)
from wagtail.admin.forms.search import SearchForm
from wagtail.admin.modal_workflow import render_modal_workflow
from wagtail.core import hooks
@ -20,6 +21,7 @@ def shared_context(request, extra_context=None):
'parent_page_id': request.GET.get('parent_page_id'),
'allow_external_link': request.GET.get('allow_external_link'),
'allow_email_link': request.GET.get('allow_email_link'),
'allow_phone_link': request.GET.get('allow_phone_link'),
'allow_anchor_link': request.GET.get('allow_anchor_link'),
}
if extra_context:
@ -287,3 +289,37 @@ def email_link(request):
'form': form,
}), json_data={'step': 'email_link'}
)
def phone_link(request):
initial_data = {
'link_text': request.GET.get('link_text', ''),
'phone_number': request.GET.get('link_url', ''),
}
if request.method == 'POST':
form = PhoneLinkChooserForm(request.POST, initial=initial_data, prefix='phone-link-chooser')
if form.is_valid():
result = {
'url': 'tel:' + form.cleaned_data['phone_number'],
'title': form.cleaned_data['link_text'].strip() or form.cleaned_data['phone_number'],
# If the user has explicitly entered / edited something in the link_text field,
# always use that text. If not, we should favour keeping the existing link/selection
# text, where applicable.
'prefer_this_title_as_link_text': ('link_text' in form.changed_data),
}
return render_modal_workflow(
request, None, None,
None, json_data={'step': 'external_link_chosen', 'result': result}
)
else:
form = PhoneLinkChooserForm(initial=initial_data, prefix='phone-link-chooser')
return render_modal_workflow(
request,
'wagtailadmin/chooser/phone_link.html', None,
shared_context(request, {
'form': form,
}), json_data={'step': 'phone_link'}
)