-
Notifications
You must be signed in to change notification settings - Fork 975
Add sharing current page menu items (Email, Twitter, Facebook...) #8517
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* This SourceCode Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
* You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
'use strict' | ||
|
||
const appConstants = require('../../../js/constants/appConstants') | ||
const {makeImmutable} = require('../../common/state/immutableUtil') | ||
const {simpleShareActiveTab} = require('../share') | ||
|
||
const shareReducer = (state, action) => { | ||
action = makeImmutable(action) | ||
switch (action.get('actionType')) { | ||
case appConstants.APP_SIMPLE_SHARE_ACTIVE_TAB_REQUESTED: | ||
state = simpleShareActiveTab(state, action.get('windowId'), action.get('shareType')) | ||
break | ||
} | ||
return state | ||
} | ||
|
||
module.exports = shareReducer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
* You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
const assert = require('assert') | ||
const tabState = require('../common/state/tabState') | ||
const {shell} = require('electron') | ||
const tabs = require('./tabs') | ||
|
||
const templateUrls = { | ||
email: 'mailto:?subject={title}&body={url}', | ||
facebook: 'https://www.facebook.com/sharer.php?u={url}', | ||
pinterest: 'https://pinterest.com/pin/create/bookmarklet/?url={url}&description={title}', | ||
twitter: 'https://twitter.com/intent/tweet?url={url}&text={title}&hashtags=brave', | ||
googlePlus: 'https://plus.google.com/share?url={url}', | ||
linkedIn: 'https://www.linkedin.com/shareArticle?url={url}&title={title}', | ||
buffer: 'https://buffer.com/add?text={title}&url={url}', | ||
digg: 'http://digg.com/submit?url={url}&title={title}', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does digg not offer an https URL? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's an oversight but we aren't using it because Brad asked not to, I'll change it in case we do use it in the future though. |
||
reddit: 'https://reddit.com/submit?url={url}&title={title}' | ||
} | ||
|
||
const validateShareType = (shareType) => | ||
assert(templateUrls[shareType], 'The specified shareType is not recognized') | ||
|
||
/** | ||
* Performs a simple share operation for the active tab in the specified window. | ||
* | ||
* @param {number} windowId - The window for the active tab to use | ||
* @param {string} shareType - The template to use, see the property key names in templateUrls above. | ||
*/ | ||
const simpleShareActiveTab = (state, windowId, shareType) => { | ||
const tabValue = tabState.getActiveTabValue(state, windowId) | ||
const encodedTitle = encodeURIComponent(tabValue.get('title') || '') | ||
const encodedUrl = encodeURIComponent(tabValue.get('url')) | ||
|
||
validateShareType(shareType) | ||
const templateUrl = templateUrls[shareType] | ||
|
||
const url = templateUrl | ||
.replace('{url}', encodedUrl) | ||
.replace('{title}', encodedTitle) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ++ on the encodeURLComponent calls 😄 |
||
if (shareType === 'email') { | ||
shell.openExternal(url) | ||
} else { | ||
tabs.create({ | ||
url | ||
}) | ||
} | ||
return state | ||
} | ||
|
||
module.exports = { | ||
simpleShareActiveTab, | ||
templateUrls | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,6 +80,14 @@ newSessionTab=New Session Tab | |
newWindow=New Window | ||
reopenLastClosedTab=Reopen Last Closed Tab | ||
print=Print… | ||
emailPageLink=Email Page Link… | ||
tweetPageLink=Tweet Page… | ||
facebookPageLink=Share on Facebook… | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these would make more sense as the generic template, like "Share on {{siteName}}" and the the l10n library could have the service name input as args. This would prevent translators from possibly changing the usage of the name / brand / platform for the share There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I posted a follow up task marked as good-first-bug for this review comment: |
||
pinterestPageLink=Share on Pinterest… | ||
googlePlusPageLink=Share on Google+… | ||
linkedInPageLink=Share on LinkedIn… | ||
bufferPageLink=Share on Buffer… | ||
redditPageLink=Share on Reddit… | ||
findOnPage=Find on Page… | ||
find=Find… | ||
checkForUpdates=Check for Updates… | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* global describe, it, before, after */ | ||
const mockery = require('mockery') | ||
const sinon = require('sinon') | ||
const Immutable = require('immutable') | ||
const assert = require('assert') | ||
const fakeElectron = require('../../../lib/fakeElectron') | ||
|
||
const appConstants = require('../../../../../js/constants/appConstants') | ||
require('../../../braveUnit') | ||
|
||
describe('shareReducer', function () { | ||
let shareReducer | ||
before(function () { | ||
mockery.enable({ | ||
warnOnReplace: false, | ||
warnOnUnregistered: false, | ||
useCleanCache: true | ||
}) | ||
mockery.registerMock('electron', fakeElectron) | ||
this.shareStub = { | ||
simpleShareActiveTab: sinon.stub() | ||
} | ||
mockery.registerMock('../share', this.shareStub) | ||
shareReducer = require('../../../../../app/browser/reducers/shareReducer') | ||
}) | ||
|
||
after(function () { | ||
mockery.disable() | ||
}) | ||
|
||
describe('APP_SIMPLE_SHARE_ACTIVE_TAB_REQUESTED', function () { | ||
before(function () { | ||
this.state = Immutable.Map() | ||
this.windowId = 2 | ||
this.shareType = 'email' | ||
this.newState = shareReducer(this.state, {actionType: appConstants.APP_SIMPLE_SHARE_ACTIVE_TAB_REQUESTED, windowId: this.windowId, shareType: this.shareType}) | ||
}) | ||
it('calls simpleShareActiveTab once with the correct args', function () { | ||
const callCount = this.shareStub.simpleShareActiveTab.withArgs(this.state, this.windowId, this.shareType).callCount | ||
assert.equal(callCount, 1) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* global describe, it, before, after, afterEach */ | ||
const mockery = require('mockery') | ||
const sinon = require('sinon') | ||
const Immutable = require('immutable') | ||
const assert = require('assert') | ||
const fakeElectron = require('../../lib/fakeElectron') | ||
|
||
require('../../braveUnit') | ||
|
||
describe('share API', function () { | ||
let share | ||
before(function () { | ||
mockery.enable({ | ||
warnOnReplace: false, | ||
warnOnUnregistered: false, | ||
useCleanCache: true | ||
}) | ||
mockery.registerMock('electron', fakeElectron) | ||
|
||
this.mockTabs = { | ||
create: sinon.stub() | ||
} | ||
mockery.registerMock('./tabs', this.mockTabs) | ||
share = require('../../../../app/browser/share') | ||
}) | ||
|
||
after(function () { | ||
mockery.disable() | ||
}) | ||
|
||
describe('simpleShareActiveTab', function () { | ||
before(function () { | ||
this.state = Immutable.fromJS({ | ||
tabs: [{ | ||
windowId: 2, | ||
tabId: 2, | ||
url: 'https://www.brave.com/2', | ||
title: 'title 2', | ||
active: true | ||
}, { | ||
windowId: 3, | ||
tabId: 3, | ||
url: 'https://www.brave.com/3', | ||
title: 'title 3' | ||
}, { | ||
windowId: 5, | ||
tabId: 5, | ||
url: 'https://www.brave.com/5', | ||
title: 'title 5', | ||
active: true | ||
}] | ||
}) | ||
this.windowId = 2 | ||
this.openExternalSpy = sinon.spy(fakeElectron.shell, 'openExternal') | ||
}) | ||
afterEach(function () { | ||
this.openExternalSpy.reset() | ||
this.mockTabs.create.reset() | ||
}) | ||
describe('email', function () { | ||
it('calls openExternal with the correct args', function () { | ||
share.simpleShareActiveTab(this.state, 2, 'email') | ||
const expectedUrl = 'mailto:?subject=title%202&body=https%3A%2F%2Fwww.brave.com%2F2' | ||
const callCount = this.openExternalSpy.withArgs(expectedUrl).callCount | ||
assert.equal(callCount, 1) | ||
assert.equal(this.mockTabs.create.withArgs(expectedUrl).callCount, 0) | ||
}) | ||
it('takes active tab windowId into consideration', function () { | ||
share.simpleShareActiveTab(this.state, 5, 'email') | ||
const expectedUrl = 'mailto:?subject=title%205&body=https%3A%2F%2Fwww.brave.com%2F5' | ||
const callCount = this.openExternalSpy.withArgs(expectedUrl).callCount | ||
assert.equal(callCount, 1) | ||
assert.equal(this.mockTabs.create.withArgs(expectedUrl).callCount, 0) | ||
}) | ||
}) | ||
describe('other', function () { | ||
it('calls createTabRequested with correct args', function () { | ||
Object.entries(share.templateUrls).forEach(([shareType, template]) => { | ||
if (shareType === 'email') { | ||
return | ||
} | ||
share.simpleShareActiveTab(this.state, 5, shareType) | ||
const expectedUrl = template | ||
.replace('{url}', 'https%3A%2F%2Fwww.brave.com%2F5') | ||
.replace('{title}', 'title%205') | ||
assert.equal(this.openExternalSpy.withArgs({url: expectedUrl}).callCount, 0) | ||
assert.equal(this.mockTabs.create.callCount, 1) | ||
assert.equal(this.mockTabs.create.args[0][0].url, expectedUrl) | ||
this.openExternalSpy.reset() | ||
this.mockTabs.create.reset() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Food for thought on this one- what other types of sharing do we think we'll have? Could this possibly handle things like
Submit Feedback
in the Help Menu, etc?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Submit feedback would make sense to be moved into here. In the future I was thinking new tab stats sharing from the new tab page would go into here as well.