diff --git a/client/src/components/Draftail/decorators/Link.js b/client/src/components/Draftail/decorators/Link.js
index c605fda4a..9e22bb5ad 100644
--- a/client/src/components/Draftail/decorators/Link.js
+++ b/client/src/components/Draftail/decorators/Link.js
@@ -12,6 +12,7 @@ const BROKEN_LINK_ICON = ;
const MAIL_ICON = ;
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;
diff --git a/client/src/components/Draftail/decorators/Link.test.js b/client/src/components/Draftail/decorators/Link.test.js
index 94df8a73e..3fceaff4b 100644
--- a/client/src/components/Draftail/decorators/Link.test.js
+++ b/client/src/components/Draftail/decorators/Link.test.js
@@ -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',
diff --git a/client/src/components/Draftail/sources/ModalWorkflowSource.js b/client/src/components/Draftail/sources/ModalWorkflowSource.js
index 18c2902a2..e0ec1c49e 100644
--- a/client/src/components/Draftail/sources/ModalWorkflowSource.js
+++ b/client/src/components/Draftail/sources/ModalWorkflowSource.js
@@ -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('#', '');
diff --git a/client/src/components/Draftail/sources/__snapshots__/ModalWorkflowSource.test.js.snap b/client/src/components/Draftail/sources/__snapshots__/ModalWorkflowSource.test.js.snap
index 3ec8f54ca..7849a6c52 100644
--- a/client/src/components/Draftail/sources/__snapshots__/ModalWorkflowSource.test.js.snap
+++ b/client/src/components/Draftail/sources/__snapshots__/ModalWorkflowSource.test.js.snap
@@ -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",
},
diff --git a/docs/editor_manual/new_pages/inserting_links.rst b/docs/editor_manual/new_pages/inserting_links.rst
index 45e5fdedd..7eb9f2598 100644
--- a/docs/editor_manual/new_pages/inserting_links.rst
+++ b/docs/editor_manual/new_pages/inserting_links.rst
@@ -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.
diff --git a/wagtail/admin/forms/choosers.py b/wagtail/admin/forms/choosers.py
index a7972dcab..76776a4d3 100644
--- a/wagtail/admin/forms/choosers.py
+++ b/wagtail/admin/forms/choosers.py
@@ -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)
diff --git a/wagtail/admin/static_src/wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js b/wagtail/admin/static_src/wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js
index 76a8be65b..11438d8ed 100644
--- a/wagtail/admin/static_src/wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js
+++ b/wagtail/admin/static_src/wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js
@@ -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('#', '');
diff --git a/wagtail/admin/static_src/wagtailadmin/js/page-chooser-modal.js b/wagtail/admin/static_src/wagtailadmin/js/page-chooser-modal.js
index 92c69c1f2..ce30a4f66 100644
--- a/wagtail/admin/static_src/wagtailadmin/js/page-chooser-modal.js
+++ b/wagtail/admin/static_src/wagtailadmin/js/page-chooser-modal.js
@@ -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);
diff --git a/wagtail/admin/templates/wagtailadmin/chooser/_link_types.html b/wagtail/admin/templates/wagtailadmin/chooser/_link_types.html
index 8d7c8ab63..500789aeb 100644
--- a/wagtail/admin/templates/wagtailadmin/chooser/_link_types.html
+++ b/wagtail/admin/templates/wagtailadmin/chooser/_link_types.html
@@ -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' %}
{% if current == 'internal' %}
{% trans "Internal link" %}
@@ -23,6 +23,12 @@
| {% trans "Email link" %}
{% endif %}
+ {% if current == 'phone' %}
+ | {% trans "Phone link" %}
+ {% elif allow_phone_link %}
+ | {% trans "Phone link" %}
+ {% endif %}
+
{% if current == 'anchor' %}
| {% trans "Anchor link" %}
{% elif allow_anchor_link %}
diff --git a/wagtail/admin/templates/wagtailadmin/chooser/phone_link.html b/wagtail/admin/templates/wagtailadmin/chooser/phone_link.html
new file mode 100644
index 000000000..1db1e3cdd
--- /dev/null
+++ b/wagtail/admin/templates/wagtailadmin/chooser/phone_link.html
@@ -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 %}
+
+
+ {% include 'wagtailadmin/chooser/_link_types.html' with current='phone' %}
+
+
+
diff --git a/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html b/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html
index f8b3dde0b..6e5f851d8 100644
--- a/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html
+++ b/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html
@@ -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 %};
diff --git a/wagtail/admin/tests/test_page_chooser.py b/wagtail/admin/tests/test_page_chooser.py
index ee2cd1d55..d631263e5 100644
--- a/wagtail/admin/tests/test_page_chooser.py
+++ b/wagtail/admin/tests/test_page_chooser.py
@@ -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']
diff --git a/wagtail/admin/urls/__init__.py b/wagtail/admin/urls/__init__.py
index dd7aa1af0..fb8b388f9 100644
--- a/wagtail/admin/urls/__init__.py
+++ b/wagtail/admin/urls/__init__.py
@@ -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'),
diff --git a/wagtail/admin/views/chooser.py b/wagtail/admin/views/chooser.py
index 94bfa4242..0123766bf 100644
--- a/wagtail/admin/views/chooser.py
+++ b/wagtail/admin/views/chooser.py
@@ -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'}
+ )