diff --git a/app/lib/client/defaultTabBars.js b/app/lib/client/defaultTabBars.js index 31bb8f8a04b3..16907b3847f3 100644 --- a/app/lib/client/defaultTabBars.js +++ b/app/lib/client/defaultTabBars.js @@ -1,6 +1,7 @@ import { Session } from 'meteor/session'; -import { TabBar } from '../../ui-utils'; +import { TabBar, popover } from '../../ui-utils'; +import { share, isShareAvailable } from '../../utils'; import { Rooms } from '../../models'; import { hasAllPermission } from '../../authorization'; import { roomTypes } from '../../utils/client'; @@ -86,3 +87,22 @@ TabBar.addButton({ template: 'keyboardShortcuts', order: 99, }); + +// Add Share button in Room +const shareButton = { + groups: ['channel', 'group', 'direct'], + id: 'share', + i18nTitle: 'Share', + icon: 'share', + template: 'share', + order: 500, +}; + +if (isShareAvailable()) { + shareButton.action = () => { + share(); + popover.close(); + }; +} + +TabBar.addButton(shareButton); diff --git a/app/theme/client/imports/components/share.css b/app/theme/client/imports/components/share.css new file mode 100644 index 000000000000..8b1ac94d679f --- /dev/null +++ b/app/theme/client/imports/components/share.css @@ -0,0 +1,56 @@ +.share-header-container { + display: flex; + flex-wrap: wrap; +} + +.share-icon { + display: inline-block; + + box-sizing: border-box; + + width: 25%; + min-width: 46px; + + padding: 16px 0; + + cursor: pointer; + + text-align: center; + + border: none; + + background-color: transparent; + + box-shadow: inset 0 0 20px rgba(0, 0, 0, 0); + + font-size: 12px; + font-weight: 400; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + &:hover { + box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.125); + } +} + +.share-svg-icon { + display: block; + + width: 42px; + height: 36px; + margin: auto; + + &__header { + width: 24px; + height: 24px; + } +} + +.share-icon-title { + display: block; + + padding-top: 10px; + + font-size: 14px; +} diff --git a/app/theme/client/main.css b/app/theme/client/main.css index 0cb526483680..e838fd0abf5d 100644 --- a/app/theme/client/main.css +++ b/app/theme/client/main.css @@ -47,6 +47,7 @@ @import 'imports/components/emojiPicker.css'; @import 'imports/components/table.css'; @import 'imports/components/tabs.css'; +@import 'imports/components/share.css'; /* Modal */ @import 'imports/components/modal/full-modal.css'; diff --git a/app/ui-share/README.md b/app/ui-share/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/app/ui-share/client/index.js b/app/ui-share/client/index.js new file mode 100644 index 000000000000..c9281cc5bff8 --- /dev/null +++ b/app/ui-share/client/index.js @@ -0,0 +1,2 @@ +import './share.html'; +import './share'; diff --git a/app/ui-share/client/share.html b/app/ui-share/client/share.html new file mode 100644 index 000000000000..482e7fc12c8f --- /dev/null +++ b/app/ui-share/client/share.html @@ -0,0 +1,45 @@ + diff --git a/app/ui-share/client/share.js b/app/ui-share/client/share.js new file mode 100644 index 000000000000..9a0b358277e2 --- /dev/null +++ b/app/ui-share/client/share.js @@ -0,0 +1,71 @@ +import { Template } from 'meteor/templating'; + +import { getShareData } from '../../utils'; + +function getShareString() { + const data = getShareData(); + return `${ data.title } \n${ data.url } \n${ data.text }`; +} + +function fallbackCopyTextToClipboard(text) { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; // avoid scrolling to bottom + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + console.error('Unable to copy', err); + } + + document.body.removeChild(textArea); +} + +Template.share.helpers({ + +}); + +Template.share.events({ + 'click [data-type="copy"]'() { + if (!navigator.clipboard) { + fallbackCopyTextToClipboard(getShareString()); + return; + } + navigator.clipboard.writeText(getShareString()); + }, + 'click [data-type="print"]'() { + self.print(); + }, + 'click [data-type="email"]'() { + const { title } = getShareData(); + window.open(`mailto:?subject=${ title }&body=${ getShareString() }`); + }, + 'click [data-type="sms"]'() { + location.href = `sms:?&body=${ getShareString() }`; + }, + + + 'click [data-type="facebook"]'() { + const { url } = getShareData(); + window.open(`https://www.facebook.com/sharer/sharer.php?u=${ encodeURIComponent(url) }`); + }, + 'click [data-type="whatsapp"]'() { + window.open(`https://api.whatsapp.com/send?text=${ encodeURIComponent(getShareString()) }`); + }, + 'click [data-type="twitter"]'() { + const { url } = getShareData(); + window.open(`http://twitter.com/share?text=${ getShareString() }&url=${ url }`); + }, + 'click [data-type="linkedin"]'() { + const { title, url } = getShareData(); + window.open(`https://www.linkedin.com/shareArticle?mini=true&url=${ url }&title=${ title }&summary=${ getShareString() }&source=LinkedIn`); + }, + 'click [data-type="telegram"]'() { + const { url } = getShareData(); + window.open(`https://telegram.me/share/msg?url=${ url }&text=${ getShareString() }`); + }, + +}); diff --git a/app/ui-share/index.js b/app/ui-share/index.js new file mode 100644 index 000000000000..40a7340d3887 --- /dev/null +++ b/app/ui-share/index.js @@ -0,0 +1 @@ +export * from './client/index'; diff --git a/app/ui-sidenav/client/sidebarHeader.js b/app/ui-sidenav/client/sidebarHeader.js index 0d3f95e189e3..1b1d54ab5e30 100644 --- a/app/ui-sidenav/client/sidebarHeader.js +++ b/app/ui-sidenav/client/sidebarHeader.js @@ -165,6 +165,12 @@ const toolbarButtons = (/* user */) => [{ }; } + const shareOption = { + name: t('Share'), + icon: 'share', + type: 'share-action', + }; + const config = { popoverClass: 'sidebar-header', columns: [ @@ -206,6 +212,8 @@ const toolbarButtons = (/* user */) => [{ offsetVertical: e.currentTarget.clientHeight + 10, }; + config.columns[0].groups[0].items = config.columns[0].groups[0].items.concat([shareOption]); + popover.open(config); }, }]; diff --git a/app/ui-utils/client/lib/popover.js b/app/ui-utils/client/lib/popover.js index 2aaaadc799aa..e3365ab5be4d 100644 --- a/app/ui-utils/client/lib/popover.js +++ b/app/ui-utils/client/lib/popover.js @@ -5,6 +5,7 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { Template } from 'meteor/templating'; import _ from 'underscore'; +import { share, isShareAvailable } from '../../../utils'; import { hide, leave } from './ChannelActions'; import { messageBox } from './messageBox'; import { MessageAction } from './MessageAction'; @@ -179,6 +180,23 @@ Template.popover.events({ return false; } }, + 'click [data-type="share-action"]'(e) { + if (isShareAvailable()) { + share(); + } else { + popover.close(); + const options = []; + const config = { + template: 'share', + currentTarget: e.target, + data: { + options, + }, + offsetVertical: e.target.clientHeight + 10, + }; + popover.open(config); + } + }, 'click [data-type="sidebar-item"]'(e, instance) { popover.close(); const { rid, name, template } = instance.data.data; diff --git a/app/utils/client/index.js b/app/utils/client/index.js index 8f536315da1d..d9d60639bc69 100644 --- a/app/utils/client/index.js +++ b/app/utils/client/index.js @@ -21,3 +21,4 @@ export { APIClient, mountArrayQueryParameters } from './lib/RestApiClient'; export { canDeleteMessage } from './lib/canDeleteMessage'; export { mime } from '../lib/mimeTypes'; export { secondsToHHMMSS } from '../lib/timeConverter'; +export { share, isShareAvailable, getShareData } from './lib/share'; diff --git a/app/utils/client/lib/share.js b/app/utils/client/lib/share.js new file mode 100644 index 000000000000..8d1f2319430f --- /dev/null +++ b/app/utils/client/lib/share.js @@ -0,0 +1,59 @@ +import { Meteor } from 'meteor/meteor'; + +import { settings } from '../../../settings'; + + +export const isShareAvailable = () => { + if (navigator.share) { return true; } + return false; +}; + +export const getShareData = () => { + const data = {}; + + const siteName = settings.get('Site_Name') || ''; + const siteURL = settings.get('Site_Url') || ''; + + data.url = document.location.href || siteURL; + const path = new URL(data.url).pathname; + const roomName = path.substring(path.lastIndexOf('/') + 1); + + const templateText = `${ siteName } is open source team communication app.`; + + data.title = `${ siteName }`; + data.text = `${ templateText } Open this link to connect.`; + + if (path.startsWith('/channel')) { + data.title = `Join #${ roomName } on ${ siteName }`; + data.text = `You are invited to channel #${ roomName } on ${ siteName }. ${ data.text }`; + } else if (path.startsWith('/group')) { + data.title = `Join #${ roomName } on ${ siteName }`; + data.text = `You are invited to private group 🔒${ roomName } on ${ siteName }. ${ data.text }`; + } else if (path.startsWith('/direct')) { + data.title = `Chat with @${ roomName } on ${ siteName }`; + } else { + const user = Meteor.user(); + + data.title = `${ siteName }`; + data.text = `${ templateText } Open this link and connect with me.`; + data.url = new URL(document.location.href).origin; + + if (data.url && user) { + data.url = `${ data.url }/direct/${ user.username }`; + } + } + + return data; +}; + +export const share = () => { + const data = getShareData(); + + if (navigator.share) { + navigator.share(data) + .then(() => console.log('Successfully shared')) + .catch((error) => console.log('Error while sharing', error)); + } else { + console.log('Share feature not available'); + } +}; diff --git a/client/importPackages.js b/client/importPackages.js index dd2237e46546..94c6745cad99 100644 --- a/client/importPackages.js +++ b/client/importPackages.js @@ -75,6 +75,7 @@ import '../app/ui-login'; import '../app/ui-master/client'; import '../app/ui-message'; import '../app/ui-sidenav'; +import '../app/ui-share'; import '../app/ui-vrecord/client'; import '../app/videobridge/client'; import '../app/webdav/client';