mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-14 05:41:05 +08:00
137 lines
5.4 KiB
CoffeeScript
137 lines
5.4 KiB
CoffeeScript
{shell} = require 'electron'
|
|
GithubStore = require('./github-store').default
|
|
{React} = require 'nylas-exports'
|
|
{RetinaImg, KeyCommandsRegion} = require 'nylas-component-kit'
|
|
|
|
###
|
|
The `ViewOnGithubButton` displays a button whenever there's a relevant
|
|
Github asset to link to.
|
|
|
|
When creating this React component the first consideration was when &
|
|
where we'd be rendered. The next consideration was what data we need to
|
|
display.
|
|
|
|
Unlike a traditional React application, N1 components have very few
|
|
guarantees on who will render them and where they will be rendered. In our
|
|
`lib/main.cjsx` file we registered this component with our
|
|
{ComponentRegistry} for the `"ThreadActionsToolbarButton"` role. That means that
|
|
whenever the "ThreadActionsToolbarButton" region gets rendered, we'll render
|
|
everything registered with that area. Other buttons, such as "Archive" and
|
|
the "Change Label" button are reigstered with that role, so we should
|
|
expect ourselves to showup alongside them.
|
|
|
|
The only data we need is a single relevant to Github. If we have one,
|
|
we'll open it up in a browser. If we don't have one, we'll hide the
|
|
component.
|
|
|
|
Getting that url takes a bit of message parsing. We need to retrieve a
|
|
message body then implement some kind of regex to find and parse out that
|
|
link.
|
|
|
|
We could have put all of that logic in this React Component, but that's
|
|
not what React components should be doing. In N1 a component's only job is
|
|
to display known data and be the first responders to user interaction.
|
|
|
|
We instead create a {GithubStore} to handle the fetching and preparation
|
|
of the data. See that file's documentation for more on how that works.
|
|
|
|
As far as this component is concerned, there will be an entity called
|
|
`GitHubStore` that will expose the correct `link`. That store will then
|
|
notify us when the `link` changes so we can update our state.
|
|
|
|
Once we know our `link` our `render` method can simply be a description of
|
|
how we want to display that link. In this case we're going to make a
|
|
simple button with a GitHub logo in it.
|
|
|
|
We'll also display nothing if there is no link.
|
|
###
|
|
class ViewOnGithubButton extends React.Component
|
|
@displayName: "ViewOnGithubButton"
|
|
@containerRequired: false
|
|
|
|
@propTypes:
|
|
items: React.PropTypes.array
|
|
|
|
#### React methods ####
|
|
# The following methods are React methods that we override. See {React}
|
|
# documentation for more info
|
|
|
|
constructor: (@props) ->
|
|
@state = @_getStateFromStores()
|
|
|
|
# When components mount, it's very common to have them listen to a
|
|
# `Store`. Since most of our React Components in N1 are registered into
|
|
# {ComponentRegistry} regions instead of manually rendered top-down much
|
|
# of our data is side-loaded from stores instead of passed in as props.
|
|
componentDidMount: ->
|
|
# The `listen` method of {NylasStore}s (which {GithubStore}
|
|
# subclasses) returns an "unlistener" function. When the unlistener is
|
|
# invoked (as it is in `componentWillUnmount`) the listener references
|
|
# are cleaned up. Every time the `GithubStore` calls its `trigger`
|
|
# method, the `_onStoreChanged` callback will be fired.
|
|
@_unlisten = GithubStore.listen(@_onStoreChanged)
|
|
|
|
componentWillUnmount: ->
|
|
@_unlisten?()
|
|
|
|
_keymapHandlers: ->
|
|
'github:open': @_openLink
|
|
|
|
render: ->
|
|
return null unless @props.items.length is 1
|
|
return null unless @state.link
|
|
<KeyCommandsRegion globalHandlers={@_keymapHandlers()}>
|
|
<button className="btn btn-toolbar btn-view-on-github"
|
|
onClick={@_openLink}
|
|
title={"Visit Thread on GitHub"}>
|
|
<RetinaImg
|
|
mode={RetinaImg.Mode.ContentIsMask}
|
|
url="nylas://message-view-on-github/assets/github@2x.png" />
|
|
</button>
|
|
</KeyCommandsRegion>
|
|
|
|
|
|
#### Super common N1 Component private methods ####
|
|
|
|
# An extremely common pattern for all N1 components are the methods
|
|
# `onStoreChanged` and `getStateFromStores`.
|
|
#
|
|
# Most N1 components listen to some source of data, which is usally a
|
|
# Store. When the store notifies that something has changed, we need to
|
|
# fetch the fresh data and updated our state.
|
|
#
|
|
# Note that when a Store updates it does not let us know what changed.
|
|
# This is intentional! This forces us to fresh the full latest state
|
|
# from the stores in a more declarative, easy-to-follow way. There are a
|
|
# couple rare exceptions that are only used for performance
|
|
# optimizations.
|
|
|
|
# Note that we bind this method to the class instance's `this`. Any
|
|
# method used as a callback must be bound. In Coffeescript we use the
|
|
# fat arrow (`=>`)
|
|
_onStoreChanged: =>
|
|
@setState(@_getStateFromStores())
|
|
|
|
# getStateFromStores fetches the data the view needs from the
|
|
# appropriate data source (our GithubStore). We return a basic object
|
|
# that can be passed directly into `setState`.
|
|
_getStateFromStores: ->
|
|
return {link: GithubStore.link()}
|
|
|
|
|
|
#### Other utility "private" methods ####
|
|
|
|
# This responds to user interaction. Since it's a callback we have to
|
|
# bind it to the instances's `this` (Coffeescript fat arrow `=>`)
|
|
#
|
|
# In the case of this component we use the Electron `shell` module to
|
|
# request the computer to open the default browser.
|
|
#
|
|
# In other very common cases, user interaction handlers may fire an
|
|
# `Action` across the system for other Stores to respond to. They may
|
|
# also queue a {Task} to eventually perform a mutating API POST or PUT
|
|
# request.
|
|
_openLink: =>
|
|
shell.openExternal(@state.link) if @state.link
|
|
|
|
module.exports = ViewOnGithubButton
|