diff --git a/app/assets/javascripts/discourse/components/invite-panel.js.es6 b/app/assets/javascripts/discourse/components/invite-panel.js.es6 index 8056a92dcd0..8622e97ac58 100644 --- a/app/assets/javascripts/discourse/components/invite-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/invite-panel.js.es6 @@ -1,5 +1,6 @@ import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; +import { computed } from "@ember/object"; import { alias, and, equal } from "@ember/object/computed"; import EmberObject from "@ember/object"; import Component from "@ember/component"; @@ -7,6 +8,7 @@ import { emailValid } from "discourse/lib/utilities"; import Group from "discourse/models/group"; import Invite from "discourse/models/invite"; import { i18n } from "discourse/lib/computed"; +import { getNativeContact } from "discourse/lib/pwa-utils"; export default Component.extend({ tagName: null, @@ -180,6 +182,10 @@ export default Component.extend({ ); }, + showContactPicker: computed(function() { + return this.capabilities.hasContactPicker; + }), + @discourseComputed("emailOrUsername") showCustomMessage(emailOrUsername) { return this.inviteModel === this.currentUser || emailValid(emailOrUsername); @@ -433,6 +439,12 @@ export default Component.extend({ } else { this.set("customMessage", null); } + }, + + searchContact() { + getNativeContact(["email"], false).then(result => { + this.set("emailOrUsername", result[0].email[0]); + }); } } }); diff --git a/app/assets/javascripts/discourse/lib/pwa-utils.js.es6 b/app/assets/javascripts/discourse/lib/pwa-utils.js.es6 index 1e2c901bd65..23746a0cb35 100644 --- a/app/assets/javascripts/discourse/lib/pwa-utils.js.es6 +++ b/app/assets/javascripts/discourse/lib/pwa-utils.js.es6 @@ -26,3 +26,18 @@ export function nativeShare(data) { } }); } + +export function getNativeContact(properties, multiple) { + const caps = Discourse.__container__.lookup("capabilities:main"); + return new Promise((resolve, reject) => { + if (!caps.hasContactPicker) { + reject(); + return; + } + + navigator.contacts + .select(properties, { multiple }) + .then(resolve) + .catch(reject); + }); +} diff --git a/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 b/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 index 0cfc4f2c619..1e111506e8b 100644 --- a/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 +++ b/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 @@ -43,6 +43,9 @@ export default { caps.isIOS = (/iPhone|iPod/.test(navigator.userAgent) || caps.isIpadOS) && !window.MSStream; + + caps.hasContactPicker = + "contacts" in navigator && "ContactsManager" in window; } // We consider high res a device with 1280 horizontal pixels. High DPI tablets like diff --git a/app/assets/javascripts/discourse/templates/components/invite-panel.hbs b/app/assets/javascripts/discourse/templates/components/invite-panel.hbs index c72a0d188d9..a83435600ba 100644 --- a/app/assets/javascripts/discourse/templates/components/invite-panel.hbs +++ b/app/assets/javascripts/discourse/templates/components/invite-panel.hbs @@ -19,25 +19,34 @@ {{else}} <div class="invite-user-control"> <label class="instructions">{{inviteInstructions}}</label> - {{#if allowExistingMembers}} - {{user-selector - fullWidthWrap=true - single=true - allowAny=true - excludeCurrentUser=true - includeMessageableGroups=isPM - hasGroups=hasGroups - usernames=emailOrUsername - placeholderKey=placeholderKey - allowEmails=true - class="invite-user-input" - autocomplete="discourse"}} - {{else}} - {{text-field - class="email-or-username-input" - value=emailOrUsername - placeholderKey="topic.invite_reply.email_placeholder"}} - {{/if}} + <div class="invite-user-input-wrapper"> + {{#if allowExistingMembers}} + {{user-selector + fullWidthWrap=true + single=true + allowAny=true + excludeCurrentUser=true + includeMessageableGroups=isPM + hasGroups=hasGroups + usernames=emailOrUsername + placeholderKey=placeholderKey + allowEmails=true + canReceiveUpdates=(if showContactPicker "true" "false") + class="invite-user-input" + autocomplete="discourse"}} + {{else}} + {{text-field + class="email-or-username-input" + value=emailOrUsername + placeholderKey="topic.invite_reply.email_placeholder"}} + {{/if}} + {{#if showContactPicker}} + {{d-button + icon="address-book" + action=(action "searchContact") + class="btn-primary open-contact-picker"}} + {{/if}} + </div> </div> {{#if showGroups}} diff --git a/app/assets/stylesheets/common/components/share-and-invite-modal.scss b/app/assets/stylesheets/common/components/share-and-invite-modal.scss index f38c0c1eab7..c5177006f90 100644 --- a/app/assets/stylesheets/common/components/share-and-invite-modal.scss +++ b/app/assets/stylesheets/common/components/share-and-invite-modal.scss @@ -85,6 +85,13 @@ .email-or-username-input { width: 100%; } + + .invite-user-input-wrapper { + display: flex; + div.ac-wrap { + flex: 1; + } + } } .footer { diff --git a/lib/svg_sprite/svg_sprite.rb b/lib/svg_sprite/svg_sprite.rb index 35ef39aed05..33d51ca4844 100644 --- a/lib/svg_sprite/svg_sprite.rb +++ b/lib/svg_sprite/svg_sprite.rb @@ -3,6 +3,7 @@ module SvgSprite SVG_ICONS ||= Set.new([ "adjust", + "address-book", "ambulance", "anchor", "angle-double-down",