Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 4ef6a2e

Browse files
authored
Merge pull request #350 from atom/confirm-when-joining-via-external-app
Join portal via URL (Part 3 of 3)
2 parents 21a4480 + a52865b commit 4ef6a2e

File tree

8 files changed

+311
-34
lines changed

8 files changed

+311
-34
lines changed

index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
const TeletypePackage = require('./lib/teletype-package')
22
module.exports = new TeletypePackage({
3+
config: atom.config,
34
workspace: atom.workspace,
45
notificationManager: atom.notifications,
56
packageManager: atom.packages,
67
commandRegistry: atom.commands,
78
tooltipManager: atom.tooltips,
89
clipboard: atom.clipboard,
9-
pusherKey: atom.config.get('teletype.pusherKey'),
10+
pusherKey: atom.config.get('teletype.dev.pusherKey'),
1011
pusherOptions: {
11-
cluster: atom.config.get('teletype.pusherCluster'),
12+
cluster: atom.config.get('teletype.dev.pusherCluster'),
1213
disableStats: true
1314
},
14-
baseURL: atom.config.get('teletype.baseURL'),
15+
baseURL: atom.config.get('teletype.dev.baseURL'),
1516
getAtomVersion: atom.getVersion.bind(atom)
1617
})

lib/host-portal-binding-component.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class HostPortalBindingComponent {
6969
),
7070
$.div({className: 'HostPortalComponent-connection-info-portal-url ' + statusClassName},
7171
$.input({className: 'input-text host-id-input', type: 'text', disabled: true, value: this.getPortalURI()}),
72-
$.button({className: 'btn btn-xs', onClick: this.copyPortalIdToClipboard}, copyButtonText)
72+
$.button({className: 'btn btn-xs', onClick: this.copyPortalURLToClipboard}, copyButtonText)
7373
)
7474
)
7575
} else {
@@ -91,7 +91,7 @@ class HostPortalBindingComponent {
9191
}
9292
}
9393

94-
copyPortalIdToClipboard () {
94+
copyPortalURLToClipboard () {
9595
const {clipboard} = this.props
9696
clipboard.write(this.getPortalURI())
9797

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const etch = require('etch')
2+
const $ = etch.dom
3+
4+
module.exports =
5+
class JoinViaExternalAppDialog {
6+
constructor ({config, commandRegistry, workspace}) {
7+
this.commandRegistry = commandRegistry
8+
this.workspace = workspace
9+
this.confirm = this.confirm.bind(this)
10+
this.cancel = this.cancel.bind(this)
11+
this.handleBlur = this.handleBlur.bind(this)
12+
this.props = {uri: ''}
13+
etch.initialize(this)
14+
this.disposables = this.commandRegistry.add(this.element, {
15+
'core:confirm': this.confirm,
16+
'core:cancel': this.cancel
17+
})
18+
}
19+
20+
destroy () {
21+
if (this.panel) this.panel.destroy()
22+
23+
this.disposables.dispose()
24+
etch.destroy(this)
25+
}
26+
27+
async show (uri) {
28+
await this.update({uri})
29+
30+
// This dialog could be opened before Atom's workaround for window focus is
31+
// triggered (see https://git.io/vxWDa), so we delay focusing it to prevent
32+
// such workaround from stealing focus from the dialog.
33+
await timeout(5)
34+
35+
// We explicitly add the modal as hidden because of a bug in the auto-focus
36+
// feature that prevents it from working correctly when using visible: true.
37+
this.panel = this.workspace.addModalPanel({item: this, visible: false, autoFocus: true})
38+
this.panel.show()
39+
this.element.focus()
40+
this.element.addEventListener('blur', this.handleBlur)
41+
42+
return new Promise((resolve) => {
43+
this.resolveWithExitStatus = resolve
44+
this.panel.onDidDestroy(() => {
45+
this.panel = null
46+
this.element.removeEventListener('blur', this.handleBlur)
47+
})
48+
})
49+
}
50+
51+
confirm () {
52+
if (this.refs.joinWithoutAskingCheckbox.checked) {
53+
this.confirmAlways()
54+
} else {
55+
this.confirmOnce()
56+
}
57+
}
58+
59+
confirmOnce () {
60+
this.resolveWithExitStatus(this.constructor.EXIT_STATUS.CONFIRM_ONCE)
61+
this.panel.destroy()
62+
}
63+
64+
confirmAlways () {
65+
this.resolveWithExitStatus(this.constructor.EXIT_STATUS.CONFIRM_ALWAYS)
66+
this.panel.destroy()
67+
}
68+
69+
cancel () {
70+
this.resolveWithExitStatus(this.constructor.EXIT_STATUS.CANCEL)
71+
this.panel.destroy()
72+
}
73+
74+
render () {
75+
return $.div({className: 'JoinViaExternalAppDialog', tabIndex: -1},
76+
$.div(null,
77+
$.h1(null, 'Join this portal?'),
78+
$.a({className: 'JoinViaExternalAppDialog-cancel icon icon-x', onClick: this.cancel})
79+
),
80+
$.p({className: 'JoinViaExternalAppDialog-uri'}, this.props.uri),
81+
$.p(null, 'By joining this portal, the other collaborators will see your GitHub username, your avatar, and any edits that you perform inside the portal.'),
82+
$.footer({className: 'JoinViaExternalAppDialog-footer'},
83+
$.label({className: 'input-label'},
84+
$.input({
85+
ref: 'joinWithoutAskingCheckbox',
86+
className: 'input-checkbox',
87+
type: 'checkbox'
88+
}),
89+
$.span(null, 'Always join without asking. I only open URLs from people I trust.')
90+
),
91+
$.button(
92+
{className: 'btn btn-lg btn-primary', onClick: this.confirm},
93+
'Join portal'
94+
)
95+
)
96+
)
97+
}
98+
99+
update (props) {
100+
this.props = props
101+
return etch.update(this)
102+
}
103+
104+
writeAfterUpdate () {
105+
this.refs.joinWithoutAskingCheckbox.checked = false
106+
}
107+
108+
handleBlur (event) {
109+
if (document.hasFocus() && !this.element.contains(event.relatedTarget)) {
110+
this.cancel()
111+
}
112+
}
113+
}
114+
115+
module.exports.EXIT_STATUS = {
116+
CONFIRM_ALWAYS: 0,
117+
CONFIRM_ONCE: 1,
118+
CANCEL: 2
119+
}
120+
121+
function timeout (ms) {
122+
return new Promise((resolve) => setTimeout(resolve, ms))
123+
}

lib/teletype-package.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ const AuthenticationProvider = require('./authentication-provider')
66
const CredentialCache = require('./credential-cache')
77
const TeletypeService = require('./teletype-service')
88
const {findPortalId} = require('./portal-id-helpers')
9+
const JoinViaExternalAppDialog = require('./join-via-external-app-dialog')
910

1011
module.exports =
1112
class TeletypePackage {
1213
constructor (options) {
1314
const {
14-
baseURL, clipboard, commandRegistry, credentialCache, getAtomVersion,
15+
baseURL, config, clipboard, commandRegistry, credentialCache, getAtomVersion,
1516
notificationManager, packageManager, peerConnectionTimeout, pubSubGateway,
1617
pusherKey, pusherOptions, tetherDisconnectWindow, tooltipManager,
1718
workspace
1819
} = options
1920

21+
this.config = config
2022
this.workspace = workspace
2123
this.notificationManager = notificationManager
2224
this.packageManager = packageManager
@@ -41,6 +43,7 @@ class TeletypePackage {
4143
})
4244
this.client.onConnectionError(this.handleConnectionError.bind(this))
4345
this.portalBindingManagerPromise = null
46+
this.joinViaExternalAppDialog = new JoinViaExternalAppDialog({config, commandRegistry, workspace})
4447
this.subscriptions = new CompositeDisposable()
4548
}
4649

@@ -87,9 +90,25 @@ class TeletypePackage {
8790
}
8891
}
8992

90-
handleURI (parsedURI, rawURI) {
93+
async handleURI (parsedURI, rawURI) {
9194
const portalId = findPortalId(parsedURI.pathname) || rawURI
92-
return this.joinPortal(portalId)
95+
96+
if (this.config.get('teletype.askBeforeJoiningPortalViaExternalApp')) {
97+
const {EXIT_STATUS} = JoinViaExternalAppDialog
98+
99+
const status = await this.joinViaExternalAppDialog.show(rawURI)
100+
switch (status) {
101+
case EXIT_STATUS.CONFIRM_ONCE:
102+
return this.joinPortal(portalId)
103+
case EXIT_STATUS.CONFIRM_ALWAYS:
104+
this.config.set('teletype.askBeforeJoiningPortalViaExternalApp', false)
105+
return this.joinPortal(portalId)
106+
default:
107+
break
108+
}
109+
} else {
110+
return this.joinPortal(portalId)
111+
}
93112
}
94113

95114
async sharePortal () {

package.json

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,41 @@
5151
"atom": ">=1.22.0"
5252
},
5353
"configSchema": {
54-
"baseURL": {
55-
"title": "API server base URL",
56-
"description": "This should only be changed for development purposes. Changes take effect on the next package activation.",
57-
"type": "string",
58-
"default": "https://api.teletype.atom.io",
54+
"askBeforeJoiningPortalViaExternalApp": {
55+
"title": "Ask before joining a portal via an external application",
56+
"description": "When set, you will be asked for confirmation every time you follow a portal URL in a third-party application.",
57+
"type": "boolean",
58+
"default": true,
5959
"order": 1
6060
},
61-
"pusherKey": {
62-
"title": "Pusher service key",
63-
"description": "This should only be changed for development purposes. Changes take effect on the next package activation.",
64-
"type": "string",
65-
"default": "f119821248b7429bece3",
66-
"order": 2
67-
},
68-
"pusherCluster": {
69-
"title": "Pusher cluster name",
70-
"description": "This should only be changed for development purposes. Changes take effect on the next package activation.",
71-
"type": "string",
72-
"default": "mt1",
73-
"order": 3
61+
"dev": {
62+
"title": "Development Settings",
63+
"collapsed": true,
64+
"type": "object",
65+
"order": 2,
66+
"properties": {
67+
"baseURL": {
68+
"title": "API server base URL",
69+
"description": "This should only be changed for development purposes. Changes take effect on the next package activation.",
70+
"type": "string",
71+
"default": "https://api.teletype.atom.io",
72+
"order": 1
73+
},
74+
"pusherKey": {
75+
"title": "Pusher service key",
76+
"description": "This should only be changed for development purposes. Changes take effect on the next package activation.",
77+
"type": "string",
78+
"default": "f119821248b7429bece3",
79+
"order": 2
80+
},
81+
"pusherCluster": {
82+
"title": "Pusher cluster name",
83+
"description": "This should only be changed for development purposes. Changes take effect on the next package activation.",
84+
"type": "string",
85+
"default": "mt1",
86+
"order": 3
87+
}
88+
}
7489
}
7590
},
7691
"standard": {

styles/teletype.less

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,45 @@
233233
.octicon(mark-github, 40px);
234234
}
235235

236+
.JoinViaExternalAppDialog {
237+
padding: @component-padding;
238+
239+
&-cancel {
240+
position: absolute;
241+
padding: @component-padding;
242+
top: 0;
243+
right: 0;
244+
text-align: right;
245+
&::before {
246+
margin-right: 0;
247+
}
248+
}
249+
250+
&-uri {
251+
padding: @component-padding/2 @component-padding;
252+
color: @text-color-highlight;
253+
border: 1px solid @base-border-color;
254+
border-radius: @component-border-radius;
255+
background-color: @background-color-highlight;
256+
}
257+
258+
&-footer {
259+
display: flex;
260+
align-items: center;
261+
justify-content: space-between;
262+
}
263+
264+
.input-label {
265+
margin-left: 2em;
266+
margin-right: 3em;
267+
}
268+
269+
.input-checkbox {
270+
margin-left: -2em;
271+
margin-right: .5em;
272+
}
273+
}
274+
236275
.JoinPortalComponent--no-prompt,
237276
.JoinPortalComponent--prompt
238277
{

test/helpers/atom-environments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const environments = []
22

33
exports.buildAtomEnvironment = function buildAtomEnvironment () {
4-
const env = global.buildAtomEnvironment()
4+
const env = global.buildAtomEnvironment({enablePersistence: false})
55
environments.push(env)
66
return env
77
}

0 commit comments

Comments
 (0)